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.web.servlet.config.annotation;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024
025import javax.servlet.ServletContext;
026
027import org.springframework.beans.factory.BeanInitializationException;
028import org.springframework.context.ApplicationContext;
029import org.springframework.core.Ordered;
030import org.springframework.lang.Nullable;
031import org.springframework.util.Assert;
032import org.springframework.web.HttpRequestHandler;
033import org.springframework.web.accept.ContentNegotiationManager;
034import org.springframework.web.servlet.HandlerMapping;
035import org.springframework.web.servlet.handler.AbstractHandlerMapping;
036import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
037import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
038import org.springframework.web.util.UrlPathHelper;
039
040/**
041 * Stores registrations of resource handlers for serving static resources such as images, css files and others
042 * through Spring MVC including setting cache headers optimized for efficient loading in a web browser.
043 * Resources can be served out of locations under web application root, from the classpath, and others.
044 *
045 * <p>To create a resource handler, use {@link #addResourceHandler(String...)} providing the URL path patterns
046 * for which the handler should be invoked to serve static resources (e.g. {@code "/resources/**"}).
047 *
048 * <p>Then use additional methods on the returned {@link ResourceHandlerRegistration} to add one or more
049 * locations from which to serve static content from (e.g. {{@code "/"},
050 * {@code "classpath:/META-INF/public-web-resources/"}}) or to specify a cache period for served resources.
051 *
052 * @author Rossen Stoyanchev
053 * @since 3.1
054 * @see DefaultServletHandlerConfigurer
055 */
056public class ResourceHandlerRegistry {
057
058        private final ServletContext servletContext;
059
060        private final ApplicationContext applicationContext;
061
062        @Nullable
063        private final ContentNegotiationManager contentNegotiationManager;
064
065        @Nullable
066        private final UrlPathHelper pathHelper;
067
068        private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();
069
070        private int order = Ordered.LOWEST_PRECEDENCE - 1;
071
072
073        /**
074         * Create a new resource handler registry for the given application context.
075         * @param applicationContext the Spring application context
076         * @param servletContext the corresponding Servlet context
077         */
078        public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext) {
079                this(applicationContext, servletContext, null);
080        }
081
082        /**
083         * Create a new resource handler registry for the given application context.
084         * @param applicationContext the Spring application context
085         * @param servletContext the corresponding Servlet context
086         * @param contentNegotiationManager the content negotiation manager to use
087         * @since 4.3
088         */
089        public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
090                        @Nullable ContentNegotiationManager contentNegotiationManager) {
091
092                this(applicationContext, servletContext, contentNegotiationManager, null);
093        }
094
095        /**
096         * A variant of
097         * {@link #ResourceHandlerRegistry(ApplicationContext, ServletContext, ContentNegotiationManager)}
098         * that also accepts the {@link UrlPathHelper} used for mapping requests to static resources.
099         * @since 4.3.13
100         */
101        public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
102                        @Nullable ContentNegotiationManager contentNegotiationManager, @Nullable UrlPathHelper pathHelper) {
103
104                Assert.notNull(applicationContext, "ApplicationContext is required");
105                this.applicationContext = applicationContext;
106                this.servletContext = servletContext;
107                this.contentNegotiationManager = contentNegotiationManager;
108                this.pathHelper = pathHelper;
109        }
110
111
112        /**
113         * Add a resource handler for serving static resources based on the specified URL path patterns.
114         * The handler will be invoked for every incoming request that matches to one of the specified
115         * path patterns.
116         * <p>Patterns like {@code "/static/**"} or {@code "/css/{filename:\\w+\\.css}"} are allowed.
117         * See {@link org.springframework.util.AntPathMatcher} for more details on the syntax.
118         * @return a {@link ResourceHandlerRegistration} to use to further configure the
119         * registered resource handler
120         */
121        public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
122                ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
123                this.registrations.add(registration);
124                return registration;
125        }
126
127        /**
128         * Whether a resource handler has already been registered for the given path pattern.
129         */
130        public boolean hasMappingForPattern(String pathPattern) {
131                for (ResourceHandlerRegistration registration : this.registrations) {
132                        if (Arrays.asList(registration.getPathPatterns()).contains(pathPattern)) {
133                                return true;
134                        }
135                }
136                return false;
137        }
138
139        /**
140         * Specify the order to use for resource handling relative to other {@link HandlerMapping HandlerMappings}
141         * configured in the Spring MVC application context.
142         * <p>The default value used is {@code Integer.MAX_VALUE-1}.
143         */
144        public ResourceHandlerRegistry setOrder(int order) {
145                this.order = order;
146                return this;
147        }
148
149        /**
150         * Return a handler mapping with the mapped resource handlers; or {@code null} in case
151         * of no registrations.
152         */
153        @Nullable
154        @SuppressWarnings("deprecation")
155        protected AbstractHandlerMapping getHandlerMapping() {
156                if (this.registrations.isEmpty()) {
157                        return null;
158                }
159
160                Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
161                for (ResourceHandlerRegistration registration : this.registrations) {
162                        for (String pathPattern : registration.getPathPatterns()) {
163                                ResourceHttpRequestHandler handler = registration.getRequestHandler();
164                                if (this.pathHelper != null) {
165                                        handler.setUrlPathHelper(this.pathHelper);
166                                }
167                                if (this.contentNegotiationManager != null) {
168                                        handler.setContentNegotiationManager(this.contentNegotiationManager);
169                                }
170                                handler.setServletContext(this.servletContext);
171                                handler.setApplicationContext(this.applicationContext);
172                                try {
173                                        handler.afterPropertiesSet();
174                                }
175                                catch (Throwable ex) {
176                                        throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex);
177                                }
178                                urlMap.put(pathPattern, handler);
179                        }
180                }
181
182                return new SimpleUrlHandlerMapping(urlMap, this.order);
183        }
184
185}