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}