You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwebbeans.apache.org by rm...@apache.org on 2016/11/13 09:34:10 UTC

svn commit: r1769479 [2/16] - in /openwebbeans/microwave/trunk: ./ meecrowave-arquillian/ meecrowave-arquillian/src/ meecrowave-arquillian/src/main/ meecrowave-arquillian/src/main/java/ meecrowave-arquillian/src/main/java/org/ meecrowave-arquillian/src...

Added: openwebbeans/microwave/trunk/meecrowave-core/pom.xml
URL: http://svn.apache.org/viewvc/openwebbeans/microwave/trunk/meecrowave-core/pom.xml?rev=1769479&view=auto
==============================================================================
--- openwebbeans/microwave/trunk/meecrowave-core/pom.xml (added)
+++ openwebbeans/microwave/trunk/meecrowave-core/pom.xml Sun Nov 13 09:34:07 2016
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="
+            http://maven.apache.org/POM/4.0.0
+            http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>meecrowave</artifactId>
+    <groupId>org.apache.meecrowave</groupId>
+    <version>0.0.1-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>meecrowave-core</artifactId>
+  <name>Meecrowave :: Core</name>
+
+  <properties>
+    <tomcat.version>9.0.0.M13</tomcat.version>
+    <openwebbeans.version>1.7.1-SNAPSHOT</openwebbeans.version>
+    <cxf.version>3.1.9-SNAPSHOT</cxf.version>
+    <johnzon.version>0.9.5</johnzon.version>
+    <log4j2.version>2.7</log4j2.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-annotation_1.2_spec</artifactId>
+      <version>1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jcdi_1.1_spec</artifactId>
+      <version>1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-atinject_1.0_spec</artifactId>
+      <version>1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-interceptor_1.2_spec</artifactId>
+      <version>1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-json_1.0_spec</artifactId>
+      <version>1.0-alpha-1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jaxrs_2.0_spec</artifactId>
+      <version>1.0-alpha-1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>jsonb-api</artifactId>
+      <version>${johnzon.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-jaspic-api</artifactId>
+      <!--<version>${tomcat.version}</version>-->
+      <version>9.0-SNAPSHOT</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-cli</groupId>
+      <artifactId>commons-cli</artifactId>
+      <version>1.2</version>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <version>3.5</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.xbean</groupId>
+      <artifactId>xbean-reflect</artifactId>
+      <version>4.5</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.openwebbeans</groupId>
+      <artifactId>openwebbeans-impl</artifactId>
+      <version>${openwebbeans.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.openwebbeans</groupId>
+      <artifactId>openwebbeans-web</artifactId>
+      <version>${openwebbeans.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-catalina</artifactId>
+      <version>${tomcat.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.tomcat</groupId>
+          <artifactId>tomcat-annotations-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.tomcat</groupId>
+          <artifactId>tomcat-jsp-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.tomcat</groupId>
+          <artifactId>tomcat-el-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+      <version>${cxf.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.ws.rs</groupId>
+          <artifactId>javax.ws.rs-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>javax.annotation</groupId>
+          <artifactId>javax.annotation-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.codehaus.woodstox</groupId>
+          <artifactId>woodstox-core-asl</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.ws.xmlschema</groupId>
+          <artifactId>xmlschema-core</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-integration-cdi</artifactId>
+      <version>${cxf.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.enterprise</groupId>
+          <artifactId>cdi-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-client</artifactId>
+      <version>${cxf.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-jaxrs</artifactId>
+      <version>${johnzon.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-jsonb</artifactId>
+      <version>${johnzon.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+      <version>${log4j2.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+      <version>${log4j2.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-jul</artifactId>
+      <version>${log4j2.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>${junit.version}</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <version>2.10</version>
+        <executions>
+          <execution>
+            <id>copy</id>
+            <phase>process-test-classes</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.apache.deltaspike.core</groupId>
+                  <artifactId>deltaspike-core-api</artifactId>
+                  <version>1.7.1</version>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${project.build.directory}/shared-test</outputDirectory>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.deltaspike.core</groupId>
+                  <artifactId>deltaspike-core-impl</artifactId>
+                  <version>1.7.1</version>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${project.build.directory}/shared-test</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.5.1</version>
+        <executions>
+          <execution>
+            <id>log4j-plugin-processor</id>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+            <phase>process-classes</phase>
+            <configuration>
+              <proc>only</proc>
+              <annotationProcessors>
+                <annotationProcessor>org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor</annotationProcessor>
+              </annotationProcessors>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>2.4.3</version>
+        <executions>
+          <execution>
+            <id>bundle</id>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <shadedClassifierName>runner</shadedClassifierName>
+              <shadedArtifactAttached>true</shadedArtifactAttached>
+              <dependencyReducedPomLocation>${project.build.directory}/reduced-pom-bundle.xml</dependencyReducedPomLocation>
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <mainClass>org.apache.meecrowave.runner.Cli</mainClass>
+                </transformer>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+                  <resource>META-INF/cxf/bus-extensions.txt</resource>
+                </transformer>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+                  <resource>log4j2.component.properties</resource>
+                </transformer>
+                <transformer implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer" />
+                <transformer implementation="org.apache.openwebbeans.maven.shade.OpenWebBeansPropertiesTransformer" />
+              </transformers>
+              <filters>
+                <filter>
+                  <artifact>*:*</artifact>
+                  <excludes>
+                    <exclude>META-INF/*.SF</exclude>
+                    <exclude>META-INF/*.DSA</exclude>
+                    <exclude>META-INF/*.RSA</exclude>
+                    <exclude>META-INF/LICENSE.txt</exclude>
+                    <exclude>META-INF/LICENSE</exclude>
+                    <exclude>META-INF/NOTICE.txt</exclude>
+                    <exclude>META-INF/NOTICE</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+              <relocations>
+                <relocation>
+                  <pattern>org.apache.commons</pattern>
+                  <shadedPattern>org.apache.meecrowave.shaded.commons</shadedPattern>
+                </relocation>
+              </relocations>
+            </configuration>
+          </execution>
+        </executions>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.openwebbeans</groupId>
+            <artifactId>openwebbeans-maven</artifactId>
+            <version>1.7.0</version>
+          </dependency>
+          <dependency>
+            <groupId>com.github.edwgiz</groupId>
+            <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>
+            <version>2.1</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
+</project>

Added: openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/catalina/startup/MeecrowaveContextConfig.java
URL: http://svn.apache.org/viewvc/openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/catalina/startup/MeecrowaveContextConfig.java?rev=1769479&view=auto
==============================================================================
--- openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/catalina/startup/MeecrowaveContextConfig.java (added)
+++ openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/catalina/startup/MeecrowaveContextConfig.java Sun Nov 13 09:34:07 2016
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.catalina.startup;
+
+import org.apache.catalina.WebResource;
+import org.apache.meecrowave.Meecrowave;
+import org.apache.meecrowave.logging.tomcat.LogFacade;
+import org.apache.meecrowave.openwebbeans.OWBTomcatWebScannerService;
+import org.apache.tomcat.util.descriptor.web.WebXml;
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.corespi.scanner.xbean.CdiArchive;
+import org.apache.webbeans.corespi.scanner.xbean.OwbAnnotationFinder;
+import org.xml.sax.InputSource;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.annotation.HandlesTypes;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.annotation.WebListener;
+import javax.servlet.annotation.WebServlet;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static java.util.Optional.ofNullable;
+
+public class MeecrowaveContextConfig extends ContextConfig {
+    private static final byte[] DEFAULT_WEB_XML = "<web-app version=\"3.1\" />".getBytes(StandardCharsets.UTF_8);
+
+    private final Meecrowave.Builder configuration;
+    private final Map<String, Collection<Class<?>>> webClasses = new HashMap<>();
+    private OwbAnnotationFinder finder;
+
+    public MeecrowaveContextConfig(final Meecrowave.Builder configuration) {
+        this.configuration = configuration;
+    }
+
+    @Override
+    protected void webConfig() {
+        if (!configuration.isTomcatScanning()) {
+            super.webConfig();
+            return;
+        }
+
+        // eagerly start CDI to scan only once and not twice (tomcat+CDI)
+        final ClassLoader loader = context.getLoader().getClassLoader(); // should already be started at that point
+        final Thread thread = Thread.currentThread();
+        final ClassLoader old = thread.getContextClassLoader();
+        thread.setContextClassLoader(loader);
+        try {
+            final OWBTomcatWebScannerService scannerService = OWBTomcatWebScannerService.class.cast(WebBeansContext.getInstance().getScannerService());
+            ofNullable(context.getJarScanner()).ifPresent(s -> scannerService.setFilter(s.getJarScanFilter()));
+            scannerService.setDocBase(context.getDocBase());
+            scannerService.setShared(configuration.getSharedLibraries());
+            scannerService.scan();
+            finder = scannerService.getFinder();
+            finder.link();
+            final CdiArchive archive = CdiArchive.class.cast(finder.getArchive());
+            Stream.of(WebServlet.class, WebFilter.class, WebListener.class)
+                    .forEach(marker -> finder.findAnnotatedClasses(marker).stream()
+                            .filter(c -> !Modifier.isAbstract(c.getModifiers()) && Modifier.isPublic(c.getModifiers()))
+                            .forEach(webComponent -> webClasses.computeIfAbsent(
+                                    archive.classesByUrl().entrySet().stream()
+                                            .filter(e -> e.getValue().getClassNames().contains(webComponent.getName()))
+                                            .findFirst().get().getKey(), k -> new HashSet<>())
+                                    .add(webComponent)));
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+        try {
+            super.webConfig();
+        } finally {
+            webClasses.clear();
+            finder = null;
+        }
+    }
+
+    @Override  // just to avoid an info log pretty useless for us
+    protected InputSource getGlobalWebXmlSource() {
+        return ofNullable(super.getGlobalWebXmlSource()).orElse(new InputSource(new ByteArrayInputStream(DEFAULT_WEB_XML)));
+    }
+
+    @Override
+    protected void processAnnotationsWebResource(final WebResource webResource, final WebXml fragment,
+                                                 final boolean handlesTypesOnly,
+                                                 final Map<String, JavaClassCacheEntry> javaClassCache) {
+        if (configuration.isTomcatScanning()) {
+            webClasses.keySet().stream().filter(k -> k.endsWith("/WEB-INF/classes"))
+                    .forEach(k -> processClasses(fragment, handlesTypesOnly, javaClassCache, k));
+        }
+    }
+
+    @Override
+    protected void processAnnotationsUrl(final URL url, final WebXml fragment, final boolean handlesTypesOnly,
+                                         final Map<String, JavaClassCacheEntry> javaClassCache) { // use our finder
+        if (!configuration.isTomcatScanning()) {
+            return;
+        }
+        processClasses(fragment, handlesTypesOnly, javaClassCache, url.toExternalForm());
+    }
+
+    @Override
+    protected void processServletContainerInitializers() { // use our finder
+        if (!configuration.isTomcatScanning()) {
+            return;
+        }
+
+        try {
+            new WebappServiceLoader<ServletContainerInitializer>(context).load(ServletContainerInitializer.class).forEach(sci -> {
+                final Set<Class<?>> classes = new HashSet<>();
+                initializerClassMap.put(sci, classes);
+
+                final HandlesTypes ht;
+                try {
+                    ht = sci.getClass().getAnnotation(HandlesTypes.class);
+                } catch (final Exception | NoClassDefFoundError e) {
+                    return;
+                }
+                if (ht == null) {
+                    return;
+                }
+                Stream.of(ht.value()).forEach(t -> {
+                    if (t.isAnnotation()) {
+                        final Class<? extends Annotation> annotation = Class.class.cast(t);
+                        finder.findAnnotatedClasses(annotation).forEach(classes::add);
+                    } else if (t.isInterface()) {
+                        finder.findImplementations(t).forEach(classes::add);
+                    } else {
+                        finder.findSubclasses(t).forEach(classes::add);
+                    }
+                });
+            });
+        } catch (final IOException e) {
+            ok = false;
+        }
+    }
+
+    private void processClasses(final WebXml fragment, final boolean handlesTypesOnly,
+                                final Map<String, JavaClassCacheEntry> javaClassCache, final String key) {
+        final Collection<Class<?>> classes = webClasses.remove(key);
+        if (classes != null && !classes.isEmpty()) {
+            final ClassLoader loader = context.getLoader().getClassLoader();
+            classes.forEach(c -> {
+                try (final InputStream stream = loader.getResourceAsStream(c.getName().replace('.', '/') + ".class")) {
+                    super.processAnnotationsStream(stream, fragment, handlesTypesOnly, javaClassCache);
+                } catch (final IOException e) {
+                    new LogFacade(MeecrowaveContextConfig.class.getName()).error("Can't parse " + c);
+                }
+            });
+        }
+    }
+}

Added: openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java
URL: http://svn.apache.org/viewvc/openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java?rev=1769479&view=auto
==============================================================================
--- openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java (added)
+++ openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java Sun Nov 13 09:34:07 2016
@@ -0,0 +1,1693 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.meecrowave;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.startup.Catalina;
+import org.apache.catalina.startup.MeecrowaveContextConfig;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.commons.lang3.text.StrLookup;
+import org.apache.commons.lang3.text.StrSubstitutor;
+import org.apache.coyote.http2.Http2Protocol;
+import org.apache.meecrowave.cxf.CxfCdiAutoSetup;
+import org.apache.meecrowave.io.IO;
+import org.apache.meecrowave.logging.jul.Log4j2Logger;
+import org.apache.meecrowave.logging.openwebbeans.Log4j2LoggerFactory;
+import org.apache.meecrowave.logging.tomcat.Log4j2Log;
+import org.apache.meecrowave.logging.tomcat.LogFacade;
+import org.apache.meecrowave.openwebbeans.OWBAutoSetup;
+import org.apache.meecrowave.runner.cli.CliOption;
+import org.apache.meecrowave.tomcat.CDIInstanceManager;
+import org.apache.meecrowave.tomcat.OWBJarScanner;
+import org.apache.meecrowave.tomcat.ProvidedLoader;
+import org.apache.meecrowave.tomcat.TomcatAutoInitializer;
+import org.apache.tomcat.JarScanFilter;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
+import org.apache.tomcat.util.net.SSLHostConfig;
+import org.apache.xbean.finder.ResourceFinder;
+import org.apache.xbean.recipe.ObjectRecipe;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
+
+public class Meecrowave implements AutoCloseable {
+    private final Builder configuration;
+    protected File base;
+    protected final File ownedTempDir;
+    protected InternalTomcat tomcat;
+    protected volatile Thread hook;
+
+    // we can undeploy webapps with that later
+    private final Map<String, Runnable> contexts = new HashMap<>();
+    private Runnable postTask;
+    private boolean clearCatalinaSystemProperties;
+
+    public Meecrowave() {
+        this(new Builder());
+    }
+
+    public Meecrowave(final Builder builder) {
+        this.configuration = builder;
+        this.ownedTempDir = new File(configuration.tempDir, "meecrowave_" + System.nanoTime());
+    }
+
+    public Builder getConfiguration() {
+        return configuration;
+    }
+
+    public File getBase() {
+        return base;
+    }
+
+    public Tomcat getTomcat() {
+        return tomcat;
+    }
+
+    public void undeploy(final String root) {
+        ofNullable(this.contexts.remove(root)).ifPresent(Runnable::run);
+    }
+
+    public Meecrowave deployClasspath(final DeploymentMeta meta) {
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        final ClassLoader parentLoader = tomcat.getServer().getParentClassLoader();
+        if (parentLoader.getParent() == classLoader) {
+            classLoader = parentLoader;
+        }
+
+        final ProvidedLoader loader = new ProvidedLoader(classLoader, configuration.isTomcatWrapLoader());
+        final Consumer<Context> builtInCustomizer = c -> c.setLoader(loader);
+        return deployWebapp(new DeploymentMeta(meta.context, meta.docBase, ofNullable(meta.consumer).map(c -> (Consumer<Context>) ctx -> {
+            builtInCustomizer.accept(ctx);
+            c.accept(ctx);
+        }).orElse(builtInCustomizer)));
+    }
+
+    // shortcut
+    public Meecrowave deployClasspath() {
+        return deployClasspath("");
+    }
+
+    // shortcut (used by plugins)
+    public Meecrowave deployClasspath(final String context) {
+        return deployClasspath(new DeploymentMeta(context, null, null));
+    }
+
+    // shortcut
+    public Meecrowave deployWebapp(final File warOrDir) {
+        return deployWebapp("", warOrDir);
+    }
+
+    // shortcut (used by plugins)
+    public Meecrowave deployWebapp(final String context, final File warOrDir) {
+        return deployWebapp(new DeploymentMeta(context, warOrDir, null));
+    }
+
+    public Meecrowave deployWebapp(final DeploymentMeta meta) {
+        if (contexts.containsKey(meta.context)) {
+            throw new IllegalArgumentException("Already deployed: '" + meta.context + "'");
+        }
+        // always nice to see the deployment with something else than internals
+        new LogFacade(Meecrowave.class.getName())
+                .info("--------------- " + configuration.getActiveProtocol() + "://"
+                        + tomcat.getHost().getName() + ':' + configuration.getActivePort() + meta.context);
+
+
+        final File dir = ofNullable(meta.docBase).orElseGet(() -> {
+            final File d = new File(ownedTempDir, "classpath/fake-" + meta.context.replace("/", ""));
+            IO.mkdirs(d);
+            return d;
+        });
+
+        final StandardContext ctx = new StandardContext();
+        ctx.setPath(meta.context);
+        ctx.setName(meta.context);
+        ctx.setJarScanner(new OWBJarScanner());
+        ctx.setInstanceManager(new CDIInstanceManager());
+        try {
+            ctx.setDocBase(dir.getCanonicalPath());
+        } catch (final IOException e) {
+            ctx.setDocBase(dir.getAbsolutePath());
+        }
+        ofNullable(configuration.tomcatFilter).ifPresent(filter -> {
+            final OWBJarScanner jarScanner = new OWBJarScanner();
+            try {
+                jarScanner.setJarScanFilter(JarScanFilter.class.cast(Thread.currentThread().getContextClassLoader().loadClass(filter).newInstance()));
+            } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+                throw new IllegalArgumentException(e);
+            }
+            ctx.setJarScanner(jarScanner);
+        });
+        ctx.addLifecycleListener(new MeecrowaveContextConfig(configuration));
+        ctx.addLifecycleListener(event -> {
+            switch (event.getType()) {
+                case Lifecycle.AFTER_START_EVENT:
+                    ctx.getResources().setCachingAllowed(configuration.webResourceCached);
+                    break;
+                case Lifecycle.BEFORE_INIT_EVENT:
+                    if (configuration.loginConfig != null) {
+                        ctx.setLoginConfig(configuration.loginConfig.build());
+                    }
+                    for (final SecurityConstaintBuilder sc : configuration.securityConstraints) {
+                        ctx.addConstraint(sc.build());
+                    }
+                    if (configuration.webXml != null) {
+                        ctx.getServletContext().setAttribute(Globals.ALT_DD_ATTR, configuration.webXml);
+                    }
+                    break;
+                default:
+            }
+
+        });
+        ctx.addLifecycleListener(new Tomcat.FixContextListener()); // after having configured the security!!!
+
+        ctx.addServletContainerInitializer((c, ctx1) -> {
+            ctx.getServletContext().setAttribute("meecrowave.configuration", configuration);
+
+            new OWBAutoSetup().onStartup(c, ctx1);
+            new CxfCdiAutoSetup().onStartup(c, ctx1);
+            new TomcatAutoInitializer().onStartup(c, ctx1);
+        }, emptySet());
+
+        if (configuration.isUseTomcatDefaults()) {
+            ctx.setSessionTimeout(30);
+            ctx.addWelcomeFile("index.html");
+            ctx.addWelcomeFile("index.htm");
+            try {
+                final Field mimesField = Tomcat.class.getDeclaredField("DEFAULT_MIME_MAPPINGS");
+                if (!mimesField.isAccessible()) {
+                    mimesField.setAccessible(true);
+                }
+                final String[] defaultMimes = String[].class.cast(mimesField.get(null));
+                for (int i = 0; i < defaultMimes.length; ) {
+                    ctx.addMimeMapping(defaultMimes[i++], defaultMimes[i++]);
+                }
+            } catch (final NoSuchFieldException | IllegalAccessException e) {
+                throw new IllegalStateException("Incompatible Tomcat", e);
+            }
+        }
+
+        ofNullable(meta.consumer).ifPresent(c -> c.accept(ctx));
+
+        tomcat.getHost().addChild(ctx);
+        contexts.put(meta.context, () -> {
+            try {
+                tomcat.getHost().removeChild(ctx);
+            } finally {
+                if (dir != meta.docBase) {
+                    IO.delete(dir);
+                }
+            }
+
+        });
+        return this;
+    }
+
+    public Meecrowave bake() {
+        return bake("");
+    }
+
+    public Meecrowave bake(final String ctx) {
+        start();
+        return deployClasspath(ctx);
+    }
+
+    public Meecrowave start() {
+        clearCatalinaSystemProperties = System.getProperty("catalina.base") == null && System.getProperty("catalina.home") == null;
+        if (configuration.isUseLog4j2JulLogManager()) {
+            System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
+        }
+
+        if (configuration.loggingGlobalSetup) {
+            final String[] toRestore = new String[]{
+                    System.getProperty("openwebbeans.logging.factory"),
+                    System.getProperty("org.apache.cxf.Logger"),
+                    System.getProperty("org.apache.tomcat.Logger")
+            };
+            System.setProperty("openwebbeans.logging.factory", Log4j2LoggerFactory.class.getName());
+            System.setProperty("org.apache.cxf.Logger", Log4j2Logger.class.getName());
+            System.setProperty("org.apache.tomcat.Logger", Log4j2Log.class.getName());
+            postTask = () -> {
+                if (toRestore[0] == null) {
+                    System.clearProperty("openwebbeans.logging.factory");
+                } else {
+                    System.setProperty("openwebbeans.logging.factory", toRestore[0]);
+                }
+                if (toRestore[1] == null) {
+                    System.clearProperty("org.apache.cxf.Logger");
+                } else {
+                    System.setProperty("org.apache.cxf.Logger", toRestore[1]);
+                }
+                if (toRestore[2] == null) {
+                    System.clearProperty("org.apache.tomcat.Logger");
+                } else {
+                    System.setProperty("org.apache.tomcat.Logger", toRestore[2]);
+                }
+            };
+        }
+        if (configuration.quickSession) {
+            tomcat = new TomcatWithFastSessionIDs();
+        } else {
+            tomcat = new InternalTomcat();
+        }
+
+        { // setup
+            base = new File(newBaseDir());
+
+            final File conf = createDirectory(base, "conf");
+            createDirectory(base, "lib");
+            createDirectory(base, "logs");
+            createDirectory(base, "temp");
+            createDirectory(base, "work");
+            createDirectory(base, "webapps");
+
+            synchronize(conf, configuration.conf);
+        }
+
+        final Properties props = configuration.properties;
+        StrSubstitutor substitutor = null;
+        for (final String s : props.stringPropertyNames()) {
+            final String v = props.getProperty(s);
+            if (v != null && v.contains("${")) {
+                if (substitutor == null) {
+                    final Map<String, String> placeHolders = new HashMap<>();
+                    placeHolders.put("meecrowave.embedded.http", Integer.toString(configuration.httpPort));
+                    placeHolders.put("meecrowave.embedded.https", Integer.toString(configuration.httpsPort));
+                    placeHolders.put("meecrowave.embedded.stop", Integer.toString(configuration.stopPort));
+                    substitutor = new StrSubstitutor(placeHolders);
+                }
+                props.put(s, substitutor.replace(v));
+            }
+        }
+
+        final File conf = new File(base, "conf");
+        final File webapps = new File(base, "webapps");
+
+        tomcat.setBaseDir(base.getAbsolutePath());
+        tomcat.setHostname(configuration.host);
+
+        final boolean initialized;
+        if (configuration.serverXml != null) {
+            final File file = new File(conf, "server.xml");
+            if (!file.equals(configuration.serverXml)) {
+                try (final InputStream is = new FileInputStream(configuration.serverXml);
+                     final FileOutputStream fos = new FileOutputStream(file)) {
+                    IO.copy(is, fos);
+                } catch (final IOException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+
+            // respect config (host/port) of the Configuration
+            final QuickServerXmlParser ports = QuickServerXmlParser.parse(file);
+            if (configuration.keepServerXmlAsThis) {
+                configuration.httpPort = Integer.parseInt(ports.http());
+                configuration.stopPort = Integer.parseInt(ports.stop());
+            } else {
+                final Map<String, String> replacements = new HashMap<>();
+                replacements.put(ports.http(), String.valueOf(configuration.httpPort));
+                replacements.put(ports.https(), String.valueOf(configuration.httpsPort));
+                replacements.put(ports.stop(), String.valueOf(configuration.stopPort));
+
+                String serverXmlContent;
+                try (final InputStream stream = new FileInputStream(file)) {
+                    serverXmlContent = IO.toString(stream);
+                    for (final Map.Entry<String, String> pair : replacements.entrySet()) {
+                        serverXmlContent = serverXmlContent.replace(pair.getKey(), pair.getValue());
+                    }
+                } catch (final IOException e) {
+                    throw new IllegalStateException(e);
+                }
+                try (final OutputStream os = new FileOutputStream(file)) {
+                    os.write(serverXmlContent.getBytes(StandardCharsets.UTF_8));
+                } catch (final IOException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+
+            tomcat.server(createServer(file.getAbsolutePath()));
+            initialized = true;
+        } else {
+            tomcat.getServer().setPort(configuration.stopPort);
+            initialized = false;
+        }
+
+        ofNullable(configuration.getSharedLibraries()).map(File::new).filter(File::isDirectory).ifPresent(libRoot -> {
+            final Collection<URL> libs = new ArrayList<>();
+            try {
+                libs.add(libRoot.toURI().toURL());
+            } catch (final MalformedURLException e) {
+                throw new IllegalStateException(e);
+            }
+            libs.addAll(ofNullable(libRoot.listFiles((dir, name) -> name.endsWith(".jar") || name.endsWith(".zip")))
+                    .map(Stream::of).map(s -> s.map(f -> {
+                        try {
+                            return f.toURI().toURL();
+                        } catch (final MalformedURLException e) {
+                            throw new IllegalStateException(e);
+                        }
+                    }).collect(toList()))
+                    .orElse(emptyList()));
+            tomcat.getServer().setParentClassLoader(new MeecrowaveContainerLoader(libs.toArray(new URL[libs.size()]), Thread.currentThread().getContextClassLoader()));
+        });
+
+        if (!initialized) {
+            tomcat.setHostname(configuration.host);
+            tomcat.getEngine().setDefaultHost(configuration.host);
+            final StandardHost host = new StandardHost();
+            host.setName(configuration.host);
+            host.setAppBase(webapps.getAbsolutePath());
+            host.setUnpackWARs(true); // forced for now cause OWB doesn't support war:file:// urls
+            try {
+                host.setWorkDir(new File(base, "work").getCanonicalPath());
+            } catch (final IOException e) {
+                host.setWorkDir(new File(base, "work").getAbsolutePath());
+            }
+            tomcat.setHost(host);
+        }
+
+        if (configuration.realm != null) {
+            tomcat.getEngine().setRealm(configuration.realm);
+        }
+
+        if (tomcat.getRawConnector() == null && !configuration.skipHttp) {
+            final Connector connector = createConnector();
+            connector.setPort(configuration.httpPort);
+            if (connector.getAttribute("connectionTimeout") == null) {
+                connector.setAttribute("connectionTimeout", "3000");
+            }
+
+            tomcat.getService().addConnector(connector);
+            tomcat.setConnector(connector);
+        }
+
+        // create https connector
+        if (configuration.ssl) {
+            final Connector httpsConnector = createConnector();
+            httpsConnector.setPort(configuration.httpsPort);
+            httpsConnector.setSecure(true);
+            httpsConnector.setProperty("SSLEnabled", "true");
+            httpsConnector.setProperty("sslProtocol", configuration.sslProtocol);
+
+            if (configuration.keystoreFile != null) {
+                httpsConnector.setAttribute("", configuration.keystoreFile);
+            }
+            if (configuration.keystorePass != null) {
+                httpsConnector.setAttribute("keystorePass", configuration.keystorePass);
+            }
+            httpsConnector.setAttribute("keystoreType", configuration.keystoreType);
+            if (configuration.clientAuth != null) {
+                httpsConnector.setAttribute("clientAuth", configuration.clientAuth);
+            }
+
+            if (configuration.keyAlias != null) {
+                httpsConnector.setAttribute("keyAlias", configuration.keyAlias);
+            }
+
+            tomcat.getService().addConnector(httpsConnector);
+
+            if (configuration.skipHttp) {
+                tomcat.setConnector(httpsConnector);
+            }
+        }
+
+        if (configuration.http2) {
+            final Connector c = configuration.ssl ?
+                    tomcat.getService().findConnectors()[tomcat.getService().findConnectors().length - 1] :
+                    ofNullable(tomcat.getRawConnector()).orElse(tomcat.getService().findConnectors()[0]);
+
+            c.addUpgradeProtocol(new Http2Protocol());
+            c.addSslHostConfig(buildSslHostConfig());
+        }
+
+        for (final Connector c : configuration.connectors) {
+            tomcat.getService().addConnector(c);
+        }
+        if (!configuration.skipHttp && !configuration.ssl && !configuration.connectors.isEmpty()) {
+            tomcat.setConnector(configuration.connectors.iterator().next());
+        }
+
+        if (configuration.users != null) {
+            for (final Map.Entry<String, String> user : configuration.users.entrySet()) {
+                tomcat.addUser(user.getKey(), user.getValue());
+            }
+        }
+        if (configuration.roles != null) {
+            for (final Map.Entry<String, String> user : configuration.roles.entrySet()) {
+                for (final String role : user.getValue().split(" *, *")) {
+                    tomcat.addRole(user.getKey(), role);
+                }
+            }
+        }
+
+        beforeStart();
+
+        try {
+            if (!initialized) {
+                tomcat.init();
+            }
+            tomcat.start();
+        } catch (final LifecycleException e) {
+            throw new IllegalStateException(e);
+        }
+        if (configuration.isUseShutdownHook()) {
+            hook = new Thread(() -> {
+                hook = null; // prevent close to remove the hook which would throw an exception
+                close();
+            }, "meecrowave-stop-hook");
+            Runtime.getRuntime().addShutdownHook(hook);
+        }
+        return this;
+    }
+
+    private SSLHostConfig buildSslHostConfig() {
+        final ObjectRecipe recipe = new ObjectRecipe(SSLHostConfig.class);
+        for (final String key : configuration.properties.stringPropertyNames()) {
+            if (!key.startsWith("connector.sslhostconfig.")) {
+                continue;
+            }
+            final String substring = key.substring("connector.sslhostconfig.".length());
+            recipe.setProperty(substring, configuration.properties.getProperty(key));
+        }
+        return SSLHostConfig.class.cast(recipe.create());
+    }
+
+    protected void beforeStart() {
+        // no-op
+    }
+
+    protected void beforeStop() {
+        // no-op
+    }
+
+    @Override
+    public void close() {
+        if (tomcat == null) {
+            return;
+        }
+        if (hook != null) {
+            Runtime.getRuntime().removeShutdownHook(hook);
+            this.hook = null;
+        }
+        beforeStop();
+        if (MeecrowaveContainerLoader.class.isInstance(tomcat.getServer().getParentClassLoader())) {
+            try {
+                MeecrowaveContainerLoader.class.cast(tomcat.getServer().getParentClassLoader()).close();
+            } catch (final IOException e) {
+                new LogFacade(Meecrowave.class.getName()).error(e.getMessage(), e);
+            }
+        }
+        try {
+            contexts.values().forEach(Runnable::run);
+        } finally {
+            try {
+                tomcat.stop();
+                tomcat.destroy();
+            } catch (final LifecycleException e) {
+                throw new IllegalStateException(e);
+            } finally {
+                if (clearCatalinaSystemProperties) {
+                    Stream.of("catalina.base", "catalina.home").forEach(System::clearProperty);
+                }
+                if (configuration.isUseLog4j2JulLogManager()) {
+                    System.clearProperty("java.util.logging.manager");
+                }
+                ofNullable(postTask).ifPresent(Runnable::run);
+                postTask = null;
+                try {
+                    IO.delete(base);
+                    IO.delete(ownedTempDir);
+                } catch (final IllegalArgumentException /*does not exist from the hook*/ e) {
+                    // no-op
+                } finally {
+                    base = null;
+                }
+            }
+        }
+    }
+
+    protected Connector createConnector() {
+        final Connector connector;
+        final Properties properties = configuration.properties;
+        if (properties != null) {
+            final Map<String, String> attributes = new HashMap<>();
+            final ObjectRecipe recipe = new ObjectRecipe(Connector.class);
+            for (final String key : properties.stringPropertyNames()) {
+                if (!key.startsWith("connector.")) {
+                    continue;
+                }
+
+                final String substring = key.substring("connector.".length());
+                if (substring.startsWith("sslhostconfig.")) {
+                    continue;
+                }
+
+                if (!substring.startsWith("attributes.")) {
+                    recipe.setProperty(substring, properties.getProperty(key));
+                } else {
+                    attributes.put(substring.substring("attributes.".length()), properties.getProperty(key));
+                }
+            }
+            connector = recipe.getProperties().isEmpty() ? new Connector() : Connector.class.cast(recipe.create());
+            for (final Map.Entry<String, String> attr : attributes.entrySet()) {
+                connector.setAttribute(attr.getKey(), attr.getValue());
+            }
+        } else {
+            connector = new Connector();
+        }
+        return connector;
+    }
+
+    private static Server createServer(final String serverXml) {
+        final Catalina catalina = new Catalina() {
+            // skip few init we don't need *here*
+            @Override
+            protected void initDirs() {
+                // no-op
+            }
+
+            @Override
+            protected void initStreams() {
+                // no-op
+            }
+
+            @Override
+            protected void initNaming() {
+                // no-op
+            }
+        };
+        catalina.setConfigFile(serverXml);
+        catalina.load();
+        return catalina.getServer();
+    }
+
+    private File createDirectory(final File parent, final String directory) {
+        final File dir = new File(parent, directory);
+        IO.mkdirs(dir);
+        return dir;
+    }
+
+    private void synchronize(final File base, final String resourceBase) {
+        if (resourceBase == null) {
+            return;
+        }
+
+        try {
+            final Map<String, URL> urls = new ResourceFinder("").getResourcesMap(resourceBase);
+            for (final Map.Entry<String, URL> u : urls.entrySet()) {
+                try (final InputStream is = u.getValue().openStream()) {
+                    final File to = new File(base, u.getKey());
+                    try (final OutputStream os = new FileOutputStream(to)) {
+                        IO.copy(is, os);
+                    }
+                    if ("server.xml".equals(u.getKey())) {
+                        configuration.setServerXml(to.getAbsolutePath());
+                    }
+                }
+            }
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private String newBaseDir() {
+        File file;
+
+        final String dir = configuration.dir;
+        if (dir != null) {
+            final File dirFile = new File(dir);
+            if (dirFile.exists()) {
+                if (base.exists() && configuration.deleteBaseOnStartup) {
+                    IO.delete(base);
+                }
+                return dir;
+            }
+            IO.mkdirs(dirFile);
+            return dirFile.getAbsolutePath();
+        }
+
+        file = new File(Stream.of("target", "build", ".")
+                .map(File::new)
+                .filter(File::isDirectory)
+                .findFirst().get(), "meecrowave-" + System.nanoTime());
+        IO.mkdirs(file);
+        return file.getAbsolutePath();
+    }
+
+    public void await() {
+        tomcat.getServer().await();
+    }
+
+    public static class Builder {
+        @CliOption(name = "http", description = "HTTP port")
+        private int httpPort = 8080;
+
+        @CliOption(name = "https", description = "HTTPS port")
+        private int httpsPort = 8443;
+
+        @CliOption(name = "stop", description = "Shutdown port if used or -1")
+        private int stopPort = -1;
+
+        @CliOption(name = "host", description = "Default host")
+        private String host = "localhost";
+
+        @CliOption(name = "dir", description = "Root folder if provided otherwise a fake one is created in tmp-dir")
+        private String dir;
+
+        @CliOption(name = "server-xml", description = "Provided server.xml")
+        private File serverXml;
+
+        @CliOption(name = "keep-server-xml-as-this", description = "Don't replace ports in server.xml")
+        private boolean keepServerXmlAsThis;
+
+        @CliOption(name = "properties", description = "Passthrough properties")
+        private Properties properties = new Properties();
+
+        @CliOption(name = "quick-session", description = "Should an unsecured but fast session id generator be used")
+        private boolean quickSession = true;
+
+        @CliOption(name = "skip-http", description = "Skip HTTP connector")
+        private boolean skipHttp;
+
+        @CliOption(name = "ssl", description = "Use HTTPS")
+        private boolean ssl;
+
+        @CliOption(name = "keystore-file", description = "HTTPS keystore location")
+        private String keystoreFile;
+
+        @CliOption(name = "keystore-password", description = "HTTPS keystore password")
+        private String keystorePass;
+
+        @CliOption(name = "keystore-type", description = "HTTPS keystore type")
+        private String keystoreType = "JKS";
+
+        @CliOption(name = "client-auth", description = "HTTPS keystore client authentication")
+        private String clientAuth;
+
+        @CliOption(name = "keystore-alias", description = "HTTPS keystore alias")
+        private String keyAlias;
+
+        @CliOption(name = "ssl-protocol", description = "HTTPS protocol")
+        private String sslProtocol;
+
+        @CliOption(name = "web-xml", description = "Global web.xml")
+        private String webXml;
+
+        @CliOption(name = "login-config", description = "web.xml login config")
+        private LoginConfigBuilder loginConfig;
+
+        @CliOption(name = "security-constraint", description = "web.xml security constraint")
+        private Collection<SecurityConstaintBuilder> securityConstraints = new LinkedList<>();
+
+        @CliOption(name = "realm", description = "realm")
+        private Realm realm;
+
+        @CliOption(name = "users", description = "In memory users")
+        private Map<String, String> users;
+
+        @CliOption(name = "roles", description = "In memory roles")
+        private Map<String, String> roles;
+
+        @CliOption(name = "http2", description = "Activate HTTP 2")
+        private boolean http2;
+
+        @CliOption(name = "connector", description = "Custom connectors")
+        private final Collection<Connector> connectors = new ArrayList<>();
+
+        @CliOption(name = "tmp-dir", description = "Temporary directory")
+        private String tempDir = System.getProperty("java.io.tmpdir");
+
+        @CliOption(name = "web-resource-cached", description = "Cache web resources")
+        private boolean webResourceCached = true;
+
+        @CliOption(name = "conf", description = "Conf folder to synchronize")
+        private String conf;
+
+        @CliOption(name = "delete-on-startup", description = "Should the directory be cleaned on startup if existing")
+        private boolean deleteBaseOnStartup = true;
+
+        @CliOption(name = "jaxrs-mapping", description = "Default jaxrs mapping")
+        private String jaxrsMapping = "/*";
+
+        @CliOption(name = "cdi-conversation", description = "Should CDI conversation be activated")
+        private boolean cdiConversation;
+
+        @CliOption(name = "jaxrs-provider-setup", description = "Should default JAX-RS provider be configured")
+        private boolean jaxrsProviderSetup = true;
+
+        @CliOption(name = "jaxrs-default-providers", description = "If jaxrsProviderSetup is true the list of default providers to load (or defaulting to johnson jsonb and jsonp ones)")
+        private String jaxrsDefaultProviders;
+
+        @CliOption(name = "jaxrs-log-provider", description = "Should JAX-RS providers be logged")
+        private boolean jaxrsLogProviders = false;
+
+        @CliOption(name = "logging-global-setup", description = "Should logging be configured to use log4j2 (it is global)")
+        private boolean loggingGlobalSetup = true;
+
+        @CliOption(name = "cxf-servlet-params", description = "Init parameters passed to CXF servlet")
+        private Map<String, String> cxfServletParams;
+
+        @CliOption(name = "tomcat-scanning", description = "Should Tomcat scanning be used (@HandleTypes, @WebXXX)")
+        private boolean tomcatScanning = true;
+
+        @CliOption(name = "tomcat-default-setup", description = "Add default servlet")
+        private boolean tomcatAutoSetup = true;
+
+        @CliOption(name = "use-shutdown-hook", description = "Use shutdown hook to automatically stop the container on Ctrl+C")
+        private boolean useShutdownHook = true;
+
+        @CliOption(name = "tomcat-filter", description = "A Tomcat JarScanFilter")
+        private String tomcatFilter;
+
+        @CliOption(name = "tomcat-default", description = "Should Tomcat default be set (session timeout, mime mapping etc...)")
+        private boolean useTomcatDefaults = true;
+
+        @CliOption(name = "tomcat-wrap-loader", description = "(Experimental) When deploying a classpath (current classloader), " +
+                "should meecrowave wrap the loader to define another loader identity but still use the same classes and resources.")
+        private boolean tomcatWrapLoader = false;
+
+        @CliOption(name = "shared-librairies", description = "A folder containing shared libraries.")
+        private String sharedLibraries;
+
+        @CliOption(name = "log4j2-jul-bridge", description = "Should JUL logs be redirected to Log4j2 - only works before JUL usage.")
+        private boolean useLog4j2JulLogManager = System.getProperty("java.util.logging.manager") == null;
+
+        public Builder() { // load defaults
+            loadFrom("meecrowave.properties");
+        }
+
+        public String getSharedLibraries() {
+            return sharedLibraries;
+        }
+
+        public Builder sharedLibraries(final String sharedLibraries) {
+            setSharedLibraries(sharedLibraries);
+            return this;
+        }
+
+        public void setSharedLibraries(final String sharedLibraries) {
+            this.sharedLibraries = sharedLibraries;
+        }
+
+        public boolean isJaxrsLogProviders() {
+            return jaxrsLogProviders;
+        }
+
+        public void setJaxrsLogProviders(final boolean jaxrsLogProviders) {
+            this.jaxrsLogProviders = jaxrsLogProviders;
+        }
+
+        public boolean isUseTomcatDefaults() {
+            return useTomcatDefaults;
+        }
+
+        public void setUseTomcatDefaults(final boolean useTomcatDefaults) {
+            this.useTomcatDefaults = useTomcatDefaults;
+        }
+
+        public String getTomcatFilter() {
+            return tomcatFilter;
+        }
+
+        public void setTomcatFilter(final String tomcatFilter) {
+            this.tomcatFilter = tomcatFilter;
+        }
+
+        public boolean isTomcatScanning() {
+            return tomcatScanning;
+        }
+
+        public void setTomcatScanning(final boolean tomcatScanning) {
+            this.tomcatScanning = tomcatScanning;
+        }
+
+        public Map<String, String> getCxfServletParams() {
+            return cxfServletParams;
+        }
+
+        public void setCxfServletParams(final Map<String, String> cxfServletParams) {
+            this.cxfServletParams = cxfServletParams;
+        }
+
+        public boolean isLoggingGlobalSetup() {
+            return loggingGlobalSetup;
+        }
+
+        public void setLoggingGlobalSetup(final boolean loggingGlobalSetup) {
+            this.loggingGlobalSetup = loggingGlobalSetup;
+        }
+
+        public boolean isJaxrsProviderSetup() {
+            return jaxrsProviderSetup;
+        }
+
+        public void setJaxrsProviderSetup(final boolean jaxrsProviderSetup) {
+            this.jaxrsProviderSetup = jaxrsProviderSetup;
+        }
+
+        public int getHttpPort() {
+            return httpPort;
+        }
+
+        public void setHttpPort(final int httpPort) {
+            this.httpPort = httpPort;
+        }
+
+        public int getHttpsPort() {
+            return httpsPort;
+        }
+
+        public void setHttpsPort(final int httpsPort) {
+            this.httpsPort = httpsPort;
+        }
+
+        public int getStopPort() {
+            return stopPort;
+        }
+
+        public void setStopPort(final int stopPort) {
+            this.stopPort = stopPort;
+        }
+
+        public String getHost() {
+            return host;
+        }
+
+        public void setHost(final String host) {
+            this.host = host;
+        }
+
+        public String getDir() {
+            return dir;
+        }
+
+        public void setDir(final String dir) {
+            this.dir = dir;
+        }
+
+        public File getServerXml() {
+            return serverXml;
+        }
+
+        public void setServerXml(final File serverXml) {
+            this.serverXml = serverXml;
+        }
+
+        public boolean isKeepServerXmlAsThis() {
+            return keepServerXmlAsThis;
+        }
+
+        public void setKeepServerXmlAsThis(final boolean keepServerXmlAsThis) {
+            this.keepServerXmlAsThis = keepServerXmlAsThis;
+        }
+
+        public Properties getProperties() {
+            return properties;
+        }
+
+        public void setProperties(final Properties properties) {
+            this.properties = properties;
+        }
+
+        public boolean isQuickSession() {
+            return quickSession;
+        }
+
+        public void setQuickSession(final boolean quickSession) {
+            this.quickSession = quickSession;
+        }
+
+        public boolean isSkipHttp() {
+            return skipHttp;
+        }
+
+        public void setSkipHttp(final boolean skipHttp) {
+            this.skipHttp = skipHttp;
+        }
+
+        public boolean isSsl() {
+            return ssl;
+        }
+
+        public void setSsl(final boolean ssl) {
+            this.ssl = ssl;
+        }
+
+        public String getKeystoreFile() {
+            return keystoreFile;
+        }
+
+        public void setKeystoreFile(final String keystoreFile) {
+            this.keystoreFile = keystoreFile;
+        }
+
+        public String getKeystorePass() {
+            return keystorePass;
+        }
+
+        public void setKeystorePass(final String keystorePass) {
+            this.keystorePass = keystorePass;
+        }
+
+        public String getKeystoreType() {
+            return keystoreType;
+        }
+
+        public void setKeystoreType(final String keystoreType) {
+            this.keystoreType = keystoreType;
+        }
+
+        public String getClientAuth() {
+            return clientAuth;
+        }
+
+        public void setClientAuth(final String clientAuth) {
+            this.clientAuth = clientAuth;
+        }
+
+        public String getKeyAlias() {
+            return keyAlias;
+        }
+
+        public void setKeyAlias(final String keyAlias) {
+            this.keyAlias = keyAlias;
+        }
+
+        public String getSslProtocol() {
+            return sslProtocol;
+        }
+
+        public void setSslProtocol(final String sslProtocol) {
+            this.sslProtocol = sslProtocol;
+        }
+
+        public String getWebXml() {
+            return webXml;
+        }
+
+        public void setWebXml(final String webXml) {
+            this.webXml = webXml;
+        }
+
+        public LoginConfigBuilder getLoginConfig() {
+            return loginConfig;
+        }
+
+        public Builder loginConfig(final LoginConfigBuilder loginConfig) {
+            setLoginConfig(loginConfig);
+            return this;
+        }
+
+        public void setLoginConfig(final LoginConfigBuilder loginConfig) {
+            this.loginConfig = loginConfig;
+        }
+
+        public Collection<SecurityConstaintBuilder> getSecurityConstraints() {
+            return securityConstraints;
+        }
+
+        public Builder securityConstraints(final SecurityConstaintBuilder securityConstraint) {
+            securityConstraints = securityConstraints == null ? new ArrayList<>() : securityConstraints;
+            securityConstraints.add(securityConstraint);
+            return this;
+        }
+
+        public void setSecurityConstraints(final Collection<SecurityConstaintBuilder> securityConstraints) {
+            this.securityConstraints = securityConstraints;
+        }
+
+        public Realm getRealm() {
+            return realm;
+        }
+
+        public Builder realm(final Realm realm) {
+            setRealm(realm);
+            return this;
+        }
+
+        public void setRealm(final Realm realm) {
+            this.realm = realm;
+        }
+
+        public Map<String, String> getUsers() {
+            return users;
+        }
+
+        public void setUsers(final Map<String, String> users) {
+            this.users = users;
+        }
+
+        public Map<String, String> getRoles() {
+            return roles;
+        }
+
+        public void setRoles(final Map<String, String> roles) {
+            this.roles = roles;
+        }
+
+        public boolean isHttp2() {
+            return http2;
+        }
+
+        public void setHttp2(final boolean http2) {
+            this.http2 = http2;
+        }
+
+        public Collection<Connector> getConnectors() {
+            return connectors;
+        }
+
+        public String getTempDir() {
+            return tempDir;
+        }
+
+        public void setTempDir(final String tempDir) {
+            this.tempDir = tempDir;
+        }
+
+        public boolean isWebResourceCached() {
+            return webResourceCached;
+        }
+
+        public void setWebResourceCached(final boolean webResourceCached) {
+            this.webResourceCached = webResourceCached;
+        }
+
+        public String getConf() {
+            return conf;
+        }
+
+        public void setConf(final String conf) {
+            this.conf = conf;
+        }
+
+        public boolean isDeleteBaseOnStartup() {
+            return deleteBaseOnStartup;
+        }
+
+        public void setDeleteBaseOnStartup(final boolean deleteBaseOnStartup) {
+            this.deleteBaseOnStartup = deleteBaseOnStartup;
+        }
+
+        public String getJaxrsMapping() {
+            return jaxrsMapping;
+        }
+
+        public void setJaxrsMapping(final String jaxrsMapping) {
+            this.jaxrsMapping = jaxrsMapping;
+        }
+
+        public boolean isCdiConversation() {
+            return cdiConversation;
+        }
+
+        public void setCdiConversation(final boolean cdiConversation) {
+            this.cdiConversation = cdiConversation;
+        }
+
+        public Builder randomHttpPort() {
+            try (final ServerSocket serverSocket = new ServerSocket(0)) {
+                this.httpPort = serverSocket.getLocalPort();
+            } catch (final IOException e) {
+                throw new IllegalStateException(e);
+            }
+            return this;
+        }
+
+        public Builder loadFrom(final String resource) {
+            try (final InputStream is = findStream(resource)) {
+                if (is != null) {
+                    final Properties config = new Properties() {{
+                        load(is);
+                    }};
+                    loadFromProperties(config);
+                }
+                return this;
+            } catch (final IOException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+
+        public void setServerXml(final String file) {
+            if (file == null) {
+                serverXml = null;
+            } else {
+                final File sXml = new File(file);
+                if (sXml.exists()) {
+                    serverXml = sXml;
+                }
+            }
+        }
+
+        public Builder property(final String key, final String value) {
+            properties.setProperty(key, value);
+            return this;
+        }
+
+        private InputStream findStream(final String resource) throws FileNotFoundException {
+            InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
+            if (stream == null) {
+                final File file = new File(resource);
+                if (file.exists()) {
+                    return new FileInputStream(file);
+                }
+            }
+            return stream;
+        }
+
+        public Builder user(final String name, final String pwd) {
+            if (users == null) {
+                users = new HashMap<>();
+            }
+            this.users.put(name, pwd);
+            return this;
+        }
+
+        public Builder role(final String user, final String roles) {
+            if (this.roles == null) {
+                this.roles = new HashMap<>();
+            }
+            this.roles.put(user, roles);
+            return this;
+        }
+
+        public Builder cxfServletParam(final String key, final String value) {
+            if (this.cxfServletParams == null) {
+                this.cxfServletParams = new HashMap<>();
+            }
+            this.cxfServletParams.put(key, value);
+            return this;
+        }
+
+        public String getActiveProtocol() {
+            return isSkipHttp() ? "https" : "http";
+        }
+
+        public int getActivePort() {
+            return isSkipHttp() ? getHttpsPort() : getHttpPort();
+        }
+
+        public boolean isTomcatAutoSetup() {
+            return tomcatAutoSetup;
+        }
+
+        public void setTomcatAutoSetup(final boolean tomcatAutoSetup) {
+            this.tomcatAutoSetup = tomcatAutoSetup;
+        }
+
+        public boolean isUseShutdownHook() {
+            return useShutdownHook;
+        }
+
+        public void setUseShutdownHook(final boolean useShutdownHook) {
+            this.useShutdownHook = useShutdownHook;
+        }
+
+        public Builder noShutdownHook() {
+            setUseShutdownHook(false);
+            return this;
+        }
+
+        public boolean isTomcatWrapLoader() {
+            return tomcatWrapLoader;
+        }
+
+        public void setTomcatWrapLoader(final boolean tomcatWrapLoader) {
+            this.tomcatWrapLoader = tomcatWrapLoader;
+        }
+
+        public void addCustomizer(final Consumer<Builder> configurationCustomizer) {
+            configurationCustomizer.accept(this);
+        }
+
+        public String getJaxrsDefaultProviders() {
+            return jaxrsDefaultProviders;
+        }
+
+        public void setJaxrsDefaultProviders(final String jaxrsDefaultProviders) {
+            this.jaxrsDefaultProviders = jaxrsDefaultProviders;
+        }
+
+        public boolean isUseLog4j2JulLogManager() {
+            return useLog4j2JulLogManager;
+        }
+
+        public void setUseLog4j2JulLogManager(final boolean useLog4j2JulLogManager) {
+            this.useLog4j2JulLogManager = useLog4j2JulLogManager;
+        }
+
+        public void loadFromProperties(final Properties config) {
+            // filtering properties with system properties or themself
+            final StrSubstitutor strSubstitutor = new StrSubstitutor(new StrLookup<String>() {
+                @Override
+                public String lookup(final String key) {
+                    final String property = System.getProperty(key);
+                    return property == null ? config.getProperty(key) : null;
+                }
+            });
+            for (final String key : config.stringPropertyNames()) {
+                final String val = config.getProperty(key);
+                if (val == null || val.trim().isEmpty()) {
+                    continue;
+                }
+                final String newVal = strSubstitutor.replace(config.getProperty(key));
+                if (!val.equals(newVal)) {
+                    config.setProperty(key, newVal);
+                }
+            }
+
+            for (final Field field : Builder.class.getDeclaredFields()) {
+                final CliOption annotation = field.getAnnotation(CliOption.class);
+                if (annotation == null) {
+                    return;
+                }
+                final String name = field.getName();
+                ofNullable(config.getProperty(annotation.name())).ifPresent(val -> {
+                    final Object toSet;
+                    if (field.getType() == String.class) {
+                        toSet = val;
+                    } else if (field.getType() == int.class) {
+                        if ("httpPort".equals(name) && "-1".equals(val)) { // special case in case of random port
+                            randomHttpPort();
+                            toSet = null;
+                        } else {
+                            toSet = Integer.parseInt(val);
+                        }
+                    } else if (field.getType() == boolean.class) {
+                        toSet = Boolean.parseBoolean(val);
+                    } else if (field.getType() == File.class) {
+                        toSet = new File(val);
+                    } else {
+                        toSet = null;
+                    }
+                    if (toSet == null) { // handled elsewhere
+                        return;
+                    }
+
+                    if (!field.isAccessible()) {
+                        field.setAccessible(true);
+                    }
+                    try {
+                        field.set(this, toSet);
+                    } catch (final IllegalAccessException e) {
+                        throw new IllegalStateException(e);
+                    }
+                });
+            }
+
+            // not trivial types
+            for (final String prop : config.stringPropertyNames()) {
+                if (prop.startsWith("properties.")) {
+                    property(prop.substring("properties.".length()), config.getProperty(prop));
+                } else if (prop.startsWith("users.")) {
+                    user(prop.substring("users.".length()), config.getProperty(prop));
+                } else if (prop.startsWith("roles.")) {
+                    role(prop.substring("roles.".length()), config.getProperty(prop));
+                } else if (prop.startsWith("cxf.servlet.params.")) {
+                    cxfServletParam(prop.substring("cxf.servlet.params.".length()), config.getProperty(prop));
+                } else if (prop.startsWith("connector.")) { // created in container
+                    property(prop, config.getProperty(prop));
+                } else if (prop.equals("realm")) {
+                    final ObjectRecipe recipe = new ObjectRecipe(config.getProperty(prop));
+                    for (final String realmConfig : config.stringPropertyNames()) {
+                        if (realmConfig.startsWith("realm.")) {
+                            recipe.setProperty(realmConfig.substring("realm.".length()), config.getProperty(realmConfig));
+                        }
+                    }
+                    this.realm = Realm.class.cast(recipe.create());
+                } else if (prop.equals("login")) {
+                    final ObjectRecipe recipe = new ObjectRecipe(LoginConfigBuilder.class.getName());
+                    for (final String nestedConfig : config.stringPropertyNames()) {
+                        if (nestedConfig.startsWith("login.")) {
+                            recipe.setProperty(nestedConfig.substring("login.".length()), config.getProperty(nestedConfig));
+                        }
+                    }
+                    loginConfig = LoginConfigBuilder.class.cast(recipe.create());
+                } else if (prop.equals("securityConstraint")) {
+                    final ObjectRecipe recipe = new ObjectRecipe(SecurityConstaintBuilder.class.getName());
+                    for (final String nestedConfig : config.stringPropertyNames()) {
+                        if (nestedConfig.startsWith("securityConstraint.")) {
+                            recipe.setProperty(nestedConfig.substring("securityConstraint.".length()), config.getProperty(nestedConfig));
+                        }
+                    }
+                    securityConstraints.add(SecurityConstaintBuilder.class.cast(recipe.create()));
+                } else if (prop.equals("configurationCustomizer")) {
+                    final ObjectRecipe recipe = new ObjectRecipe(properties.getProperty(prop));
+                    for (final String nestedConfig : config.stringPropertyNames()) {
+                        if (nestedConfig.startsWith(prop + '.')) {
+                            recipe.setProperty(nestedConfig.substring(prop.length() + 2 /*dot*/), config.getProperty(nestedConfig));
+                        }
+                    }
+                    addCustomizer(Consumer.class.cast(recipe.create()));
+                }
+            }
+        }
+    }
+
+    public static class LoginConfigBuilder {
+        private final LoginConfig loginConfig = new LoginConfig();
+
+        public void setErrorPage(final String errorPage) {
+            loginConfig.setErrorPage(errorPage);
+        }
+
+        public void setLoginPage(final String loginPage) {
+            loginConfig.setLoginPage(loginPage);
+        }
+
+        public void setRealmName(final String realmName) {
+            loginConfig.setRealmName(realmName);
+        }
+
+        public void setAuthMethod(final String authMethod) {
+            loginConfig.setAuthMethod(authMethod);
+        }
+
+        public LoginConfigBuilder errorPage(final String errorPage) {
+            loginConfig.setErrorPage(errorPage);
+            return this;
+        }
+
+        public LoginConfigBuilder loginPage(final String loginPage) {
+            loginConfig.setLoginPage(loginPage);
+            return this;
+        }
+
+        public LoginConfigBuilder realmName(final String realmName) {
+            loginConfig.setRealmName(realmName);
+            return this;
+        }
+
+        public LoginConfigBuilder authMethod(final String authMethod) {
+            loginConfig.setAuthMethod(authMethod);
+            return this;
+        }
+
+        public LoginConfig build() {
+            return loginConfig;
+        }
+
+        public LoginConfigBuilder basic() {
+            return authMethod("BASIC");
+        }
+
+        public LoginConfigBuilder digest() {
+            return authMethod("DIGEST");
+        }
+
+        public LoginConfigBuilder clientCert() {
+            return authMethod("CLIENT-CERT");
+        }
+
+        public LoginConfigBuilder form() {
+            return authMethod("FORM");
+        }
+    }
+
+    public static class SecurityConstaintBuilder {
+        private final SecurityConstraint securityConstraint = new SecurityConstraint();
+
+        public SecurityConstaintBuilder authConstraint(final boolean authConstraint) {
+            securityConstraint.setAuthConstraint(authConstraint);
+            return this;
+        }
+
+        public SecurityConstaintBuilder displayName(final String displayName) {
+            securityConstraint.setDisplayName(displayName);
+            return this;
+        }
+
+        public SecurityConstaintBuilder userConstraint(final String constraint) {
+            securityConstraint.setUserConstraint(constraint);
+            return this;
+        }
+
+        public SecurityConstaintBuilder addAuthRole(final String authRole) {
+            securityConstraint.addAuthRole(authRole);
+            return this;
+        }
+
+        public SecurityConstaintBuilder addCollection(final String name, final String pattern, final String... methods) {
+            final SecurityCollection collection = new SecurityCollection();
+            collection.setName(name);
+            collection.addPattern(pattern);
+            for (final String httpMethod : methods) {
+                collection.addMethod(httpMethod);
+            }
+            securityConstraint.addCollection(collection);
+            return this;
+        }
+
+        public void setAuthConstraint(final boolean authConstraint) {
+            securityConstraint.setAuthConstraint(authConstraint);
+        }
+
+        public void setDisplayName(final String displayName) {
+            securityConstraint.setDisplayName(displayName);
+        }
+
+        public void setUserConstraint(final String userConstraint) {
+            securityConstraint.setUserConstraint(userConstraint);
+        }
+
+        public void setAuthRole(final String authRole) { // easier for config
+            addAuthRole(authRole);
+        }
+
+        // name:pattern:method1/method2
+        public void setCollection(final String value) { // for config
+            final String[] split = value.split(":");
+            if (split.length != 3 && split.length != 2) {
+                throw new IllegalArgumentException("Can't parse " + value + ", syntax is: name:pattern:method1/method2");
+            }
+            addCollection(split[0], split[1], split.length == 2 ? new String[0] : split[2].split("/"));
+        }
+
+        public SecurityConstraint build() {
+            return securityConstraint;
+        }
+    }
+
+    private static class InternalTomcat extends Tomcat {
+        private Connector connector;
+
+        private void server(final Server s) {
+            server = s;
+            connector = server != null && server.findServices().length > 0 && server.findServices()[0].findConnectors().length > 0 ?
+                    server.findServices()[0].findConnectors()[0] : null;
+        }
+
+        Connector getRawConnector() {
+            return connector;
+        }
+    }
+
+    private static class TomcatWithFastSessionIDs extends InternalTomcat {
+        @Override
+        public void start() throws LifecycleException {
+            // Use fast, insecure session ID generation for all tests
+            final Server server = getServer();
+            for (final Service service : server.findServices()) {
+                final org.apache.catalina.Container e = service.getContainer();
+                for (final org.apache.catalina.Container h : e.findChildren()) {
+                    for (final org.apache.catalina.Container c : h.findChildren()) {
+                        Manager m = ((org.apache.catalina.Context) c).getManager();
+                        if (m == null) {
+                            m = new StandardManager();
+                            org.apache.catalina.Context.class.cast(c).setManager(m);
+                        }
+                        if (m instanceof ManagerBase) {
+                            ManagerBase.class.cast(m).setSecureRandomClass(
+                                    "org.apache.catalina.startup.FastNonSecureRandom");
+                        }
+                    }
+                }
+            }
+            super.start();
+        }
+    }
+
+    private static class QuickServerXmlParser extends DefaultHandler {
+        private static final SAXParserFactory FACTORY = SAXParserFactory.newInstance();
+
+        static {
+            FACTORY.setNamespaceAware(true);
+            FACTORY.setValidating(false);
+        }
+
+        private static final String STOP_KEY = "STOP";
+        private static final String HTTP_KEY = "HTTP";
+        private static final String SECURED_SUFFIX = "S";
+        private static final String HOST_KEY = "host";
+        private static final String DEFAULT_CONNECTOR_KEY = HTTP_KEY;
+
+        private static final String DEFAULT_HTTP_PORT = "8080";
+        private static final String DEFAULT_HTTPS_PORT = "8443";
+        private static final String DEFAULT_STOP_PORT = "8005";
+        private static final String DEFAULT_HOST = "localhost";
+
+        private final Map<String, String> values = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+
+        private QuickServerXmlParser(final boolean useDefaults) {
+            if (useDefaults) {
+                values.put(STOP_KEY, DEFAULT_STOP_PORT);
+                values.put(HTTP_KEY, DEFAULT_HTTP_PORT);
+                values.put(HOST_KEY, DEFAULT_HOST);
+            }
+        }
+
+        @Override
+        public void startElement(final String uri, final String localName,
+                                 final String qName, final Attributes attributes) throws SAXException {
+            if ("Server".equalsIgnoreCase(localName)) {
+                final String port = attributes.getValue("port");
+                if (port != null) {
+                    values.put(STOP_KEY, port);
+                } else {
+                    values.put(STOP_KEY, DEFAULT_STOP_PORT);
+                }
+            } else if ("Connector".equalsIgnoreCase(localName)) {
+                String protocol = attributes.getValue("protocol");
+                if (protocol == null) {
+                    protocol = DEFAULT_CONNECTOR_KEY;
+                } else if (protocol.contains("/")) {
+                    protocol = protocol.substring(0, protocol.indexOf("/"));
+                }
+                final String port = attributes.getValue("port");
+                final String ssl = attributes.getValue("secure");
+
+                if (ssl == null || "false".equalsIgnoreCase(ssl)) {
+                    values.put(protocol.toUpperCase(), port);
+                } else {
+                    values.put(protocol.toUpperCase() + SECURED_SUFFIX, port);
+                }
+            } else if ("Host".equalsIgnoreCase(localName)) {
+                final String host = attributes.getValue("name");
+                if (host != null) {
+                    values.put(HOST_KEY, host);
+                }
+            }
+        }
+
+        private static QuickServerXmlParser parse(final File serverXml) {
+            return parse(serverXml, true);
+        }
+
+        private static QuickServerXmlParser parse(final File serverXml, final boolean defaults) {
+            final QuickServerXmlParser handler = new QuickServerXmlParser(defaults);
+            try {
+                final SAXParser parser = FACTORY.newSAXParser();
+                parser.parse(serverXml, handler);
+            } catch (final Exception e) {
+                // no-op: using defaults
+            }
+            return handler;
+        }
+
+        public String http() {
+            return value(HTTP_KEY, DEFAULT_HTTP_PORT);
+        }
+
+        private String https() { // enough common to be exposed as method
+            return securedValue(HTTP_KEY, DEFAULT_HTTPS_PORT);
+        }
+
+        private String stop() {
+            return value(STOP_KEY, DEFAULT_STOP_PORT);
+        }
+
+        private String value(final String key, final String defaultValue) {
+            final String val = values.get(key);
+            if (val == null) {
+                return defaultValue;
+            }
+            return val;
+        }
+
+        private String securedValue(final String key, final String defaultValue) {
+            return value(key + SECURED_SUFFIX, defaultValue);
+        }
+    }
+
+    // there to be able to stack config later on without breaking all methods
+    public static class DeploymentMeta {
+        private final String context;
+        private final File docBase;
+        private final Consumer<Context> consumer;
+
+        public DeploymentMeta(final String context, final File docBase, final Consumer<Context> consumer) {
+            this.context = context;
+            this.docBase = docBase;
+            this.consumer = consumer;
+        }
+    }
+
+    // just to type it and allow some extensions to use a ServiceLoader
+    public interface ConfigurationCustomizer extends Consumer<Meecrowave.Builder> {
+    }
+
+    private static final class MeecrowaveContainerLoader extends URLClassLoader {
+        private MeecrowaveContainerLoader(final URL[] urls, final ClassLoader parent) {
+            super(urls, parent);
+        }
+    }
+}

Added: openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/MeecrowaveExplosion.java
URL: http://svn.apache.org/viewvc/openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/MeecrowaveExplosion.java?rev=1769479&view=auto
==============================================================================
--- openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/MeecrowaveExplosion.java (added)
+++ openwebbeans/microwave/trunk/meecrowave-core/src/main/java/org/apache/meecrowave/MeecrowaveExplosion.java Sun Nov 13 09:34:07 2016
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.meecrowave;
+
+public class MeecrowaveExplosion extends RuntimeException {
+    public MeecrowaveExplosion(final String msg, final Exception e) {
+        super(msg, e);
+    }
+}