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.LinkedHashSet;
022import java.util.Set;
023
024import javax.servlet.MultipartConfigElement;
025import javax.servlet.Servlet;
026import javax.servlet.ServletContext;
027import javax.servlet.ServletRegistration;
028
029import org.springframework.util.Assert;
030import org.springframework.util.ObjectUtils;
031import org.springframework.util.StringUtils;
032
033/**
034 * A {@link ServletContextInitializer} to register {@link Servlet}s in a Servlet 3.0+
035 * container. Similar to the {@link ServletContext#addServlet(String, Servlet)
036 * registration} features provided by {@link ServletContext} but with a Spring Bean
037 * friendly design.
038 * <p>
039 * The {@link #setServlet(Servlet) servlet} must be specified before calling
040 * {@link #onStartup}. URL mapping can be configured used {@link #setUrlMappings} or
041 * omitted when mapping to '/*' (unless
042 * {@link #ServletRegistrationBean(Servlet, boolean, String...) alwaysMapUrl} is set to
043 * {@code false}). The servlet name will be deduced if not specified.
044 *
045 * @param <T> the type of the {@link Servlet} to register
046 * @author Phillip Webb
047 * @since 1.4.0
048 * @see ServletContextInitializer
049 * @see ServletContext#addServlet(String, Servlet)
050 */
051public class ServletRegistrationBean<T extends Servlet>
052                extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
053
054        private static final String[] DEFAULT_MAPPINGS = { "/*" };
055
056        private T servlet;
057
058        private Set<String> urlMappings = new LinkedHashSet<>();
059
060        private boolean alwaysMapUrl = true;
061
062        private int loadOnStartup = -1;
063
064        private MultipartConfigElement multipartConfig;
065
066        /**
067         * Create a new {@link ServletRegistrationBean} instance.
068         */
069        public ServletRegistrationBean() {
070        }
071
072        /**
073         * Create a new {@link ServletRegistrationBean} instance with the specified
074         * {@link Servlet} and URL mappings.
075         * @param servlet the servlet being mapped
076         * @param urlMappings the URLs being mapped
077         */
078        public ServletRegistrationBean(T servlet, String... urlMappings) {
079                this(servlet, true, urlMappings);
080        }
081
082        /**
083         * Create a new {@link ServletRegistrationBean} instance with the specified
084         * {@link Servlet} and URL mappings.
085         * @param servlet the servlet being mapped
086         * @param alwaysMapUrl if omitted URL mappings should be replaced with '/*'
087         * @param urlMappings the URLs being mapped
088         */
089        public ServletRegistrationBean(T servlet, boolean alwaysMapUrl,
090                        String... urlMappings) {
091                Assert.notNull(servlet, "Servlet must not be null");
092                Assert.notNull(urlMappings, "UrlMappings must not be null");
093                this.servlet = servlet;
094                this.alwaysMapUrl = alwaysMapUrl;
095                this.urlMappings.addAll(Arrays.asList(urlMappings));
096        }
097
098        /**
099         * Sets the servlet to be registered.
100         * @param servlet the servlet
101         */
102        public void setServlet(T servlet) {
103                Assert.notNull(servlet, "Servlet must not be null");
104                this.servlet = servlet;
105        }
106
107        /**
108         * Return the servlet being registered.
109         * @return the servlet
110         */
111        public T getServlet() {
112                return this.servlet;
113        }
114
115        /**
116         * Set the URL mappings for the servlet. If not specified the mapping will default to
117         * '/'. This will replace any previously specified mappings.
118         * @param urlMappings the mappings to set
119         * @see #addUrlMappings(String...)
120         */
121        public void setUrlMappings(Collection<String> urlMappings) {
122                Assert.notNull(urlMappings, "UrlMappings must not be null");
123                this.urlMappings = new LinkedHashSet<>(urlMappings);
124        }
125
126        /**
127         * Return a mutable collection of the URL mappings, as defined in the Servlet
128         * specification, for the servlet.
129         * @return the urlMappings
130         */
131        public Collection<String> getUrlMappings() {
132                return this.urlMappings;
133        }
134
135        /**
136         * Add URL mappings, as defined in the Servlet specification, for the servlet.
137         * @param urlMappings the mappings to add
138         * @see #setUrlMappings(Collection)
139         */
140        public void addUrlMappings(String... urlMappings) {
141                Assert.notNull(urlMappings, "UrlMappings must not be null");
142                this.urlMappings.addAll(Arrays.asList(urlMappings));
143        }
144
145        /**
146         * Sets the {@code loadOnStartup} priority. See
147         * {@link ServletRegistration.Dynamic#setLoadOnStartup} for details.
148         * @param loadOnStartup if load on startup is enabled
149         */
150        public void setLoadOnStartup(int loadOnStartup) {
151                this.loadOnStartup = loadOnStartup;
152        }
153
154        /**
155         * Set the {@link MultipartConfigElement multi-part configuration}.
156         * @param multipartConfig the multi-part configuration to set or {@code null}
157         */
158        public void setMultipartConfig(MultipartConfigElement multipartConfig) {
159                this.multipartConfig = multipartConfig;
160        }
161
162        /**
163         * Returns the {@link MultipartConfigElement multi-part configuration} to be applied
164         * or {@code null}.
165         * @return the multipart config
166         */
167        public MultipartConfigElement getMultipartConfig() {
168                return this.multipartConfig;
169        }
170
171        @Override
172        protected String getDescription() {
173                Assert.notNull(this.servlet, "Servlet must not be null");
174                return "servlet " + getServletName();
175        }
176
177        @Override
178        protected ServletRegistration.Dynamic addRegistration(String description,
179                        ServletContext servletContext) {
180                String name = getServletName();
181                return servletContext.addServlet(name, this.servlet);
182        }
183
184        /**
185         * Configure registration settings. Subclasses can override this method to perform
186         * additional configuration if required.
187         * @param registration the registration
188         */
189        @Override
190        protected void configure(ServletRegistration.Dynamic registration) {
191                super.configure(registration);
192                String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
193                if (urlMapping.length == 0 && this.alwaysMapUrl) {
194                        urlMapping = DEFAULT_MAPPINGS;
195                }
196                if (!ObjectUtils.isEmpty(urlMapping)) {
197                        registration.addMapping(urlMapping);
198                }
199                registration.setLoadOnStartup(this.loadOnStartup);
200                if (this.multipartConfig != null) {
201                        registration.setMultipartConfig(this.multipartConfig);
202                }
203        }
204
205        /**
206         * Returns the servlet name that will be registered.
207         * @return the servlet name
208         */
209        public String getServletName() {
210                return getOrDeduceName(this.servlet);
211        }
212
213        @Override
214        public String toString() {
215                return getServletName() + " urls=" + getUrlMappings();
216        }
217
218}