001/* 002 * Copyright 2002-2018 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.core.io.support; 018 019import java.beans.PropertyEditorSupport; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.List; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.core.env.Environment; 030import org.springframework.core.env.PropertyResolver; 031import org.springframework.core.env.StandardEnvironment; 032import org.springframework.core.io.Resource; 033import org.springframework.lang.Nullable; 034import org.springframework.util.Assert; 035 036/** 037 * Editor for {@link org.springframework.core.io.Resource} arrays, to 038 * automatically convert {@code String} location patterns 039 * (e.g. {@code "file:C:/my*.txt"} or {@code "classpath*:myfile.txt"}) 040 * to {@code Resource} array properties. Can also translate a collection 041 * or array of location patterns into a merged Resource array. 042 * 043 * <p>A path may contain {@code ${...}} placeholders, to be 044 * resolved as {@link org.springframework.core.env.Environment} properties: 045 * e.g. {@code ${user.dir}}. Unresolvable placeholders are ignored by default. 046 * 047 * <p>Delegates to a {@link ResourcePatternResolver}, 048 * by default using a {@link PathMatchingResourcePatternResolver}. 049 * 050 * @author Juergen Hoeller 051 * @author Chris Beams 052 * @since 1.1.2 053 * @see org.springframework.core.io.Resource 054 * @see ResourcePatternResolver 055 * @see PathMatchingResourcePatternResolver 056 */ 057public class ResourceArrayPropertyEditor extends PropertyEditorSupport { 058 059 private static final Log logger = LogFactory.getLog(ResourceArrayPropertyEditor.class); 060 061 private final ResourcePatternResolver resourcePatternResolver; 062 063 @Nullable 064 private PropertyResolver propertyResolver; 065 066 private final boolean ignoreUnresolvablePlaceholders; 067 068 069 /** 070 * Create a new ResourceArrayPropertyEditor with a default 071 * {@link PathMatchingResourcePatternResolver} and {@link StandardEnvironment}. 072 * @see PathMatchingResourcePatternResolver 073 * @see Environment 074 */ 075 public ResourceArrayPropertyEditor() { 076 this(new PathMatchingResourcePatternResolver(), null, true); 077 } 078 079 /** 080 * Create a new ResourceArrayPropertyEditor with the given {@link ResourcePatternResolver} 081 * and {@link PropertyResolver} (typically an {@link Environment}). 082 * @param resourcePatternResolver the ResourcePatternResolver to use 083 * @param propertyResolver the PropertyResolver to use 084 */ 085 public ResourceArrayPropertyEditor( 086 ResourcePatternResolver resourcePatternResolver, @Nullable PropertyResolver propertyResolver) { 087 088 this(resourcePatternResolver, propertyResolver, true); 089 } 090 091 /** 092 * Create a new ResourceArrayPropertyEditor with the given {@link ResourcePatternResolver} 093 * and {@link PropertyResolver} (typically an {@link Environment}). 094 * @param resourcePatternResolver the ResourcePatternResolver to use 095 * @param propertyResolver the PropertyResolver to use 096 * @param ignoreUnresolvablePlaceholders whether to ignore unresolvable placeholders 097 * if no corresponding system property could be found 098 */ 099 public ResourceArrayPropertyEditor(ResourcePatternResolver resourcePatternResolver, 100 @Nullable PropertyResolver propertyResolver, boolean ignoreUnresolvablePlaceholders) { 101 102 Assert.notNull(resourcePatternResolver, "ResourcePatternResolver must not be null"); 103 this.resourcePatternResolver = resourcePatternResolver; 104 this.propertyResolver = propertyResolver; 105 this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; 106 } 107 108 109 /** 110 * Treat the given text as a location pattern and convert it to a Resource array. 111 */ 112 @Override 113 public void setAsText(String text) { 114 String pattern = resolvePath(text).trim(); 115 try { 116 setValue(this.resourcePatternResolver.getResources(pattern)); 117 } 118 catch (IOException ex) { 119 throw new IllegalArgumentException( 120 "Could not resolve resource location pattern [" + pattern + "]: " + ex.getMessage()); 121 } 122 } 123 124 /** 125 * Treat the given value as a collection or array and convert it to a Resource array. 126 * Considers String elements as location patterns and takes Resource elements as-is. 127 */ 128 @Override 129 public void setValue(Object value) throws IllegalArgumentException { 130 if (value instanceof Collection || (value instanceof Object[] && !(value instanceof Resource[]))) { 131 Collection<?> input = (value instanceof Collection ? (Collection<?>) value : Arrays.asList((Object[]) value)); 132 List<Resource> merged = new ArrayList<>(); 133 for (Object element : input) { 134 if (element instanceof String) { 135 // A location pattern: resolve it into a Resource array. 136 // Might point to a single resource or to multiple resources. 137 String pattern = resolvePath((String) element).trim(); 138 try { 139 Resource[] resources = this.resourcePatternResolver.getResources(pattern); 140 for (Resource resource : resources) { 141 if (!merged.contains(resource)) { 142 merged.add(resource); 143 } 144 } 145 } 146 catch (IOException ex) { 147 // ignore - might be an unresolved placeholder or non-existing base directory 148 if (logger.isDebugEnabled()) { 149 logger.debug("Could not retrieve resources for pattern '" + pattern + "'", ex); 150 } 151 } 152 } 153 else if (element instanceof Resource) { 154 // A Resource object: add it to the result. 155 Resource resource = (Resource) element; 156 if (!merged.contains(resource)) { 157 merged.add(resource); 158 } 159 } 160 else { 161 throw new IllegalArgumentException("Cannot convert element [" + element + "] to [" + 162 Resource.class.getName() + "]: only location String and Resource object supported"); 163 } 164 } 165 super.setValue(merged.toArray(new Resource[0])); 166 } 167 168 else { 169 // An arbitrary value: probably a String or a Resource array. 170 // setAsText will be called for a String; a Resource array will be used as-is. 171 super.setValue(value); 172 } 173 } 174 175 /** 176 * Resolve the given path, replacing placeholders with 177 * corresponding system property values if necessary. 178 * @param path the original file path 179 * @return the resolved file path 180 * @see PropertyResolver#resolvePlaceholders 181 * @see PropertyResolver#resolveRequiredPlaceholders(String) 182 */ 183 protected String resolvePath(String path) { 184 if (this.propertyResolver == null) { 185 this.propertyResolver = new StandardEnvironment(); 186 } 187 return (this.ignoreUnresolvablePlaceholders ? this.propertyResolver.resolvePlaceholders(path) : 188 this.propertyResolver.resolveRequiredPlaceholders(path)); 189 } 190 191}