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); + } +}