001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.beans.propertyeditors; 018 019import java.beans.PropertyEditorSupport; 020import java.io.IOException; 021import java.net.URI; 022import java.net.URISyntaxException; 023 024import org.springframework.core.io.ClassPathResource; 025import org.springframework.lang.Nullable; 026import org.springframework.util.ClassUtils; 027import org.springframework.util.ResourceUtils; 028import org.springframework.util.StringUtils; 029 030/** 031 * Editor for {@code java.net.URI}, to directly populate a URI property 032 * instead of using a String property as bridge. 033 * 034 * <p>Supports Spring-style URI notation: any fully qualified standard URI 035 * ("file:", "http:", etc) and Spring's special "classpath:" pseudo-URL, 036 * which will be resolved to a corresponding URI. 037 * 038 * <p>By default, this editor will encode Strings into URIs. For instance, 039 * a space will be encoded into {@code %20}. This behavior can be changed 040 * by calling the {@link #URIEditor(boolean)} constructor. 041 * 042 * <p>Note: A URI is more relaxed than a URL in that it does not require 043 * a valid protocol to be specified. Any scheme within a valid URI syntax 044 * is allowed, even without a matching protocol handler being registered. 045 * 046 * @author Juergen Hoeller 047 * @since 2.0.2 048 * @see java.net.URI 049 * @see URLEditor 050 */ 051public class URIEditor extends PropertyEditorSupport { 052 053 @Nullable 054 private final ClassLoader classLoader; 055 056 private final boolean encode; 057 058 059 060 /** 061 * Create a new, encoding URIEditor, converting "classpath:" locations into 062 * standard URIs (not trying to resolve them into physical resources). 063 */ 064 public URIEditor() { 065 this(true); 066 } 067 068 /** 069 * Create a new URIEditor, converting "classpath:" locations into 070 * standard URIs (not trying to resolve them into physical resources). 071 * @param encode indicates whether Strings will be encoded or not 072 * @since 3.0 073 */ 074 public URIEditor(boolean encode) { 075 this.classLoader = null; 076 this.encode = encode; 077 } 078 079 /** 080 * Create a new URIEditor, using the given ClassLoader to resolve 081 * "classpath:" locations into physical resource URLs. 082 * @param classLoader the ClassLoader to use for resolving "classpath:" locations 083 * (may be {@code null} to indicate the default ClassLoader) 084 */ 085 public URIEditor(@Nullable ClassLoader classLoader) { 086 this(classLoader, true); 087 } 088 089 /** 090 * Create a new URIEditor, using the given ClassLoader to resolve 091 * "classpath:" locations into physical resource URLs. 092 * @param classLoader the ClassLoader to use for resolving "classpath:" locations 093 * (may be {@code null} to indicate the default ClassLoader) 094 * @param encode indicates whether Strings will be encoded or not 095 * @since 3.0 096 */ 097 public URIEditor(@Nullable ClassLoader classLoader, boolean encode) { 098 this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); 099 this.encode = encode; 100 } 101 102 103 @Override 104 public void setAsText(String text) throws IllegalArgumentException { 105 if (StringUtils.hasText(text)) { 106 String uri = text.trim(); 107 if (this.classLoader != null && uri.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) { 108 ClassPathResource resource = new ClassPathResource( 109 uri.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length()), this.classLoader); 110 try { 111 setValue(resource.getURI()); 112 } 113 catch (IOException ex) { 114 throw new IllegalArgumentException("Could not retrieve URI for " + resource + ": " + ex.getMessage()); 115 } 116 } 117 else { 118 try { 119 setValue(createURI(uri)); 120 } 121 catch (URISyntaxException ex) { 122 throw new IllegalArgumentException("Invalid URI syntax: " + ex.getMessage()); 123 } 124 } 125 } 126 else { 127 setValue(null); 128 } 129 } 130 131 /** 132 * Create a URI instance for the given user-specified String value. 133 * <p>The default implementation encodes the value into a RFC-2396 compliant URI. 134 * @param value the value to convert into a URI instance 135 * @return the URI instance 136 * @throws java.net.URISyntaxException if URI conversion failed 137 */ 138 protected URI createURI(String value) throws URISyntaxException { 139 int colonIndex = value.indexOf(':'); 140 if (this.encode && colonIndex != -1) { 141 int fragmentIndex = value.indexOf('#', colonIndex + 1); 142 String scheme = value.substring(0, colonIndex); 143 String ssp = value.substring(colonIndex + 1, (fragmentIndex > 0 ? fragmentIndex : value.length())); 144 String fragment = (fragmentIndex > 0 ? value.substring(fragmentIndex + 1) : null); 145 return new URI(scheme, ssp, fragment); 146 } 147 else { 148 // not encoding or the value contains no scheme - fallback to default 149 return new URI(value); 150 } 151 } 152 153 154 @Override 155 public String getAsText() { 156 URI value = (URI) getValue(); 157 return (value != null ? value.toString() : ""); 158 } 159 160}