svn commit: r1799708 - /ofbiz/ofbiz-framework/trunk/framework/catalina/src/main/java/org/apache/ofbiz/catalina/container/CatalinaContainer.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1799708 - /ofbiz/ofbiz-framework/trunk/framework/catalina/src/main/java/org/apache/ofbiz/catalina/container/CatalinaContainer.java

Taher Alkhateeb
Author: taher
Date: Fri Jun 23 21:34:48 2017
New Revision: 1799708

URL: http://svn.apache.org/viewvc?rev=1799708&view=rev
Log:
Implemented: Refactor CatalinaContainer
(OFBIZ-9392)

No functional change, but an almost complete rewrite of the tomcat container
with the following highlights:

- breakup the logic in init() many smaller functions each specializing in one
  thing
- Unify the host creation logic between main host and context-specific hosts
- introduce streams and lambdas where appropriate
- rename loadComponents() to loadWebapps()
- rename createContext() to createCallableContext()
- rename configureContext() to prepareContext()
- remove instance variables that are not necessary / redundant for operating
  the container correctly and refactor the code logic accordingly
- remove unnecessary valve comments and point to documentation URL
- remove any commented out code
- remove the static block for initializing SSLUtil.loadJsseProperties(). This
- code is already called and hence redundant
- remove redundant / dead / unused code
- add missing FilterDef to context
- rename J2EE server from "ofbiz container 3.1" to "ofbiz container"
- lots and lots of re-arranging and small code improvements

Thanks: Jacopo Capellato, Michael Brohl, Scott Gray and Jacques Le Roux for
        your help in testing, reviewing and providing feedback.

Modified:
    ofbiz/ofbiz-framework/trunk/framework/catalina/src/main/java/org/apache/ofbiz/catalina/container/CatalinaContainer.java

Modified: ofbiz/ofbiz-framework/trunk/framework/catalina/src/main/java/org/apache/ofbiz/catalina/container/CatalinaContainer.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/catalina/src/main/java/org/apache/ofbiz/catalina/container/CatalinaContainer.java?rev=1799708&r1=1799707&r2=1799708&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/catalina/src/main/java/org/apache/ofbiz/catalina/container/CatalinaContainer.java (original)
+++ ofbiz/ofbiz-framework/trunk/framework/catalina/src/main/java/org/apache/ofbiz/catalina/container/CatalinaContainer.java Fri Jun 23 21:34:48 2017
@@ -19,33 +19,36 @@
 package org.apache.ofbiz.catalina.container;
 
 import java.io.File;
-import java.net.MalformedURLException;
+import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
 
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
+import javax.xml.parsers.ParserConfigurationException;
 
-import org.apache.catalina.Cluster;
 import org.apache.catalina.Context;
 import org.apache.catalina.Engine;
 import org.apache.catalina.Globals;
 import org.apache.catalina.Host;
 import org.apache.catalina.LifecycleException;
-import org.apache.catalina.Manager;
+import org.apache.catalina.Valve;
 import org.apache.catalina.connector.Connector;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.core.StandardEngine;
 import org.apache.catalina.core.StandardHost;
 import org.apache.catalina.core.StandardServer;
 import org.apache.catalina.filters.RequestDumperFilter;
+import org.apache.catalina.ha.ClusterManager;
 import org.apache.catalina.ha.tcp.ReplicationValve;
 import org.apache.catalina.ha.tcp.SimpleTcpCluster;
 import org.apache.catalina.loader.WebappLoader;
@@ -68,135 +71,81 @@ import org.apache.ofbiz.base.component.C
 import org.apache.ofbiz.base.concurrent.ExecutionPool;
 import org.apache.ofbiz.base.container.Container;
 import org.apache.ofbiz.base.container.ContainerConfig;
+import org.apache.ofbiz.base.container.ContainerConfig.Configuration;
 import org.apache.ofbiz.base.container.ContainerConfig.Configuration.Property;
 import org.apache.ofbiz.base.container.ContainerException;
 import org.apache.ofbiz.base.location.FlexibleLocation;
 import org.apache.ofbiz.base.start.Start;
 import org.apache.ofbiz.base.start.StartupCommand;
 import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.SSLUtil;
 import org.apache.ofbiz.base.util.UtilValidate;
 import org.apache.ofbiz.base.util.UtilXml;
 import org.w3c.dom.Document;
-
-/*
- * --- Access Log Pattern Information - From Tomcat 5 AccessLogValve.java
- * <p>Patterns for the logged message may include constant text or any of the
- * following replacement strings, for which the corresponding information
- * from the specified Response is substituted:</p>
- * <ul>
- * <li><b>%a</b> - Remote IP address
- * <li><b>%A</b> - Local IP address
- * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes
- *     were sent
- * <li><b>%B</b> - Bytes sent, excluding HTTP headers
- * <li><b>%h</b> - Remote host name
- * <li><b>%H</b> - Request protocol
- * <li><b>%l</b> - Remote logical username from identd (always returns '-')
- * <li><b>%m</b> - Request method
- * <li><b>%p</b> - Local port
- * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise
- *     an empty string
- * <li><b>%r</b> - First line of the request
- * <li><b>%s</b> - HTTP status code of the response
- * <li><b>%S</b> - User session ID
- * <li><b>%t</b> - Date and time, in Common Log Format format
- * <li><b>%u</b> - Remote user that was authenticated
- * <li><b>%U</b> - Requested URL path
- * <li><b>%v</b> - Local server name
- * <li><b>%D</b> - Time taken to process the request, in millis
- * <li><b>%T</b> - Time taken to process the request, in seconds
- * </ul>
- * <p>In addition, the caller can specify one of the following aliases for
- * commonly utilized patterns:</p>
- * <ul>
- * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code>
- * <li><b>combined</b> -
- *   <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
- * </ul>
- *
- * <p>
- * There is also support to write information from the cookie, incoming
- * header, the Session or something else in the ServletRequest.<br/>
- * It is modeled after the apache syntax:
- * <ul>
- * <li><code>%{xxx}i</code> for incoming headers
- * <li><code>%{xxx}c</code> for a specific cookie
- * <li><code>%{xxx}r</code> xxx is an attribute in the ServletRequest
- * <li><code>%{xxx}s</code> xxx is an attribute in the HttpSession
- * </ul>
- * </p>
- */
+import org.xml.sax.SAXException;
 
 /**
  * CatalinaContainer -  Tomcat
  *
+ * For more information about the AccessLogValve pattern visit the
+ * <a href="https://tomcat.apache.org/tomcat-8.0-doc/config/valve.html#Access_Log_Valve">Documentation</a>
  */
 public class CatalinaContainer implements Container {
 
-    public static final String CATALINA_HOSTS_HOME = System.getProperty("ofbiz.home") + "/framework/catalina/hosts";
-    public static final String J2EE_SERVER = "OFBiz Container 3.1";
-    public static final String J2EE_APP = "OFBiz";
     public static final String module = CatalinaContainer.class.getName();
-    private static final ThreadGroup CATALINA_THREAD_GROUP = new ThreadGroup("CatalinaContainer");
-
-    // load the JSSE properties (set the trust store)
-    static {
-        SSLUtil.loadJsseProperties();
-    }
-
-    private Tomcat tomcat = null;
-    protected Map<String, ContainerConfig.Configuration.Property> clusterConfig = new HashMap<String, ContainerConfig.Configuration.Property>();
-
-    protected boolean contextReloadable = false;
-    protected boolean crossContext = false;
-    protected boolean distribute = false;
-
-    protected String catalinaRuntimeHome;
 
     private String name;
+    private Tomcat tomcat;
 
     @Override
     public void init(List<StartupCommand> ofbizCommands, String name, String configFile) throws ContainerException {
-        this.name = name;
-        // get the container config
-        ContainerConfig.Configuration cc = ContainerConfig.getConfiguration(name, configFile);
-        if (cc == null) {
-            throw new ContainerException("No catalina-container configuration found in container config!");
-        }
 
-        // embedded properties
-        boolean useNaming = ContainerConfig.getPropertyValue(cc, "use-naming", false);
-        //int debug = ContainerConfig.getPropertyValue(cc, "debug", 0);
-
-        // grab some global context settings
-        this.contextReloadable = ContainerConfig.getPropertyValue(cc, "apps-context-reloadable", false);
-        this.crossContext = ContainerConfig.getPropertyValue(cc, "apps-cross-context", true);
-        this.distribute = ContainerConfig.getPropertyValue(cc, "apps-distributable", true);
+        this.name = name;
+        ContainerConfig.Configuration configuration = ContainerConfig.getConfiguration(name, configFile);
+        Property engineConfig = retrieveTomcatEngineConfig(configuration);
 
-        this.catalinaRuntimeHome = ContainerConfig.getPropertyValue(cc, "catalina-runtime-home", "runtime/catalina");
+        // tomcat setup
+        tomcat = prepareTomcatServer(configuration, engineConfig);
+        Engine engine = prepareTomcatEngine(tomcat, engineConfig);
+        Host host = prepareHost(tomcat, null);
+
+        // clustering, valves and connectors setup
+        Property clusterProps = prepareTomcatClustering(host, engineConfig);
+        prepareTomcatEngineValves(engineConfig).forEach(valve -> ((StandardEngine)engine).addValve(valve));
+        prepareTomcatConnectors(configuration).forEach(connector -> tomcat.getService().addConnector(connector));
 
-        // set catalina_home
-        System.setProperty(Globals.CATALINA_HOME_PROP, System.getProperty("ofbiz.home") + "/" + this.catalinaRuntimeHome);
-        System.setProperty(Globals.CATALINA_BASE_PROP, System.getProperty(Globals.CATALINA_HOME_PROP));
+        loadWebapps(tomcat, configuration, clusterProps);
+    }
 
-        // create the instance of embedded Tomcat
-        System.setProperty("catalina.useNaming", String.valueOf(useNaming));
-        tomcat = new Tomcat();
-        tomcat.setBaseDir(System.getProperty("ofbiz.home"));
+    public boolean start() throws ContainerException {
+        try {
+            tomcat.start();
+        } catch (LifecycleException e) {
+            throw new ContainerException(e);
+        }
 
-        // configure JNDI in the StandardServer
-        StandardServer server = (StandardServer) tomcat.getServer();
-        if (useNaming) {
-            tomcat.enableNaming();
+        for (Connector con: tomcat.getService().findConnectors()) {
+            Debug.logInfo("Connector " + con.getProtocol() + " @ " + con.getPort() + " - " +
+                (con.getSecure() ? "secure" : "not-secure") + " [" + con.getProtocolHandlerClassName() + "] started.", module);
         }
+        Debug.logInfo("Started " + ServerInfo.getServerInfo(), module);
+        return true;
+    }
+
+    public void stop() throws ContainerException {
         try {
-            server.setGlobalNamingContext(new InitialContext());
-        } catch (NamingException e) {
-            throw new ContainerException(e);
+            tomcat.stop();
+        } catch (LifecycleException e) {
+            /* Don't re-throw this exception or it will kill the rest of the shutdown process.
+             * Happens usually when running tests. Output disabled unless in verbose */
+            Debug.logVerbose(e, module);
         }
+    }
 
-        // create the engine
+    public String getName() {
+        return name;
+    }
+
+    private Property retrieveTomcatEngineConfig(ContainerConfig.Configuration cc) throws ContainerException {
         List<ContainerConfig.Configuration.Property> engineProps = cc.getPropertiesWithValue("engine");
         if (UtilValidate.isEmpty(engineProps)) {
             throw new ContainerException("Cannot load CatalinaContainer; no engines defined.");
@@ -204,53 +153,43 @@ public class CatalinaContainer implement
         if (engineProps.size() > 1) {
             throw new ContainerException("Cannot load CatalinaContainer; more than one engine configuration found; only one is supported.");
         }
-        createEngine(engineProps.get(0));
-
-        // create the connectors
-        List<ContainerConfig.Configuration.Property> connectorProps = cc.getPropertiesWithValue("connector");
-        if (UtilValidate.isEmpty(connectorProps)) {
-            throw new ContainerException("Cannot load CatalinaContainer; no connectors defined!");
-        }
-        for (ContainerConfig.Configuration.Property connectorProp: connectorProps) {
-            createConnector(connectorProp);
-        }
+        return engineProps.get(0);
     }
 
-    public boolean start() throws ContainerException {
-        // load the web applications
-        loadComponents();
+    private Tomcat prepareTomcatServer(ContainerConfig.Configuration cc,
+            ContainerConfig.Configuration.Property engineConfig) throws ContainerException {
 
-        // Start the Tomcat server
-        try {
-            tomcat.getServer().start();
-        } catch (LifecycleException e) {
-            throw new ContainerException(e);
-        }
+        System.setProperty(Globals.CATALINA_HOME_PROP, System.getProperty("ofbiz.home") + "/" +
+                    ContainerConfig.getPropertyValue(cc, "catalina-runtime-home", "runtime/catalina"));
+        System.setProperty(Globals.CATALINA_BASE_PROP, System.getProperty(Globals.CATALINA_HOME_PROP));
 
-        for (Connector con: tomcat.getService().findConnectors()) {
-            Debug.logInfo("Connector " + con.getProtocol() + " @ " + con.getPort() + " - " +
-                (con.getSecure() ? "secure" : "not-secure") + " [" + con.getProtocolHandlerClassName() + "] started.", module);
+        Tomcat tomcat = new Tomcat();
+        tomcat.setBaseDir(System.getProperty("ofbiz.home"));
+
+        Property defaultHostProp = engineConfig.getProperty("default-host");
+        if (defaultHostProp == null) {
+            throw new ContainerException("default-host element of server property is required for catalina!");
+        } else {
+            tomcat.setHostname(defaultHostProp.value);
         }
-        Debug.logInfo("Started " + ServerInfo.getServerInfo(), module);
-        return true;
-    }
 
-    private Engine createEngine(ContainerConfig.Configuration.Property engineConfig) throws ContainerException {
-        if (tomcat == null) {
-            throw new ContainerException("Cannot create Engine without Tomcat instance!");
+        if (ContainerConfig.getPropertyValue(cc, "use-naming", false)) {
+            tomcat.enableNaming();
         }
 
-        ContainerConfig.Configuration.Property defaultHostProp = engineConfig.getProperty("default-host");
-        if (defaultHostProp == null) {
-            throw new ContainerException("default-host element of server property is required for catalina!");
+        StandardServer server = (StandardServer) tomcat.getServer();
+        try {
+            server.setGlobalNamingContext(new InitialContext());
+        } catch (NamingException e) {
+            throw new ContainerException(e);
         }
 
-        String engineName = engineConfig.name;
-        String hostName = defaultHostProp.value;
+        return tomcat;
+    }
 
-        tomcat.setHostname(hostName);
+    private Engine prepareTomcatEngine(Tomcat tomcat, Property engineConfig) {
         Engine engine = tomcat.getEngine();
-        engine.setName(engineName);
+        engine.setName(engineConfig.name);
 
         // set the JVM Route property (JK/JK2)
         String jvmRoute = ContainerConfig.getPropertyValue(engineConfig, "jvm-route", null);
@@ -258,107 +197,125 @@ public class CatalinaContainer implement
             engine.setJvmRoute(jvmRoute);
         }
 
-        // create a default virtual host; others will be created as needed
-        Host host = tomcat.getHost();
-        configureHost(host);
+        return engine;
+    }
 
-        // configure clustering
-        List<ContainerConfig.Configuration.Property> clusterProps = engineConfig.getPropertiesWithValue("cluster");
-        if (clusterProps != null && clusterProps.size() > 1) {
-            throw new ContainerException("Only one cluster configuration allowed per engine");
-        }
+    private Host prepareHost(Tomcat tomcat, List<String> virtualHosts) {
+        Host host;
 
-        if (UtilValidate.isNotEmpty(clusterProps)) {
-            ContainerConfig.Configuration.Property clusterProp = clusterProps.get(0);
-            createCluster(clusterProp, host);
-            clusterConfig.put(engineName, clusterProp);
+        if (UtilValidate.isEmpty(virtualHosts)) {
+            host = tomcat.getHost();
+        } else {
+            host = prepareVirtualHost(tomcat, virtualHosts);
         }
 
-        // configure the CrossSubdomainSessionValve
-        boolean enableSessionValve = ContainerConfig.getPropertyValue(engineConfig, "enable-cross-subdomain-sessions", false);
-        if (enableSessionValve) {
-            CrossSubdomainSessionValve sessionValve = new CrossSubdomainSessionValve();
-            ((StandardEngine)engine).addValve(sessionValve);
-        }
+        host.setAppBase(System.getProperty("ofbiz.home") + "/framework/catalina/hosts");
+        host.setDeployOnStartup(false);
+        host.setBackgroundProcessorDelay(5);
+        host.setAutoDeploy(false);
+        ((StandardHost)host).setWorkDir(new File(System.getProperty(Globals.CATALINA_HOME_PROP),
+                "work" + File.separator + host.getName()).getAbsolutePath());
 
-        // configure the access log valve
-        String logDir = ContainerConfig.getPropertyValue(engineConfig, "access-log-dir", null);
-        AccessLogValve al = null;
-        if (logDir != null) {
-            al = new AccessLogValve();
-            if (!logDir.startsWith("/")) {
-                logDir = System.getProperty("ofbiz.home") + "/" + logDir;
-            }
-            File logFile = new File(logDir);
-            if (!logFile.isDirectory()) {
-                throw new ContainerException("Log directory [" + logDir + "] is not available; make sure the directory is created");
-            }
-            al.setDirectory(logFile.getAbsolutePath());
-        }
+        return host;
+    }
 
-        // configure the SslAcceleratorValve
-        String sslAcceleratorPortStr = ContainerConfig.getPropertyValue(engineConfig, "ssl-accelerator-port", null);
-        if (UtilValidate.isNotEmpty(sslAcceleratorPortStr)) {
-            Integer sslAcceleratorPort = Integer.valueOf(sslAcceleratorPortStr);
-            SslAcceleratorValve sslAcceleratorValve = new SslAcceleratorValve();
-            sslAcceleratorValve.setSslAcceleratorPort(sslAcceleratorPort);
-            ((StandardEngine)engine).addValve(sslAcceleratorValve);
+    private Host prepareVirtualHost(Tomcat tomcat, List<String> virtualHosts) {
+        // assume that the first virtual-host will be the default; additional virtual-hosts will be aliases
+        String hostName = virtualHosts.get(0);
+        Host host;
+        Engine engine = tomcat.getEngine();
+
+        org.apache.catalina.Container childContainer = engine.findChild(hostName);
+        if (childContainer instanceof Host) {
+            host = (Host) childContainer;
+        } else {
+            host = new StandardHost();
+            host.setName(hostName);
+            engine.addChild(host);
         }
 
+        virtualHosts.stream()
+            .filter(virtualHost -> virtualHost != hostName)
+            .forEach(virtualHost -> host.addAlias(virtualHost));
 
-        String alp2 = ContainerConfig.getPropertyValue(engineConfig, "access-log-pattern", null);
-        if (al != null && UtilValidate.isNotEmpty(alp2)) {
-            al.setPattern(alp2);
-        }
+        return host;
+    }
 
-        String alp3 = ContainerConfig.getPropertyValue(engineConfig, "access-log-prefix", null);
-        if (al != null && UtilValidate.isNotEmpty(alp3)) {
-            al.setPrefix(alp3);
+    private Property prepareTomcatClustering(Host host, Property engineConfig) throws ContainerException {
+        Property clusterProp = null;
+
+        List<Property> clusterProps = engineConfig.getPropertiesWithValue("cluster");
+        if (clusterProps != null && clusterProps.size() > 1) {
+            throw new ContainerException("Only one cluster configuration allowed per engine");
         }
 
-        boolean alp5 = ContainerConfig.getPropertyValue(engineConfig, "access-log-rotate", false);
-        if (al != null) {
-            al.setRotatable(alp5);
+        if (UtilValidate.isNotEmpty(clusterProps)) {
+            clusterProp = clusterProps.get(0);
+
+            GroupChannel channel = new GroupChannel();
+            channel.setChannelReceiver(prepareChannelReceiver(clusterProp));
+            channel.setChannelSender(prepareChannelSender(clusterProp));
+            channel.setMembershipService(prepareChannelMcastService(clusterProp));
+
+            SimpleTcpCluster cluster = new SimpleTcpCluster();
+            cluster.setClusterName(clusterProp.name);
+            cluster.setManagerTemplate(prepareClusterManager(clusterProp));
+            cluster.setChannel(channel);
+            cluster.addValve(prepareClusterValve(clusterProp));
+
+            host.setCluster(cluster);
+
+            Debug.logInfo("Catalina Cluster [" + cluster.getClusterName() + "] configured for host - " + host.getName(), module);
         }
+        return clusterProp;
+    }
+
+    private NioReceiver prepareChannelReceiver(Property clusterProp) throws ContainerException {
+        NioReceiver listener = new NioReceiver();
 
-        if (al != null) {
-            ((StandardEngine)engine).addValve(al);
+        String tla = ContainerConfig.getPropertyValue(clusterProp, "tcp-listen-host", "auto");
+        int tlp = ContainerConfig.getPropertyValue(clusterProp, "tcp-listen-port", 4001);
+        int tlt = ContainerConfig.getPropertyValue(clusterProp, "tcp-sector-timeout", 100);
+        int tlc = ContainerConfig.getPropertyValue(clusterProp, "tcp-thread-count", 6);
+
+        if (tlp == -1) {
+            throw new ContainerException("Cluster configuration requires tcp-listen-port property");
         }
 
-        return engine;
-    }
+        listener.setAddress(tla);
+        listener.setPort(tlp);
+        listener.setSelectorTimeout(tlt);
+        listener.setMaxThreads(tlc);
+        listener.setMinThreads(tlc);
 
-    private static Host createHost(String hostName) {
-        Host host = new StandardHost();
-        host.setName(hostName);
-        configureHost(host);
-        return host;
-    }
-    private static void configureHost(Host host) {
-        host.setAppBase(CATALINA_HOSTS_HOME);
-        host.setDeployOnStartup(false);
-        host.setBackgroundProcessorDelay(5);
-        host.setAutoDeploy(false);
-        ((StandardHost)host).setWorkDir(new File(System.getProperty(Globals.CATALINA_HOME_PROP), "work" + File.separator + host.getName()).getAbsolutePath());
+        return listener;
     }
 
-    protected Cluster createCluster(ContainerConfig.Configuration.Property clusterProps, Host host) throws ContainerException {
-        String defaultValveFilter = ".*\\.gif;.*\\.js;.*\\.jpg;.*\\.htm;.*\\.html;.*\\.txt;.*\\.png;.*\\.css;.*\\.ico;.*\\.htc;";
+    private ReplicationTransmitter prepareChannelSender(Property clusterProp) throws ContainerException {
+        ReplicationTransmitter trans = new ReplicationTransmitter();
+        try {
+            MultiPointSender mps = (MultiPointSender)Class.forName(ContainerConfig.getPropertyValue(clusterProp,
+                    "replication-mode", "org.apache.catalina.tribes.transport.bio.PooledMultiSender")).newInstance();
+            trans.setTransport(mps);
+        } catch (Exception exc) {
+            throw new ContainerException("Cluster configuration requires a valid replication-mode property: " + exc.getMessage());
+        }
+        return trans;
+    }
 
-        ReplicationValve clusterValve = new ReplicationValve();
-        clusterValve.setFilter(ContainerConfig.getPropertyValue(clusterProps, "rep-valve-filter", defaultValveFilter));
+    private McastService prepareChannelMcastService(Property clusterProp) throws ContainerException {
+        McastService mcast = new McastService();
 
-        String mcb = ContainerConfig.getPropertyValue(clusterProps, "mcast-bind-addr", null);
-        String mca = ContainerConfig.getPropertyValue(clusterProps, "mcast-addr", null);
-        int mcp = ContainerConfig.getPropertyValue(clusterProps, "mcast-port", -1);
-        int mcf = ContainerConfig.getPropertyValue(clusterProps, "mcast-freq", 500);
-        int mcd = ContainerConfig.getPropertyValue(clusterProps, "mcast-drop-time", 3000);
+        String mcb = ContainerConfig.getPropertyValue(clusterProp, "mcast-bind-addr", null);
+        String mca = ContainerConfig.getPropertyValue(clusterProp, "mcast-addr", null);
+        int mcp = ContainerConfig.getPropertyValue(clusterProp, "mcast-port", -1);
+        int mcf = ContainerConfig.getPropertyValue(clusterProp, "mcast-freq", 500);
+        int mcd = ContainerConfig.getPropertyValue(clusterProp, "mcast-drop-time", 3000);
 
         if (mca == null || mcp == -1) {
             throw new ContainerException("Cluster configuration requires mcast-addr and mcast-port properties");
         }
 
-        McastService mcast = new McastService();
         if (mcb != null) {
             mcast.setMcastBindAddress(mcb);
         }
@@ -368,86 +325,88 @@ public class CatalinaContainer implement
         mcast.setMcastDropTime(mcd);
         mcast.setFrequency(mcf);
 
-        String tla = ContainerConfig.getPropertyValue(clusterProps, "tcp-listen-host", "auto");
-        int tlp = ContainerConfig.getPropertyValue(clusterProps, "tcp-listen-port", 4001);
-        int tlt = ContainerConfig.getPropertyValue(clusterProps, "tcp-sector-timeout", 100);
-        int tlc = ContainerConfig.getPropertyValue(clusterProps, "tcp-thread-count", 6);
-        //String tls = getPropertyValue(clusterProps, "", "");
+        return mcast;
+    }
 
-        if (tlp == -1) {
-            throw new ContainerException("Cluster configuration requires tcp-listen-port property");
+    private ClusterManager prepareClusterManager(Property clusterProp) throws ContainerException {
+        String mgrClassName = ContainerConfig.getPropertyValue(clusterProp, "manager-class", "org.apache.catalina.ha.session.DeltaManager");
+        try {
+            return (ClusterManager)Class.forName(mgrClassName).newInstance();
+        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+            throw new ContainerException("Cluster configuration requires a valid manager-class property", e);
         }
+    }
 
-        NioReceiver listener = new NioReceiver();
-        listener.setAddress(tla);
-        listener.setPort(tlp);
-        listener.setSelectorTimeout(tlt);
-        listener.setMaxThreads(tlc);
-        listener.setMinThreads(tlc);
-        //listener.setIsSenderSynchronized(false);
+    private ReplicationValve prepareClusterValve(Property clusterProp) {
+        ReplicationValve clusterValve = new ReplicationValve();
+        String defaultValveFilter = ".*\\.gif;.*\\.js;.*\\.jpg;.*\\.htm;.*\\.html;.*\\.txt;.*\\.png;.*\\.css;.*\\.ico;.*\\.htc;";
+        clusterValve.setFilter(ContainerConfig.getPropertyValue(clusterProp, "rep-valve-filter", defaultValveFilter));
+        return clusterValve;
+    }
 
-        ReplicationTransmitter trans = new ReplicationTransmitter();
-        try {
-            MultiPointSender mps = (MultiPointSender)Class.forName(ContainerConfig.getPropertyValue(clusterProps, "replication-mode", "org.apache.catalina.tribes.transport.bio.PooledMultiSender")).newInstance();
-            trans.setTransport(mps);
-        } catch (Exception exc) {
-            throw new ContainerException("Cluster configuration requires a valid replication-mode property: " + exc.getMessage());
+    private List<Valve> prepareTomcatEngineValves(Property engineConfig) throws ContainerException {
+        List<Valve> engineValves = new ArrayList<Valve>();
+
+        // configure the CrossSubdomainSessionValve
+        if (ContainerConfig.getPropertyValue(engineConfig, "enable-cross-subdomain-sessions", false)) {
+            engineValves.add(new CrossSubdomainSessionValve());
         }
-        String mgrClassName = ContainerConfig.getPropertyValue(clusterProps, "manager-class", "org.apache.catalina.ha.session.DeltaManager");
-        //int debug = ContainerConfig.getPropertyValue(clusterProps, "debug", 0);
-        // removed since 5.5.9? boolean expireSession = ContainerConfig.getPropertyValue(clusterProps, "expire-session", false);
-        // removed since 5.5.9? boolean useDirty = ContainerConfig.getPropertyValue(clusterProps, "use-dirty", true);
-
-        SimpleTcpCluster cluster = new SimpleTcpCluster();
-        cluster.setClusterName(clusterProps.name);
-        Manager manager = null;
-        try {
-            manager = (Manager)Class.forName(mgrClassName).newInstance();
-        } catch (Exception exc) {
-            throw new ContainerException("Cluster configuration requires a valid manager-class property: " + exc.getMessage());
+
+        // configure the SslAcceleratorValve
+        String sslAcceleratorPortStr = ContainerConfig.getPropertyValue(engineConfig, "ssl-accelerator-port", null);
+        if (UtilValidate.isNotEmpty(sslAcceleratorPortStr)) {
+            Integer sslAcceleratorPort = Integer.valueOf(sslAcceleratorPortStr);
+            SslAcceleratorValve sslAcceleratorValve = new SslAcceleratorValve();
+            sslAcceleratorValve.setSslAcceleratorPort(sslAcceleratorPort);
+            engineValves.add(sslAcceleratorValve);
         }
-        //cluster.setManagerClassName(mgrClassName);
-        //host.setManager(manager);
-        //cluster.registerManager(manager);
-        cluster.setManagerTemplate((org.apache.catalina.ha.ClusterManager)manager);
-        //cluster.setDebug(debug);
-        // removed since 5.5.9? cluster.setExpireSessionsOnShutdown(expireSession);
-        // removed since 5.5.9? cluster.setUseDirtyFlag(useDirty);
-
-        GroupChannel channel = new GroupChannel();
-        channel.setChannelReceiver(listener);
-        channel.setChannelSender(trans);
-        channel.setMembershipService(mcast);
-
-        cluster.setChannel(channel);
-        cluster.addValve(clusterValve);
-        // removed since 5.5.9? cluster.setPrintToScreen(true);
-
-        // set the cluster to the host
-        host.setCluster(cluster);
-        Debug.logInfo("Catalina Cluster [" + cluster.getClusterName() + "] configured for host - " + host.getName(), module);
-
-        return cluster;
-    }
-
-    protected Connector createConnector(ContainerConfig.Configuration.Property connectorProp) throws ContainerException {
-        if (tomcat == null) {
-            throw new ContainerException("Cannot create Connector without Tomcat instance!");
-        }
-        Connector connector = null;
-        if (UtilValidate.isNotEmpty(connectorProp.properties)) {
-            String protocol = ContainerConfig.getPropertyValue(connectorProp, "protocol", "HTTP/1.1");
-            int port = ContainerConfig.getPropertyValue(connectorProp, "port", 0) + Start.getInstance().getConfig().portOffset;
-
-            // set the protocol and the port first
-            connector = new Connector(protocol);
-            connector.setPort(port);
-            // then set all the other parameters
-            for (ContainerConfig.Configuration.Property prop: connectorProp.properties.values()) {
-                if ("protocol".equals(prop.name) || "port".equals(prop.name)) {
-                    // protocol and port are already set
-                    continue;
-                }
+
+        // configure the AccessLogValve
+        String logDir = ContainerConfig.getPropertyValue(engineConfig, "access-log-dir", null);
+        if (logDir != null) {
+            AccessLogValve accessLogValve = new AccessLogValve();
+
+            logDir = logDir.startsWith("/") ? System.getProperty("ofbiz.home") + "/" + logDir : logDir;
+            File logFile = new File(logDir);
+            if (!logFile.isDirectory()) {
+                throw new ContainerException("Log directory [" + logDir + "] is not available; make sure the directory is created");
+            }
+
+            accessLogValve.setDirectory(logFile.getAbsolutePath());
+            String accessLogPattern = ContainerConfig.getPropertyValue(engineConfig, "access-log-pattern", null);
+            if (UtilValidate.isNotEmpty(accessLogPattern)) {
+                accessLogValve.setPattern(accessLogPattern);
+            }
+            String accessLogPrefix = ContainerConfig.getPropertyValue(engineConfig, "access-log-prefix", null);
+            if (UtilValidate.isNotEmpty(accessLogPrefix)) {
+                accessLogValve.setPrefix(accessLogPrefix);
+            }
+            accessLogValve.setRotatable(ContainerConfig.getPropertyValue(engineConfig, "access-log-rotate", false));
+
+            engineValves.add(accessLogValve);
+        }
+
+        return engineValves;
+    }
+
+    private List<Connector> prepareTomcatConnectors(Configuration configuration) throws ContainerException {
+        List<Property> connectorProps = configuration.getPropertiesWithValue("connector");
+        if (UtilValidate.isEmpty(connectorProps)) {
+            throw new ContainerException("Cannot load CatalinaContainer; no connectors defined!");
+        }
+        return connectorProps.stream()
+            .filter(connectorProp -> UtilValidate.isNotEmpty(connectorProp.properties))
+            .map(connectorProp -> prepareConnector(connectorProp))
+            .collect(Collectors.toList());
+    }
+
+    private Connector prepareConnector(Property connectorProp) {
+        Connector connector = new Connector(ContainerConfig.getPropertyValue(connectorProp, "protocol", "HTTP/1.1"));
+        connector.setPort(ContainerConfig.getPropertyValue(connectorProp, "port", 0) + Start.getInstance().getConfig().portOffset);
+
+        connectorProp.properties.values().stream()
+            .filter(prop -> !"protocol".equals(prop.name) && !"port".equals(prop.name))
+            .forEach(prop -> {
                 if (IntrospectionUtils.setProperty(connector, prop.name, prop.value)) {
                     if (prop.name.indexOf("Pass") != -1) {
                         // this property may be a password, do not include its value in the logs
@@ -458,106 +417,99 @@ public class CatalinaContainer implement
                 } else {
                     Debug.logWarning("Tomcat " + connector + ": ignored parameter " + prop.name, module);
                 }
-            }
-
-            tomcat.getService().addConnector(connector);
-        }
+            });
         return connector;
     }
 
-    private Callable<Context> createContext(final ComponentConfig.WebappInfo appInfo) throws ContainerException {
-        Debug.logInfo("Creating context [" + appInfo.name + "]", module);
-        final Engine engine = tomcat.getEngine();
+    private void loadWebapps(Tomcat tomcat, ContainerConfig.Configuration configuration, Property clusterProp) {
+        ScheduledExecutorService executor = ExecutionPool.getScheduledExecutor(new ThreadGroup(module),
+                "catalina-startup", Runtime.getRuntime().availableProcessors(), 0, true);
+        List<Future<Context>> futures = new ArrayList<Future<Context>>();
 
-        List<String> virtualHosts = appInfo.getVirtualHosts();
-        final Host host;
-        if (UtilValidate.isEmpty(virtualHosts)) {
-            host = tomcat.getHost();
-        } else {
-            // assume that the first virtual-host will be the default; additional virtual-hosts will be aliases
-            Iterator<String> vhi = virtualHosts.iterator();
-            String hostName = vhi.next();
-
-            org.apache.catalina.Container childContainer = engine.findChild(hostName);
-            if (childContainer instanceof Host) {
-                host = (Host)childContainer;
+        List<ComponentConfig.WebappInfo> webResourceInfos = ComponentConfig.getAllWebappResourceInfos();
+        Collections.reverse(webResourceInfos); // allow higher level webapps to override lower ones
+
+        Set<String> webappsMounts = new HashSet<String>();
+        webResourceInfos.forEach(appInfo -> webappsMounts.addAll(getWebappMounts(appInfo)));
+
+        for (ComponentConfig.WebappInfo appInfo: webResourceInfos) {
+            if(webappsMounts.removeAll(getWebappMounts(appInfo))) {
+                // webapp is not yet loaded
+                if (!appInfo.location.isEmpty()) {
+                    futures.add(executor.submit(createCallableContext(tomcat, appInfo, clusterProp, configuration)));
+                }
             } else {
-                host = createHost(hostName);
-                engine.addChild(host);
-            }
-            while (vhi.hasNext()) {
-                host.addAlias(vhi.next());
+                /* webapp is loaded already (overridden). Therefore, disable
+                 * app bar display on overridden apps and do not load */
+                appInfo.setAppBarDisplay(false);
+                Debug.logInfo("Duplicate webapp mount; not loading : " + appInfo.getName() + " / " + appInfo.getLocation(), module);
             }
         }
+        ExecutionPool.getAllFutures(futures);
+        executor.shutdown();
+    }
+
+    private List<String> getWebappMounts(ComponentConfig.WebappInfo webappInfo) {
+        List<String> allAppsMounts = new ArrayList<String>();
+        String engineName = webappInfo.server;
+        String mount = webappInfo.getContextRoot();
+        List<String> virtualHosts = webappInfo.getVirtualHosts();
+        if (virtualHosts.isEmpty()) {
+            allAppsMounts.add(engineName + ":DEFAULT:" + mount);
+        } else {
+            virtualHosts.forEach(virtualHost -> allAppsMounts.add(engineName + ":" + virtualHost + ":" + mount));
+        }
+        return allAppsMounts;
+    }
+
+    private Callable<Context> createCallableContext(Tomcat tomcat, ComponentConfig.WebappInfo appInfo,
+            Property clusterProp, ContainerConfig.Configuration configuration) {
+
+        Debug.logInfo("Creating context [" + appInfo.name + "]", module);
+        Host host = prepareHost(tomcat, appInfo.getVirtualHosts());
+
         return new Callable<Context>() {
             public Context call() throws ContainerException, LifecycleException {
-                StandardContext context = configureContext(engine, host, appInfo);
+                StandardContext context = prepareContext(host, configuration, appInfo, clusterProp);
                 host.addChild(context);
                 return context;
             }
         };
     }
 
-    private StandardContext configureContext(Engine engine, Host host, ComponentConfig.WebappInfo appInfo) throws ContainerException {
-        // webapp settings
-        Map<String, String> initParameters = appInfo.getInitParameters();
+    private StandardContext prepareContext(Host host, ContainerConfig.Configuration configuration,
+            ComponentConfig.WebappInfo appInfo, Property clusterProp) throws ContainerException {
 
-        // set the root location (make sure we set the paths correctly)
-        String location = appInfo.componentConfig.getRootLocation() + appInfo.location;
-        location = location.replace('\\', '/');
-        if (location.endsWith("/")) {
-            location = location.substring(0, location.length() - 1);
-        }
-
-        // get the mount point
-        String mount = appInfo.mountPoint;
-        if (mount.endsWith("/*")) {
-            mount = mount.substring(0, mount.length() - 2);
-        }
+        StandardContext context = new StandardContext();
+        Tomcat.initWebappDefaults(context);
 
-        final String webXmlFilePath = new StringBuilder().append("file:///").append(location).append("/WEB-INF/web.xml").toString();
-        boolean appIsDistributable = distribute;
-        URL webXmlUrl = null;
-        try {
-            webXmlUrl = FlexibleLocation.resolveLocation(webXmlFilePath);
-        } catch (MalformedURLException e) {
-            throw new ContainerException(e);
-        }
-        File webXmlFile = new File(webXmlUrl.getFile());
-        if (webXmlFile.exists()) {
-            Document webXmlDoc = null;
-            try {
-                webXmlDoc = UtilXml.readXmlDocument(webXmlUrl);
-            } catch (Exception e) {
-                throw new ContainerException(e);
-            }
-            appIsDistributable = webXmlDoc.getElementsByTagName("distributable").getLength() > 0;
-        } else {
-            Debug.logInfo(webXmlFilePath + " not found.", module);
-        }
-        final boolean contextIsDistributable = distribute && appIsDistributable;
+        String location = getWebappRootLocation(appInfo);
+        boolean contextIsDistributable = isContextDistributable(configuration, location);
 
-        // create the web application context
-        StandardContext context = new StandardContext();
         context.setParent(host);
         context.setDocBase(location);
-        context.setPath(mount);
+        context.setPath(getWebappMountPoint(appInfo));
         context.addLifecycleListener(new ContextConfig());
-        Tomcat.initWebappDefaults(context);
-        // configure persistent sessions
-        // important: the call to context.setManager(...) must be done after Tomcat.initWebappDefaults(...)
-        Property clusterProp = clusterConfig.get(engine.getName());
+        context.setJ2EEApplication("OFBiz");
+        context.setJ2EEServer("OFBiz Container");
+        context.setLoader(new WebappLoader(Thread.currentThread().getContextClassLoader()));
+        context.setDisplayName(appInfo.name);
+        context.setDocBase(location);
+        context.setReloadable(ContainerConfig.getPropertyValue(configuration, "apps-context-reloadable", false));
+        context.setDistributable(contextIsDistributable);
+        context.setCrossContext(ContainerConfig.getPropertyValue(configuration, "apps-cross-context", true));
+        context.setPrivileged(appInfo.privileged);
+        context.getServletContext().setAttribute("_serverId", appInfo.server);
+        context.getServletContext().setAttribute("componentName", appInfo.componentConfig.getComponentName());
+
         if (clusterProp != null && contextIsDistributable) {
-            Manager sessionMgr = null;
-            String mgrClassName = ContainerConfig.getPropertyValue(clusterProp, "manager-class", "org.apache.catalina.ha.session.DeltaManager");
-            try {
-                sessionMgr = (Manager)Class.forName(mgrClassName).newInstance();
-            } catch (Exception exc) {
-                throw new ContainerException("Cluster configuration requires a valid manager-class property: " + exc.getMessage());
-            }
-            context.setManager(sessionMgr);
+            context.setManager(prepareClusterManager(clusterProp));
         }
 
+        StandardRoot resources = new StandardRoot(context);
+        resources.setAllowLinking(true);
+        context.setResources(resources);
+
         JarScanner jarScanner = context.getJarScanner();
         if (jarScanner instanceof StandardJarScanner) {
             StandardJarScanner standardJarScanner = (StandardJarScanner) jarScanner;
@@ -565,33 +517,14 @@ public class CatalinaContainer implement
             standardJarScanner.setScanClassPath(true);
         }
 
-        context.setJ2EEApplication(J2EE_APP);
-        context.setJ2EEServer(J2EE_SERVER);
-        context.setLoader(new WebappLoader(Thread.currentThread().getContextClassLoader()));
-
-        context.setDisplayName(appInfo.name);
-        context.setDocBase(location);
-        
-        StandardRoot resources = new StandardRoot(context);
-        resources.setAllowLinking(true);
-        context.setResources(resources);
-
-        context.setReloadable(contextReloadable);
-
-        context.setDistributable(contextIsDistributable);
-
-        context.setCrossContext(crossContext);
-        context.setPrivileged(appInfo.privileged);
-        context.getServletContext().setAttribute("_serverId", appInfo.server);
-        context.getServletContext().setAttribute("componentName", appInfo.componentConfig.getComponentName());
-
+        Map<String, String> initParameters = appInfo.getInitParameters();
         // request dumper filter
-        String enableRequestDump = initParameters.get("enableRequestDump");
-        if ("true".equals(enableRequestDump)) {
-            // create the Requester Dumper Filter instance
+        if ("true".equals(initParameters.get("enableRequestDump"))) {
             FilterDef requestDumperFilterDef = new FilterDef();
             requestDumperFilterDef.setFilterClass(RequestDumperFilter.class.getName());
             requestDumperFilterDef.setFilterName("RequestDumper");
+            context.addFilterDef(requestDumperFilterDef);
+
             FilterMap requestDumperFilterMap = new FilterMap();
             requestDumperFilterMap.setFilterName("RequestDumper");
             requestDumperFilterMap.addURLPattern("*");
@@ -599,71 +532,43 @@ public class CatalinaContainer implement
         }
 
         // set the init parameters
-        for (Map.Entry<String, String> entry: initParameters.entrySet()) {
-            context.addParameter(entry.getKey(), entry.getValue());
-        }
+        initParameters.entrySet().forEach(entry -> context.addParameter(entry.getKey(), entry.getValue()));
 
         return context;
     }
 
-    protected void loadComponents() throws ContainerException {
-        if (tomcat == null) {
-            throw new ContainerException("Cannot load web applications without Tomcat instance!");
-        }
-
-        // load the applications
-        List<ComponentConfig.WebappInfo> webResourceInfos = ComponentConfig.getAllWebappResourceInfos();
-        List<String> loadedMounts = new ArrayList<String>();
-        if (webResourceInfos == null) {
-            return;
+    private String getWebappRootLocation(ComponentConfig.WebappInfo appInfo) {
+        String location = appInfo.componentConfig.getRootLocation() + appInfo.location;
+        location = location.replace('\\', '/');
+        if (location.endsWith("/")) {
+            location = location.substring(0, location.length() - 1);
         }
+        return location;
+    }
 
-        ScheduledExecutorService executor = ExecutionPool.getScheduledExecutor(CATALINA_THREAD_GROUP, "catalina-startup", Runtime.getRuntime().availableProcessors(), 0, true);
-        try {
-            List<Future<Context>> futures = new ArrayList<Future<Context>>();
-
-            for (int i = webResourceInfos.size(); i > 0; i--) {
-                ComponentConfig.WebappInfo appInfo = webResourceInfos.get(i - 1);
-                String engineName = appInfo.server;
-                List<String> virtualHosts = appInfo.getVirtualHosts();
-                String mount = appInfo.getContextRoot();
-                List<String> keys = new ArrayList<String>();
-                if (virtualHosts.isEmpty()) {
-                    keys.add(engineName + ":DEFAULT:" + mount);
-                } else {
-                    for (String virtualHost: virtualHosts) {
-                        keys.add(engineName + ":" + virtualHost + ":" + mount);
-                    }
-                }
-                if (!keys.removeAll(loadedMounts)) {
-                    // nothing was removed from the new list of keys; this
-                    // means there are no existing loaded entries that overlap
-                    // with the new set
-                    if (!appInfo.location.isEmpty()) {
-                        futures.add(executor.submit(createContext(appInfo)));
-                    }
-                    loadedMounts.addAll(keys);
-                } else {
-                    appInfo.setAppBarDisplay(false); // disable app bar display on overridden apps
-                    Debug.logInfo("Duplicate webapp mount; not loading : " + appInfo.getName() + " / " + appInfo.getLocation(), module);
-                }
-            }
-            ExecutionPool.getAllFutures(futures);
-        } finally {
-            executor.shutdown();
+    private String getWebappMountPoint(ComponentConfig.WebappInfo appInfo) {
+        String mount = appInfo.mountPoint;
+        if (mount.endsWith("/*")) {
+            mount = mount.substring(0, mount.length() - 2);
         }
+        return mount;
     }
 
-    public void stop() throws ContainerException {
+    private boolean isContextDistributable(ContainerConfig.Configuration configuration, String location) throws ContainerException {
+        String webXmlFilePath = new StringBuilder().append("file:///").append(location).append("/WEB-INF/web.xml").toString();
+        boolean appIsDistributable = ContainerConfig.getPropertyValue(configuration, "apps-distributable", true);
         try {
-            tomcat.stop();
-        } catch (LifecycleException e) {
-            // don't throw this; or it will kill the rest of the shutdown process
-            Debug.logVerbose(e, module); // happens usually when running tests, disabled unless in verbose
+            URL webXmlUrl = FlexibleLocation.resolveLocation(webXmlFilePath);
+            File webXmlFile = new File(webXmlUrl.getFile());
+            if (webXmlFile.exists()) {
+                Document webXmlDoc = UtilXml.readXmlDocument(webXmlUrl);
+                return appIsDistributable && webXmlDoc.getElementsByTagName("distributable").getLength() > 0;
+            } else {
+                Debug.logInfo(webXmlFilePath + " not found.", module);
+                return appIsDistributable;
+            }
+        } catch (SAXException | ParserConfigurationException | IOException e) {
+            throw new ContainerException(e);
         }
     }
-
-    public String getName() {
-        return name;
-    }
 }