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}