001/*
002 * Copyright 2002-2019 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;
018
019import java.net.MalformedURLException;
020import java.net.URL;
021import java.util.Collection;
022import java.util.LinkedHashSet;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.ConcurrentHashMap;
026
027import org.springframework.lang.Nullable;
028import org.springframework.util.Assert;
029import org.springframework.util.ClassUtils;
030import org.springframework.util.ResourceUtils;
031import org.springframework.util.StringUtils;
032
033/**
034 * Default implementation of the {@link ResourceLoader} interface.
035 * Used by {@link ResourceEditor}, and serves as base class for
036 * {@link org.springframework.context.support.AbstractApplicationContext}.
037 * Can also be used standalone.
038 *
039 * <p>Will return a {@link UrlResource} if the location value is a URL,
040 * and a {@link ClassPathResource} if it is a non-URL path or a
041 * "classpath:" pseudo-URL.
042 *
043 * @author Juergen Hoeller
044 * @since 10.03.2004
045 * @see FileSystemResourceLoader
046 * @see org.springframework.context.support.ClassPathXmlApplicationContext
047 */
048public class DefaultResourceLoader implements ResourceLoader {
049
050        @Nullable
051        private ClassLoader classLoader;
052
053        private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
054
055        private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
056
057
058        /**
059         * Create a new DefaultResourceLoader.
060         * <p>ClassLoader access will happen using the thread context class loader
061         * at the time of this ResourceLoader's initialization.
062         * @see java.lang.Thread#getContextClassLoader()
063         */
064        public DefaultResourceLoader() {
065                this.classLoader = ClassUtils.getDefaultClassLoader();
066        }
067
068        /**
069         * Create a new DefaultResourceLoader.
070         * @param classLoader the ClassLoader to load class path resources with, or {@code null}
071         * for using the thread context class loader at the time of actual resource access
072         */
073        public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
074                this.classLoader = classLoader;
075        }
076
077
078        /**
079         * Specify the ClassLoader to load class path resources with, or {@code null}
080         * for using the thread context class loader at the time of actual resource access.
081         * <p>The default is that ClassLoader access will happen using the thread context
082         * class loader at the time of this ResourceLoader's initialization.
083         */
084        public void setClassLoader(@Nullable ClassLoader classLoader) {
085                this.classLoader = classLoader;
086        }
087
088        /**
089         * Return the ClassLoader to load class path resources with.
090         * <p>Will get passed to ClassPathResource's constructor for all
091         * ClassPathResource objects created by this resource loader.
092         * @see ClassPathResource
093         */
094        @Override
095        @Nullable
096        public ClassLoader getClassLoader() {
097                return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
098        }
099
100        /**
101         * Register the given resolver with this resource loader, allowing for
102         * additional protocols to be handled.
103         * <p>Any such resolver will be invoked ahead of this loader's standard
104         * resolution rules. It may therefore also override any default rules.
105         * @since 4.3
106         * @see #getProtocolResolvers()
107         */
108        public void addProtocolResolver(ProtocolResolver resolver) {
109                Assert.notNull(resolver, "ProtocolResolver must not be null");
110                this.protocolResolvers.add(resolver);
111        }
112
113        /**
114         * Return the collection of currently registered protocol resolvers,
115         * allowing for introspection as well as modification.
116         * @since 4.3
117         */
118        public Collection<ProtocolResolver> getProtocolResolvers() {
119                return this.protocolResolvers;
120        }
121
122        /**
123         * Obtain a cache for the given value type, keyed by {@link Resource}.
124         * @param valueType the value type, e.g. an ASM {@code MetadataReader}
125         * @return the cache {@link Map}, shared at the {@code ResourceLoader} level
126         * @since 5.0
127         */
128        @SuppressWarnings("unchecked")
129        public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
130                return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
131        }
132
133        /**
134         * Clear all resource caches in this resource loader.
135         * @since 5.0
136         * @see #getResourceCache
137         */
138        public void clearResourceCaches() {
139                this.resourceCaches.clear();
140        }
141
142
143        @Override
144        public Resource getResource(String location) {
145                Assert.notNull(location, "Location must not be null");
146
147                for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
148                        Resource resource = protocolResolver.resolve(location, this);
149                        if (resource != null) {
150                                return resource;
151                        }
152                }
153
154                if (location.startsWith("/")) {
155                        return getResourceByPath(location);
156                }
157                else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
158                        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
159                }
160                else {
161                        try {
162                                // Try to parse the location as a URL...
163                                URL url = new URL(location);
164                                return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
165                        }
166                        catch (MalformedURLException ex) {
167                                // No URL -> resolve as resource path.
168                                return getResourceByPath(location);
169                        }
170                }
171        }
172
173        /**
174         * Return a Resource handle for the resource at the given path.
175         * <p>The default implementation supports class path locations. This should
176         * be appropriate for standalone implementations but can be overridden,
177         * e.g. for implementations targeted at a Servlet container.
178         * @param path the path to the resource
179         * @return the corresponding Resource handle
180         * @see ClassPathResource
181         * @see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath
182         * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
183         */
184        protected Resource getResourceByPath(String path) {
185                return new ClassPathContextResource(path, getClassLoader());
186        }
187
188
189        /**
190         * ClassPathResource that explicitly expresses a context-relative path
191         * through implementing the ContextResource interface.
192         */
193        protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
194
195                public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
196                        super(path, classLoader);
197                }
198
199                @Override
200                public String getPathWithinContext() {
201                        return getPath();
202                }
203
204                @Override
205                public Resource createRelative(String relativePath) {
206                        String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
207                        return new ClassPathContextResource(pathToUse, getClassLoader());
208                }
209        }
210
211}