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.server;
018
019import java.io.File;
020import java.net.URL;
021import java.nio.charset.Charset;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Locale;
029import java.util.Map;
030import java.util.Set;
031
032import javax.servlet.ServletContext;
033import javax.servlet.ServletException;
034import javax.servlet.SessionCookieConfig;
035
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038
039import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
040import org.springframework.boot.web.server.MimeMappings;
041import org.springframework.boot.web.servlet.ServletContextInitializer;
042import org.springframework.util.Assert;
043import org.springframework.util.ClassUtils;
044
045/**
046 * Abstract base class for {@link ConfigurableServletWebServerFactory} implementations.
047 *
048 * @author Phillip Webb
049 * @author Dave Syer
050 * @author Andy Wilkinson
051 * @author Stephane Nicoll
052 * @author Ivan Sopov
053 * @author EddĂș MelĂ©ndez
054 * @author Brian Clozel
055 * @since 2.0.0
056 */
057public abstract class AbstractServletWebServerFactory
058                extends AbstractConfigurableWebServerFactory
059                implements ConfigurableServletWebServerFactory {
060
061        protected final Log logger = LogFactory.getLog(getClass());
062
063        private String contextPath = "";
064
065        private String displayName;
066
067        private Session session = new Session();
068
069        private boolean registerDefaultServlet = true;
070
071        private MimeMappings mimeMappings = new MimeMappings(MimeMappings.DEFAULT);
072
073        private List<ServletContextInitializer> initializers = new ArrayList<>();
074
075        private Jsp jsp = new Jsp();
076
077        private Map<Locale, Charset> localeCharsetMappings = new HashMap<>();
078
079        private Map<String, String> initParameters = Collections.emptyMap();
080
081        private final DocumentRoot documentRoot = new DocumentRoot(this.logger);
082
083        private final StaticResourceJars staticResourceJars = new StaticResourceJars();
084
085        /**
086         * Create a new {@link AbstractServletWebServerFactory} instance.
087         */
088        public AbstractServletWebServerFactory() {
089        }
090
091        /**
092         * Create a new {@link AbstractServletWebServerFactory} instance with the specified
093         * port.
094         * @param port the port number for the web server
095         */
096        public AbstractServletWebServerFactory(int port) {
097                super(port);
098        }
099
100        /**
101         * Create a new {@link AbstractServletWebServerFactory} instance with the specified
102         * context path and port.
103         * @param contextPath the context path for the web server
104         * @param port the port number for the web server
105         */
106        public AbstractServletWebServerFactory(String contextPath, int port) {
107                super(port);
108                checkContextPath(contextPath);
109                this.contextPath = contextPath;
110        }
111
112        /**
113         * Returns the context path for the web server. The path will start with "/" and not
114         * end with "/". The root context is represented by an empty string.
115         * @return the context path
116         */
117        public String getContextPath() {
118                return this.contextPath;
119        }
120
121        @Override
122        public void setContextPath(String contextPath) {
123                checkContextPath(contextPath);
124                this.contextPath = contextPath;
125        }
126
127        private void checkContextPath(String contextPath) {
128                Assert.notNull(contextPath, "ContextPath must not be null");
129                if (!contextPath.isEmpty()) {
130                        if ("/".equals(contextPath)) {
131                                throw new IllegalArgumentException(
132                                                "Root ContextPath must be specified using an empty string");
133                        }
134                        if (!contextPath.startsWith("/") || contextPath.endsWith("/")) {
135                                throw new IllegalArgumentException(
136                                                "ContextPath must start with '/' and not end with '/'");
137                        }
138                }
139        }
140
141        public String getDisplayName() {
142                return this.displayName;
143        }
144
145        @Override
146        public void setDisplayName(String displayName) {
147                this.displayName = displayName;
148        }
149
150        /**
151         * Flag to indicate that the default servlet should be registered.
152         * @return true if the default servlet is to be registered
153         */
154        public boolean isRegisterDefaultServlet() {
155                return this.registerDefaultServlet;
156        }
157
158        @Override
159        public void setRegisterDefaultServlet(boolean registerDefaultServlet) {
160                this.registerDefaultServlet = registerDefaultServlet;
161        }
162
163        /**
164         * Returns the mime-type mappings.
165         * @return the mimeMappings the mime-type mappings.
166         */
167        public MimeMappings getMimeMappings() {
168                return this.mimeMappings;
169        }
170
171        @Override
172        public void setMimeMappings(MimeMappings mimeMappings) {
173                this.mimeMappings = new MimeMappings(mimeMappings);
174        }
175
176        /**
177         * Returns the document root which will be used by the web context to serve static
178         * files.
179         * @return the document root
180         */
181        public File getDocumentRoot() {
182                return this.documentRoot.getDirectory();
183        }
184
185        @Override
186        public void setDocumentRoot(File documentRoot) {
187                this.documentRoot.setDirectory(documentRoot);
188        }
189
190        @Override
191        public void setInitializers(List<? extends ServletContextInitializer> initializers) {
192                Assert.notNull(initializers, "Initializers must not be null");
193                this.initializers = new ArrayList<>(initializers);
194        }
195
196        @Override
197        public void addInitializers(ServletContextInitializer... initializers) {
198                Assert.notNull(initializers, "Initializers must not be null");
199                this.initializers.addAll(Arrays.asList(initializers));
200        }
201
202        public Jsp getJsp() {
203                return this.jsp;
204        }
205
206        @Override
207        public void setJsp(Jsp jsp) {
208                this.jsp = jsp;
209        }
210
211        public Session getSession() {
212                return this.session;
213        }
214
215        @Override
216        public void setSession(Session session) {
217                this.session = session;
218        }
219
220        /**
221         * Return the Locale to Charset mappings.
222         * @return the charset mappings
223         */
224        public Map<Locale, Charset> getLocaleCharsetMappings() {
225                return this.localeCharsetMappings;
226        }
227
228        @Override
229        public void setLocaleCharsetMappings(Map<Locale, Charset> localeCharsetMappings) {
230                Assert.notNull(localeCharsetMappings, "localeCharsetMappings must not be null");
231                this.localeCharsetMappings = localeCharsetMappings;
232        }
233
234        @Override
235        public void setInitParameters(Map<String, String> initParameters) {
236                this.initParameters = initParameters;
237        }
238
239        public Map<String, String> getInitParameters() {
240                return this.initParameters;
241        }
242
243        /**
244         * Utility method that can be used by subclasses wishing to combine the specified
245         * {@link ServletContextInitializer} parameters with those defined in this instance.
246         * @param initializers the initializers to merge
247         * @return a complete set of merged initializers (with the specified parameters
248         * appearing first)
249         */
250        protected final ServletContextInitializer[] mergeInitializers(
251                        ServletContextInitializer... initializers) {
252                List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
253                mergedInitializers.add((servletContext) -> this.initParameters
254                                .forEach(servletContext::setInitParameter));
255                mergedInitializers.add(new SessionConfiguringInitializer(this.session));
256                mergedInitializers.addAll(Arrays.asList(initializers));
257                mergedInitializers.addAll(this.initializers);
258                return mergedInitializers.toArray(new ServletContextInitializer[0]);
259        }
260
261        /**
262         * Returns whether or not the JSP servlet should be registered with the web server.
263         * @return {@code true} if the servlet should be registered, otherwise {@code false}
264         */
265        protected boolean shouldRegisterJspServlet() {
266                return this.jsp != null && this.jsp.getRegistered() && ClassUtils
267                                .isPresent(this.jsp.getClassName(), getClass().getClassLoader());
268        }
269
270        /**
271         * Returns the absolute document root when it points to a valid directory, logging a
272         * warning and returning {@code null} otherwise.
273         * @return the valid document root
274         */
275        protected final File getValidDocumentRoot() {
276                return this.documentRoot.getValidDirectory();
277        }
278
279        protected final List<URL> getUrlsOfJarsWithMetaInfResources() {
280                return this.staticResourceJars.getUrls();
281        }
282
283        protected final File getValidSessionStoreDir() {
284                return getValidSessionStoreDir(true);
285        }
286
287        protected final File getValidSessionStoreDir(boolean mkdirs) {
288                return this.session.getSessionStoreDirectory().getValidDirectory(mkdirs);
289        }
290
291        /**
292         * {@link ServletContextInitializer} to apply appropriate parts of the {@link Session}
293         * configuration.
294         */
295        private static class SessionConfiguringInitializer
296                        implements ServletContextInitializer {
297
298                private final Session session;
299
300                SessionConfiguringInitializer(Session session) {
301                        this.session = session;
302                }
303
304                @Override
305                public void onStartup(ServletContext servletContext) throws ServletException {
306                        if (this.session.getTrackingModes() != null) {
307                                servletContext
308                                                .setSessionTrackingModes(unwrap(this.session.getTrackingModes()));
309                        }
310                        configureSessionCookie(servletContext.getSessionCookieConfig());
311                }
312
313                private void configureSessionCookie(SessionCookieConfig config) {
314                        Session.Cookie cookie = this.session.getCookie();
315                        if (cookie.getName() != null) {
316                                config.setName(cookie.getName());
317                        }
318                        if (cookie.getDomain() != null) {
319                                config.setDomain(cookie.getDomain());
320                        }
321                        if (cookie.getPath() != null) {
322                                config.setPath(cookie.getPath());
323                        }
324                        if (cookie.getComment() != null) {
325                                config.setComment(cookie.getComment());
326                        }
327                        if (cookie.getHttpOnly() != null) {
328                                config.setHttpOnly(cookie.getHttpOnly());
329                        }
330                        if (cookie.getSecure() != null) {
331                                config.setSecure(cookie.getSecure());
332                        }
333                        if (cookie.getMaxAge() != null) {
334                                config.setMaxAge((int) cookie.getMaxAge().getSeconds());
335                        }
336                }
337
338                private Set<javax.servlet.SessionTrackingMode> unwrap(
339                                Set<Session.SessionTrackingMode> modes) {
340                        if (modes == null) {
341                                return null;
342                        }
343                        Set<javax.servlet.SessionTrackingMode> result = new LinkedHashSet<>();
344                        for (Session.SessionTrackingMode mode : modes) {
345                                result.add(javax.servlet.SessionTrackingMode.valueOf(mode.name()));
346                        }
347                        return result;
348                }
349
350        }
351
352}