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.util.Map;
023import java.util.Map.Entry;
024
025import javax.servlet.ServletException;
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.tomcat.util.descriptor.web.FilterDef;
034import org.apache.tomcat.util.descriptor.web.FilterMap;
035import org.apache.tomcat.util.scan.StandardJarScanner;
036import org.nuxeo.common.Environment;
037import org.nuxeo.common.server.WebApplication;
038import org.nuxeo.runtime.RuntimeServiceException;
039import org.nuxeo.runtime.server.FilterDescriptor;
040import org.nuxeo.runtime.server.FilterMappingDescriptor;
041import org.nuxeo.runtime.server.ServerConfigurator;
042import org.nuxeo.runtime.server.ServletContextListenerDescriptor;
043import org.nuxeo.runtime.server.ServletDescriptor;
044
045/**
046 * Configurator for an embedded Tomcat server.
047 *
048 * @since 10.2
049 */
050public class TomcatServerConfigurator implements ServerConfigurator {
051
052    public static final int DEFAULT_PORT = 8080;
053
054    protected Tomcat tomcat;
055
056    @Override
057    public int initialize(int port) {
058        if (port <= 0) {
059            port = DEFAULT_PORT;
060        }
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        return port;
069    }
070
071    @Override
072    public void close() {
073        tomcat = null;
074    }
075
076    @Override
077    public void start() {
078        try {
079            tomcat.start();
080        } catch (LifecycleException e) {
081            throw new RuntimeServiceException(e);
082        }
083    }
084
085    @Override
086    public void stop() {
087        if (tomcat == null) {
088            return;
089        }
090        try {
091            tomcat.stop();
092            tomcat.destroy();
093        } catch (LifecycleException e) {
094            throw new RuntimeServiceException(e);
095        }
096    }
097
098    @Override
099    public void addWepApp(WebApplication descriptor) {
100        String contextPath = normalizeContextPath(descriptor.getContextPath());
101
102        File home = Environment.getDefault().getHome();
103        File docBase = new File(home, descriptor.getWebRoot());
104        docBase.mkdirs(); // make sure the WAR root exists
105        try {
106            Context context = tomcat.addWebapp(contextPath, docBase.getAbsolutePath());
107            StandardJarScanner jarScanner = (StandardJarScanner) context.getJarScanner();
108            // avoid costly scanning, we register everything explicitly
109            jarScanner.setScanManifest(false); // many MANIFEST.MF files have incorrect Class-Path
110            jarScanner.setScanAllDirectories(false);
111            jarScanner.setScanAllFiles(false);
112            jarScanner.setScanBootstrapClassPath(false);
113            jarScanner.setScanClassPath(false);
114        } catch (ServletException e) {
115            throw new RuntimeServiceException(e);
116        }
117    }
118
119    @Override
120    public void addFilter(FilterDescriptor descriptor) {
121        String name = descriptor.getName();
122        Context context = getContextForPath(descriptor.getContext());
123        FilterDef filterDef = new FilterDef();
124        filterDef.setFilterName(name);
125        filterDef.setDisplayName(descriptor.getDisplayName());
126        filterDef.setFilterClass(descriptor.getClazz().getName());
127        Map<String, String> initParams = descriptor.getInitParams();
128        if (initParams != null) {
129            filterDef.getParameterMap().putAll(initParams);
130        }
131        context.addFilterDef(filterDef);
132        for (FilterMappingDescriptor fmd : descriptor.getFilterMappings()) {
133            FilterMap filterMap = new FilterMap();
134            filterMap.setFilterName(name);
135            filterMap.addServletName("*");
136            filterMap.addURLPatternDecoded(fmd.getUrlPattern());
137            for (String dispatch : fmd.getDispatchers()) {
138                filterMap.setDispatcher(dispatch);
139            }
140            context.addFilterMap(filterMap);
141        }
142    }
143
144    @Override
145    public void addServlet(ServletDescriptor descriptor) {
146        String name = descriptor.getName();
147        Context context = getContextForPath(descriptor.getContext());
148        // remove existing servlet, to allow overrides (usually to change init params)
149        Container previous = context.findChild(name);
150        if (previous != null) {
151            context.removeChild(previous);
152        }
153        Wrapper servlet = Tomcat.addServlet(context, name, descriptor.getClazz().getName());
154        Map<String, String> initParams = descriptor.getInitParams();
155        if (initParams != null) {
156            for (Entry<String, String> es : initParams.entrySet()) {
157                servlet.addInitParameter(es.getKey(), es.getValue());
158            }
159        }
160        for (String urlPattern : descriptor.getUrlPatterns()) {
161            context.addServletMappingDecoded(urlPattern, name);
162        }
163    }
164
165    @Override
166    public void addLifecycleListener(ServletContextListenerDescriptor descriptor) {
167        Context context = getContextForPath(descriptor.getContext());
168        context.addApplicationListener(descriptor.getClazz().getName());
169    }
170
171    protected Context getContextForPath(String contextPath) {
172        contextPath = normalizeContextPath(contextPath);
173        Context context = (Context) tomcat.getHost().findChild(contextPath);
174        if (context == null) {
175            context = tomcat.addContext(contextPath, null);
176        }
177        return context;
178    }
179
180    protected String normalizeContextPath(String contextPath) {
181        if (contextPath.equals("/")) {
182            contextPath = "";
183        }
184        return contextPath;
185    }
186
187}