001/*
002 * Copyright 2012-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 *      http://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.boot.web.servlet;
018
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.EnumSet;
023import java.util.LinkedHashSet;
024import java.util.Set;
025
026import javax.servlet.DispatcherType;
027import javax.servlet.Filter;
028import javax.servlet.FilterRegistration;
029import javax.servlet.FilterRegistration.Dynamic;
030import javax.servlet.ServletContext;
031
032import org.springframework.util.Assert;
033import org.springframework.util.StringUtils;
034
035/**
036 * Abstract base {@link ServletContextInitializer} to register {@link Filter}s in a
037 * Servlet 3.0+ container.
038 *
039 * @param <T> the type of {@link Filter} to register
040 * @author Phillip Webb
041 * @author Brian Clozel
042 * @since 2.0.1
043 */
044public abstract class AbstractFilterRegistrationBean<T extends Filter>
045                extends DynamicRegistrationBean<Dynamic> {
046
047        /**
048         * Filters that wrap the servlet request should be ordered less than or equal to this.
049         * @deprecated since 2.1.0 in favor of
050         * {@code OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER}
051         */
052        @Deprecated
053        protected static final int REQUEST_WRAPPER_FILTER_MAX_ORDER = 0;
054
055        private static final String[] DEFAULT_URL_MAPPINGS = { "/*" };
056
057        private Set<ServletRegistrationBean<?>> servletRegistrationBeans = new LinkedHashSet<>();
058
059        private Set<String> servletNames = new LinkedHashSet<>();
060
061        private Set<String> urlPatterns = new LinkedHashSet<>();
062
063        private EnumSet<DispatcherType> dispatcherTypes;
064
065        private boolean matchAfter = false;
066
067        /**
068         * Create a new instance to be registered with the specified
069         * {@link ServletRegistrationBean}s.
070         * @param servletRegistrationBeans associate {@link ServletRegistrationBean}s
071         */
072        AbstractFilterRegistrationBean(
073                        ServletRegistrationBean<?>... servletRegistrationBeans) {
074                Assert.notNull(servletRegistrationBeans,
075                                "ServletRegistrationBeans must not be null");
076                Collections.addAll(this.servletRegistrationBeans, servletRegistrationBeans);
077        }
078
079        /**
080         * Set {@link ServletRegistrationBean}s that the filter will be registered against.
081         * @param servletRegistrationBeans the Servlet registration beans
082         */
083        public void setServletRegistrationBeans(
084                        Collection<? extends ServletRegistrationBean<?>> servletRegistrationBeans) {
085                Assert.notNull(servletRegistrationBeans,
086                                "ServletRegistrationBeans must not be null");
087                this.servletRegistrationBeans = new LinkedHashSet<>(servletRegistrationBeans);
088        }
089
090        /**
091         * Return a mutable collection of the {@link ServletRegistrationBean} that the filter
092         * will be registered against. {@link ServletRegistrationBean}s.
093         * @return the Servlet registration beans
094         * @see #setServletNames
095         * @see #setUrlPatterns
096         */
097        public Collection<ServletRegistrationBean<?>> getServletRegistrationBeans() {
098                return this.servletRegistrationBeans;
099        }
100
101        /**
102         * Add {@link ServletRegistrationBean}s for the filter.
103         * @param servletRegistrationBeans the servlet registration beans to add
104         * @see #setServletRegistrationBeans
105         */
106        public void addServletRegistrationBeans(
107                        ServletRegistrationBean<?>... servletRegistrationBeans) {
108                Assert.notNull(servletRegistrationBeans,
109                                "ServletRegistrationBeans must not be null");
110                Collections.addAll(this.servletRegistrationBeans, servletRegistrationBeans);
111        }
112
113        /**
114         * Set servlet names that the filter will be registered against. This will replace any
115         * previously specified servlet names.
116         * @param servletNames the servlet names
117         * @see #setServletRegistrationBeans
118         * @see #setUrlPatterns
119         */
120        public void setServletNames(Collection<String> servletNames) {
121                Assert.notNull(servletNames, "ServletNames must not be null");
122                this.servletNames = new LinkedHashSet<>(servletNames);
123        }
124
125        /**
126         * Return a mutable collection of servlet names that the filter will be registered
127         * against.
128         * @return the servlet names
129         */
130        public Collection<String> getServletNames() {
131                return this.servletNames;
132        }
133
134        /**
135         * Add servlet names for the filter.
136         * @param servletNames the servlet names to add
137         */
138        public void addServletNames(String... servletNames) {
139                Assert.notNull(servletNames, "ServletNames must not be null");
140                this.servletNames.addAll(Arrays.asList(servletNames));
141        }
142
143        /**
144         * Set the URL patterns that the filter will be registered against. This will replace
145         * any previously specified URL patterns.
146         * @param urlPatterns the URL patterns
147         * @see #setServletRegistrationBeans
148         * @see #setServletNames
149         */
150        public void setUrlPatterns(Collection<String> urlPatterns) {
151                Assert.notNull(urlPatterns, "UrlPatterns must not be null");
152                this.urlPatterns = new LinkedHashSet<>(urlPatterns);
153        }
154
155        /**
156         * Return a mutable collection of URL patterns, as defined in the Servlet
157         * specification, that the filter will be registered against.
158         * @return the URL patterns
159         */
160        public Collection<String> getUrlPatterns() {
161                return this.urlPatterns;
162        }
163
164        /**
165         * Add URL patterns, as defined in the Servlet specification, that the filter will be
166         * registered against.
167         * @param urlPatterns the URL patterns
168         */
169        public void addUrlPatterns(String... urlPatterns) {
170                Assert.notNull(urlPatterns, "UrlPatterns must not be null");
171                Collections.addAll(this.urlPatterns, urlPatterns);
172        }
173
174        /**
175         * Convenience method to {@link #setDispatcherTypes(EnumSet) set dispatcher types}
176         * using the specified elements.
177         * @param first the first dispatcher type
178         * @param rest additional dispatcher types
179         */
180        public void setDispatcherTypes(DispatcherType first, DispatcherType... rest) {
181                this.dispatcherTypes = EnumSet.of(first, rest);
182        }
183
184        /**
185         * Sets the dispatcher types that should be used with the registration. If not
186         * specified the types will be deduced based on the value of
187         * {@link #isAsyncSupported()}.
188         * @param dispatcherTypes the dispatcher types
189         */
190        public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes) {
191                this.dispatcherTypes = dispatcherTypes;
192        }
193
194        /**
195         * Set if the filter mappings should be matched after any declared filter mappings of
196         * the ServletContext. Defaults to {@code false} indicating the filters are supposed
197         * to be matched before any declared filter mappings of the ServletContext.
198         * @param matchAfter if filter mappings are matched after
199         */
200        public void setMatchAfter(boolean matchAfter) {
201                this.matchAfter = matchAfter;
202        }
203
204        /**
205         * Return if filter mappings should be matched after any declared Filter mappings of
206         * the ServletContext.
207         * @return if filter mappings are matched after
208         */
209        public boolean isMatchAfter() {
210                return this.matchAfter;
211        }
212
213        @Override
214        protected String getDescription() {
215                Filter filter = getFilter();
216                Assert.notNull(filter, "Filter must not be null");
217                return "filter " + getOrDeduceName(filter);
218        }
219
220        @Override
221        protected Dynamic addRegistration(String description, ServletContext servletContext) {
222                Filter filter = getFilter();
223                return servletContext.addFilter(getOrDeduceName(filter), filter);
224        }
225
226        /**
227         * Configure registration settings. Subclasses can override this method to perform
228         * additional configuration if required.
229         * @param registration the registration
230         */
231        @Override
232        protected void configure(FilterRegistration.Dynamic registration) {
233                super.configure(registration);
234                EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
235                if (dispatcherTypes == null) {
236                        dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
237                }
238                Set<String> servletNames = new LinkedHashSet<>();
239                for (ServletRegistrationBean<?> servletRegistrationBean : this.servletRegistrationBeans) {
240                        servletNames.add(servletRegistrationBean.getServletName());
241                }
242                servletNames.addAll(this.servletNames);
243                if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
244                        registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
245                                        DEFAULT_URL_MAPPINGS);
246                }
247                else {
248                        if (!servletNames.isEmpty()) {
249                                registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
250                                                StringUtils.toStringArray(servletNames));
251                        }
252                        if (!this.urlPatterns.isEmpty()) {
253                                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
254                                                StringUtils.toStringArray(this.urlPatterns));
255                        }
256                }
257        }
258
259        /**
260         * Return the {@link Filter} to be registered.
261         * @return the filter
262         */
263        public abstract T getFilter();
264
265        @Override
266        public String toString() {
267                StringBuilder builder = new StringBuilder(getOrDeduceName(this));
268                if (this.servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
269                        builder.append(" urls=").append(Arrays.toString(DEFAULT_URL_MAPPINGS));
270                }
271                else {
272                        if (!this.servletNames.isEmpty()) {
273                                builder.append(" servlets=").append(this.servletNames);
274                        }
275                        if (!this.urlPatterns.isEmpty()) {
276                                builder.append(" urls=").append(this.urlPatterns);
277                        }
278                }
279                return builder.toString();
280        }
281
282}