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}