001/* 002 * (C) Copyright 2018 Nuxeo (http://nuxeo.com/) and others. 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 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.runtime.server.tomcat; 020 021import java.io.File; 022import java.io.IOException; 023import java.nio.file.Paths; 024import java.util.Map; 025import java.util.Map.Entry; 026 027import org.apache.catalina.Container; 028import org.apache.catalina.Context; 029import org.apache.catalina.LifecycleException; 030import org.apache.catalina.Wrapper; 031import org.apache.catalina.connector.Connector; 032import org.apache.catalina.startup.Tomcat; 033import org.apache.commons.io.FileUtils; 034import org.apache.logging.log4j.LogManager; 035import org.apache.logging.log4j.Logger; 036import org.apache.tomcat.util.descriptor.web.FilterDef; 037import org.apache.tomcat.util.descriptor.web.FilterMap; 038import org.apache.tomcat.util.scan.StandardJarScanner; 039import org.nuxeo.common.Environment; 040import org.nuxeo.common.server.WebApplication; 041import org.nuxeo.runtime.RuntimeServiceException; 042import org.nuxeo.runtime.server.FilterDescriptor; 043import org.nuxeo.runtime.server.FilterMappingDescriptor; 044import org.nuxeo.runtime.server.ServerConfigurator; 045import org.nuxeo.runtime.server.ServletContextListenerDescriptor; 046import org.nuxeo.runtime.server.ServletDescriptor; 047 048/** 049 * Configurator for an embedded Tomcat server. 050 * 051 * @since 10.2 052 */ 053public class TomcatServerConfigurator implements ServerConfigurator { 054 055 private static final Logger log = LogManager.getLogger(TomcatServerConfigurator.class); 056 057 protected Tomcat tomcat; 058 059 @Override 060 public int initialize(int port) { 061 tomcat = new Tomcat(); 062 tomcat.setBaseDir("."); // for tmp dir 063 tomcat.setHostname("localhost"); 064 tomcat.setPort(port); 065 Connector connector = tomcat.getConnector(); 066 connector.setProperty("maxKeepAliveRequests", "1"); // vital for clean shutdown 067 connector.setProperty("socket.soReuseAddress", "true"); 068 log.info("Configuring test Tomcat on port: {}", port); 069 return port; 070 } 071 072 @Override 073 public void close() { 074 tomcat = null; 075 } 076 077 @Override 078 public void start() { 079 try { 080 tomcat.start(); 081 } catch (LifecycleException e) { 082 throw new RuntimeServiceException(e); 083 } 084 } 085 086 @Override 087 public void stop() { 088 if (tomcat == null) { 089 return; 090 } 091 try { 092 tomcat.stop(); 093 File workDirectory = Paths.get(tomcat.getServer().getCatalinaHome().getAbsolutePath(), "work").toFile(); 094 FileUtils.deleteDirectory(workDirectory); 095 tomcat.destroy(); 096 } catch (LifecycleException | IOException e) { 097 throw new RuntimeServiceException(e); 098 } 099 } 100 101 @Override 102 public void addWepApp(WebApplication descriptor) { 103 String contextPath = normalizeContextPath(descriptor.getContextPath()); 104 105 File home = Environment.getDefault().getHome(); 106 File docBase = new File(home, descriptor.getWebRoot()); 107 docBase.mkdirs(); // make sure the WAR root exists 108 Context context = tomcat.addWebapp(contextPath, docBase.getAbsolutePath()); 109 StandardJarScanner jarScanner = (StandardJarScanner) context.getJarScanner(); 110 // avoid costly scanning, we register everything explicitly 111 jarScanner.setScanManifest(false); // many MANIFEST.MF files have incorrect Class-Path 112 jarScanner.setScanAllDirectories(false); 113 jarScanner.setScanAllFiles(false); 114 jarScanner.setScanBootstrapClassPath(false); 115 jarScanner.setScanClassPath(false); 116 } 117 118 @Override 119 public void addFilter(FilterDescriptor descriptor) { 120 String name = descriptor.getName(); 121 Context context = getContextForPath(descriptor.getContext()); 122 FilterDef filterDef = new FilterDef(); 123 filterDef.setFilterName(name); 124 filterDef.setDisplayName(descriptor.getDisplayName()); 125 filterDef.setFilterClass(descriptor.getClazz().getName()); 126 Map<String, String> initParams = descriptor.getInitParams(); 127 if (initParams != null) { 128 filterDef.getParameterMap().putAll(initParams); 129 } 130 context.addFilterDef(filterDef); 131 for (FilterMappingDescriptor fmd : descriptor.getFilterMappings()) { 132 FilterMap filterMap = new FilterMap(); 133 filterMap.setFilterName(name); 134 filterMap.addURLPatternDecoded(fmd.getUrlPattern()); 135 for (String dispatch : fmd.getDispatchers()) { 136 filterMap.setDispatcher(dispatch); 137 } 138 context.addFilterMap(filterMap); 139 } 140 } 141 142 @Override 143 public void addServlet(ServletDescriptor descriptor) { 144 String name = descriptor.getName(); 145 Context context = getContextForPath(descriptor.getContext()); 146 // remove existing servlet, to allow overrides (usually to change init params) 147 Container previous = context.findChild(name); 148 if (previous != null) { 149 context.removeChild(previous); 150 } 151 Wrapper servlet = Tomcat.addServlet(context, name, descriptor.getClazz().getName()); 152 Map<String, String> initParams = descriptor.getInitParams(); 153 if (initParams != null) { 154 for (Entry<String, String> es : initParams.entrySet()) { 155 servlet.addInitParameter(es.getKey(), es.getValue()); 156 } 157 } 158 for (String urlPattern : descriptor.getUrlPatterns()) { 159 context.addServletMappingDecoded(urlPattern, name); 160 } 161 } 162 163 @Override 164 public void addLifecycleListener(ServletContextListenerDescriptor descriptor) { 165 Context context = getContextForPath(descriptor.getContext()); 166 context.addApplicationListener(descriptor.getClazz().getName()); 167 } 168 169 protected Context getContextForPath(String contextPath) { 170 contextPath = normalizeContextPath(contextPath); 171 Context context = (Context) tomcat.getHost().findChild(contextPath); 172 if (context == null) { 173 context = tomcat.addContext(contextPath, null); 174 } 175 return context; 176 } 177 178 protected String normalizeContextPath(String contextPath) { 179 if (contextPath.equals("/")) { 180 contextPath = ""; 181 } 182 return contextPath; 183 } 184 185}