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