All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.meecrowave.Meecrowave Maven / Gradle / Ivy

/*
 * 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 static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Comparator.comparing;
import static java.util.Locale.ROOT;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.Writer;
import java.lang.management.ManagementFactory;
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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.CDI;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.SessionCookieConfig;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Realm;
import org.apache.catalina.Server;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Catalina;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.util.StandardSessionIdGenerator;
import org.apache.coyote.http2.Http2Protocol;
import org.apache.meecrowave.api.StartListening;
import org.apache.meecrowave.api.StopListening;
import org.apache.meecrowave.configuration.Configuration;
import org.apache.meecrowave.cxf.ConfigurableBus;
import org.apache.meecrowave.cxf.CxfCdiAutoSetup;
import org.apache.meecrowave.cxf.Cxfs;
import org.apache.meecrowave.io.IO;
import org.apache.meecrowave.lang.Substitutor;
import org.apache.meecrowave.logging.jul.Log4j2Logger;
import org.apache.meecrowave.logging.log4j2.Log4j2Shutdown;
import org.apache.meecrowave.logging.log4j2.Log4j2s;
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.service.Priotities;
import org.apache.meecrowave.service.ValueTransformer;
import org.apache.meecrowave.tomcat.CDIInstanceManager;
import org.apache.meecrowave.tomcat.LoggingAccessLogPattern;
import org.apache.meecrowave.tomcat.MeecrowaveContextConfig;
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.modeler.Registry;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.xbean.finder.ResourceFinder;
import org.apache.xbean.recipe.ObjectRecipe;
import org.apache.xbean.recipe.Option;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class Meecrowave implements AutoCloseable {
    private final Configuration configuration;
    protected ConfigurableBus clientBus;
    protected File base;
    protected final File ownedTempDir;
    protected File workDir;
    protected InternalTomcat tomcat;
    protected volatile Thread hook;

    // we can undeploy webapps with that later
    private final Map contexts = new HashMap<>();
    private Runnable postTask;
    private boolean clearCatalinaSystemProperties;
    private boolean deleteBase;

    public Meecrowave() {
        this(new Builder());
    }

    public Meecrowave(final Configuration configuration) {
        this.configuration = configuration;
        this.ownedTempDir = new File(this.configuration.getTempDir(), "meecrowave_" + System.nanoTime());
    }

    public Builder getConfiguration() {
        return Builder.class.isInstance(configuration) ?
                Builder.class.cast(configuration) :
                new Builder(configuration);
    }

    public File getBase() {
        return base;
    }

    public Tomcat getTomcat() {
        return tomcat;
    }

    public boolean isServing() {
        return tomcat != null && tomcat.getHost().getState() == LifecycleState.STARTED;
    }

    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 builtInCustomizer = c -> c.setLoader(loader);
        return deployWebapp(new DeploymentMeta(meta.context, meta.docBase, ofNullable(meta.consumer).map(c -> (Consumer) ctx -> {
            builtInCustomizer.accept(ctx);
            c.accept(ctx);
        }).orElse(builtInCustomizer), meta.redeployCallback));
    }

    // shortcut
    public Meecrowave deployClasspath() {
        return deployClasspath("");
    }

    // shortcut
    public Meecrowave bake(final Consumer customizer) {
        start();
        return deployClasspath(new DeploymentMeta("", null, customizer, null));
    }

    // shortcut (used by plugins)
    public Meecrowave deployClasspath(final String context) {
        return deployClasspath(new DeploymentMeta(context, null, 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, 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
        final String base = tomcat.getService().findConnectors().length > 0 ?
                (configuration.getActiveProtocol() + "://" + tomcat.getHost().getName() + ':' + configuration.getActivePort()) : "";
        new LogFacade(Meecrowave.class.getName()).info("--------------- " + base + meta.context);


        final OWBJarScanner scanner = new OWBJarScanner();
        final StandardContext ctx = new StandardContext() {
            @Override
            public void setApplicationEventListeners(final Object[] listeners) {
                if (listeners == null) {
                    super.setApplicationEventListeners(null);
                    return;
                }

                // ensure owb is first and cxf is last otherwise surprises,
                // if we don't -> no @RequestScoped in request listeners :(
                for (int i = 1; i < listeners.length; i++) {
                    if (OWBAutoSetup.EagerBootListener.class.isInstance(listeners[i])) {
                        final Object first = listeners[0];
                        listeners[0] = listeners[i];
                        listeners[i] = first;
                        break;
                    }
                }

                // and finally let it go after our re-ordering
                super.setApplicationEventListeners(listeners);
            }
        };
        ctx.setPath(meta.context);
        ctx.setName(meta.context);
        ctx.setJarScanner(scanner);
        ctx.setInstanceManager(new CDIInstanceManager());
        ofNullable(meta.docBase).ifPresent(d -> {
            try {
                ctx.setDocBase(meta.docBase.getCanonicalPath());
            } catch (final IOException e) {
                ctx.setDocBase(meta.docBase.getAbsolutePath());
            }
        });
        ofNullable(configuration.getTomcatFilter()).ifPresent(filter -> {
            try {
                scanner.setJarScanFilter(JarScanFilter.class.cast(Thread.currentThread().getContextClassLoader().loadClass(filter).newInstance()));
            } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                throw new IllegalArgumentException(e);
            }
        });

        final AtomicReference releaseSCI = new AtomicReference<>();
        final ServletContainerInitializer meecrowaveInitializer = (c, ctx1) -> {
            ctx1.setAttribute("meecrowave.configuration", getConfiguration());
            ctx1.setAttribute("meecrowave.instance", Meecrowave.this);

            new OWBAutoSetup().onStartup(c, ctx1);
            if (Cxfs.IS_PRESENT) {
                new CxfCdiAutoSetup().onStartup(c, ctx1);
            }
            new TomcatAutoInitializer().onStartup(c, ctx1);

            if (configuration.isInjectServletContainerInitializer()) {
                final Field f;
                try { // now cdi is on, we can inject cdi beans in ServletContainerInitializer
                    f = StandardContext.class.getDeclaredField("initializers");
                    if (!f.isAccessible()) {
                        f.setAccessible(true);
                    }
                } catch (final Exception e) {
                    throw new IllegalStateException("Bad tomcat version", e);
                }

                final List cc;
                try {
                    cc = ((Map>>) f.get(ctx)).keySet().stream()
                            .filter(i -> !i.getClass().getName().startsWith(Meecrowave.class.getName()))
                            .map(i -> {
                                try {
                                    return this.inject(i);
                                } catch (final IllegalArgumentException iae) {
                                    return null;
                                }
                            })
                            .filter(Objects::nonNull)
                            .collect(toList());
                } catch (final IllegalAccessException e) {
                    throw new IllegalStateException("Can't read initializers", e);
                }
                releaseSCI.set(() -> cc.forEach(closeable -> {
                    try {
                        closeable.close();
                    } catch (final Exception e) {
                        throw new IllegalStateException(e);
                    }
                }));
            }
        };

        ctx.addLifecycleListener(new MeecrowaveContextConfig(configuration, meta.docBase != null, meecrowaveInitializer, meta.redeployCallback));
        ctx.addLifecycleListener(event -> {
            switch (event.getType()) {
                case Lifecycle.BEFORE_START_EVENT:
                    if (configuration.getWebSessionCookieConfig() != null) {
                        final Properties p = new Properties();
                        try {
                            p.load(new StringReader(configuration.getWebSessionCookieConfig()));
                        } catch (final IOException e) {
                            throw new IllegalArgumentException(e);
                        }
                        if (p.containsKey("domain")) {
                            ctx.setSessionCookieDomain(p.getProperty("domain"));
                        }
                        if (p.containsKey("path")) {
                            ctx.setSessionCookiePath(p.getProperty("path"));
                        }
                        if (p.containsKey("name")) {
                            ctx.setSessionCookieName(p.getProperty("name"));
                        }
                        if (p.containsKey("use-trailing-slash")) {
                            ctx.setSessionCookiePathUsesTrailingSlash(Boolean.parseBoolean(p.getProperty("use-trailing-slash")));
                        }
                        if (p.containsKey("http-only")) {
                            ctx.setUseHttpOnly(Boolean.parseBoolean(p.getProperty("http-only")));
                        }
                        if (p.containsKey("secured")) {
                            final SessionCookieConfig sessionCookieConfig = ctx.getServletContext().getSessionCookieConfig();
                            sessionCookieConfig.setSecure(Boolean.parseBoolean(p.getProperty("secured")));
                        }
                    }
                    break;
                case Lifecycle.AFTER_START_EVENT:
                    ctx.getResources().setCachingAllowed(configuration.isWebResourceCached());
                    break;
                case Lifecycle.BEFORE_INIT_EVENT:
                    if (configuration.getLoginConfig() != null) {
                        ctx.setLoginConfig(configuration.getLoginConfig().build());
                    }
                    for (final SecurityConstaintBuilder sc : configuration.getSecurityConstraints()) {
                        ctx.addConstraint(sc.build());
                    }
                    if (configuration.getWebXml() != null) {
                        ctx.getServletContext().setAttribute(Globals.ALT_DD_ATTR, configuration.getWebXml());
                    }
                    break;
                default:
            }

        });
        ctx.addLifecycleListener(new Tomcat.FixContextListener()); // after having configured the security!!!

        ctx.addServletContainerInitializer(meecrowaveInitializer, emptySet());

        if (configuration.isUseTomcatDefaults()) {
            ctx.setSessionTimeout(configuration.getWebSessionTimeout() != null ? configuration.getWebSessionTimeout() : 30);
            ctx.addWelcomeFile("index.html");
            ctx.addWelcomeFile("index.htm");
            Tomcat.addDefaultMimeTypeMappings(ctx);
        } else if (configuration.getWebSessionTimeout() != null) {
            ctx.setSessionTimeout(configuration.getWebSessionTimeout());
        }

        ofNullable(meta.consumer).ifPresent(c -> c.accept(ctx));
        if (configuration.isQuickSession() && ctx.getManager() == null) {
            final StandardManager manager = new StandardManager();
            manager.setSessionIdGenerator(new StandardSessionIdGenerator() {
                @Override
                protected void getRandomBytes(final byte bytes[]) {
                    ThreadLocalRandom.current().nextBytes(bytes);
                }

                @Override
                public String toString() {
                    return "MeecrowaveSessionIdGenerator@" + System.identityHashCode(this);
                }
            });
            ctx.setManager(manager);
        }
        if (configuration.isAntiResourceLocking() && StandardContext.class.isInstance(ctx)) {
            StandardContext.class.cast(ctx).setAntiResourceLocking(true);
        }
        configuration.getInitializers().forEach(i -> ctx.addServletContainerInitializer(i, emptySet()));
        configuration.getGlobalContextConfigurers().forEach(it -> it.accept(ctx));

        final Host host = tomcat.getHost();
        host.addChild(ctx);

        final ClassLoader classLoader = ctx.getLoader().getClassLoader();
        if (host.getState().isAvailable()) {
            fire(new StartListening(findFirstConnector(), host, ctx), classLoader);
        }
        contexts.put(meta.context, () -> {
            if (host.getState().isAvailable()) {
                fire(new StopListening(findFirstConnector(), host, ctx), classLoader);
            }
            ofNullable(releaseSCI.get()).ifPresent(Runnable::run);
            tomcat.getHost().removeChild(ctx);
        });
        return this;
    }

    public Meecrowave bake() {
        return bake("");
    }

    public Meecrowave bake(final String ctx) {
        start();
        return deployClasspath(ctx);
    }

    public Meecrowave start() {
        final Map systemPropsToRestore = new HashMap<>();

        if (configuration.getMeecrowaveProperties() != null && !"meecrowave.properties".equals(configuration.getMeecrowaveProperties())) {
            configuration.loadFrom(configuration.getMeecrowaveProperties());
        }

        if (configuration.isUseLog4j2JulLogManager() && Log4j2s.IS_PRESENT) { // /!\ don't move this line or add anything before without checking log setup
            System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
        }

        if (configuration.isLoggingGlobalSetup() && Log4j2s.IS_PRESENT) {

            setSystemProperty(systemPropsToRestore, "log4j.shutdownHookEnabled", "false");
            setSystemProperty(systemPropsToRestore, "openwebbeans.logging.factory", Log4j2LoggerFactory.class.getName());
            setSystemProperty(systemPropsToRestore, "org.apache.cxf.Logger", Log4j2Logger.class.getName());
            setSystemProperty(systemPropsToRestore, "org.apache.tomcat.Logger", Log4j2Log.class.getName());

            postTask = () -> {
                if (Log4j2s.IS_PRESENT) {
                    new Log4j2Shutdown().shutdown();
                }
                systemPropsToRestore.forEach((key, value) -> {
                    if (value == null) {
                        System.clearProperty(key);
                    } else {
                        System.setProperty(key, value);
                    }
                });
            };
        }

        setupJmx(configuration.isTomcatNoJmx());

        clearCatalinaSystemProperties = System.getProperty("catalina.base") == null && System.getProperty("catalina.home") == null;

        tomcat = new InternalTomcat();

        { // setup
            base = new File(newBaseDir());

            // create the temp dir folder.
            File tempDir;
            if (configuration.getTempDir() == null || configuration.getTempDir().length() == 0) {
                tempDir = createDirectory(base, "temp");
            } else {
                tempDir = new File(configuration.getTempDir());
                if (!tempDir.exists()) {
                    tempDir.mkdirs();
                }
            }

            try {
                workDir = createDirectory(base, "work");
            } catch (final IllegalStateException ise) {
                // in case we could not create that directory we create it in the temp dir folder
                workDir = createDirectory(tempDir, "work");
            }

            synchronize(new File(base, "conf"), configuration.getConf());
        }

        final Properties props = configuration.getProperties();
        Substitutor substitutor = null;
        for (final String s : props.stringPropertyNames()) {
            final String v = props.getProperty(s);
            if (v != null && v.contains("${")) {
                if (substitutor == null) {
                    final Map placeHolders = new HashMap<>();
                    placeHolders.put("meecrowave.embedded.http", Integer.toString(configuration.getHttpPort()));
                    placeHolders.put("meecrowave.embedded.https", Integer.toString(configuration.getHttpsPort()));
                    placeHolders.put("meecrowave.embedded.stop", Integer.toString(configuration.getStopPort()));
                    substitutor = new Substitutor(placeHolders);
                }
                props.put(s, substitutor.replace(v));
            }
        }

        final File conf = new File(base, "conf");

        tomcat.setBaseDir(base.getAbsolutePath());
        tomcat.setHostname(configuration.getHost());

        final boolean initialized;
        if (configuration.getServerXml() != null) {
            final File file = new File(conf, "server.xml");
            if (!file.equals(configuration.getServerXml())) {
                try (final InputStream is = new FileInputStream(configuration.getServerXml());
                     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.isKeepServerXmlAsThis()) {
                configuration.setHttpPort(Integer.parseInt(ports.http()));
                configuration.setStopPort(Integer.parseInt(ports.stop()));
            } else {
                final Map replacements = new HashMap<>();
                replacements.put(ports.http(), String.valueOf(configuration.getHttpPort()));
                replacements.put(ports.https(), String.valueOf(configuration.getHttpsPort()));
                replacements.put(ports.stop(), String.valueOf(configuration.getStopPort()));

                String serverXmlContent;
                try (final InputStream stream = new FileInputStream(file)) {
                    serverXmlContent = IO.toString(stream);
                    for (final Map.Entry 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.getStopPort());
            initialized = false;
        }

        ofNullable(configuration.getSharedLibraries()).map(File::new).filter(File::isDirectory).ifPresent(libRoot -> {
            final Collection 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.getHost());
            tomcat.getEngine().setDefaultHost(configuration.getHost());
            final StandardHost host = new StandardHost();
            host.setName(configuration.getHost());

            try {
                final File webapps = createDirectory(base, "webapps");
                host.setAppBase(webapps.getAbsolutePath());
            } catch (final IllegalStateException ise) {
                // never an issue since the webapps are deployed being put in webapps - so no dynamic folder
                // or through their path - so don't need webapps folder
            }

            host.setUnpackWARs(true); // forced for now cause OWB doesn't support war:file:// urls
            try {
                host.setWorkDir(workDir.getCanonicalPath());
            } catch (final IOException e) {
                host.setWorkDir(workDir.getAbsolutePath());
            }
            tomcat.setHost(host);
        }

        ofNullable(configuration.getTomcatAccessLogPattern())
                .ifPresent(pattern -> tomcat.getHost().getPipeline().addValve(new LoggingAccessLogPattern(pattern)));
        final List valves = buildValves();
        if (!valves.isEmpty()) {
            final Pipeline pipeline = tomcat.getHost().getPipeline();
            valves.forEach(pipeline::addValve);
        }

        if (configuration.getRealm() != null) {
            tomcat.getEngine().setRealm(configuration.getRealm());
        }

        if (tomcat.getRawConnector() == null && !configuration.isSkipHttp()) {
            final Connector connector = createConnector();
            connector.setPort(configuration.getHttpPort());
            if (connector.getProperty("connectionTimeout") == null) {
                connector.setProperty("connectionTimeout", "3000");
            }

            tomcat.getService().addConnector(connector);
            tomcat.setConnector(connector);
        }

        // create https connector
        if (configuration.isSsl()) {
            final Connector httpsConnector = createConnector();
            httpsConnector.setPort(configuration.getHttpsPort());
            httpsConnector.setSecure(true);
            httpsConnector.setScheme("https");
            httpsConnector.setProperty("SSLEnabled", "true");
            if (configuration.getSslProtocol() != null) {
                configuration.getProperties().setProperty("connector.sslhostconfig.sslProtocol", configuration.getSslProtocol());
            }
            if (configuration.getProperties().getProperty("connector.sslhostconfig.hostName") != null) {
                httpsConnector.setProperty("defaultSSLHostConfigName", configuration.getProperties().getProperty("connector.sslhostconfig.hostName"));
            }
            if (configuration.getKeystoreFile() != null) {
                configuration.getProperties().setProperty("connector.sslhostconfig.certificateKeystoreFile", configuration.getKeystoreFile());
            }
            if (configuration.getKeystorePass() != null) {
                configuration.getProperties().setProperty("connector.sslhostconfig.certificateKeystorePassword", configuration.getKeystorePass());
            }
            configuration.getProperties().setProperty("connector.sslhostconfig.certificateKeystoreType", configuration.getKeystoreType());
            if (configuration.getClientAuth() != null) {
                httpsConnector.setProperty("clientAuth", configuration.getClientAuth());
            }

            if (configuration.getKeyAlias() != null) {
                configuration.getProperties().setProperty("connector.sslhostconfig.certificateKeyAlias", configuration.getKeyAlias());
            }
            if (configuration.isHttp2()) {
                httpsConnector.addUpgradeProtocol(new Http2Protocol());
            }
            final List buildSslHostConfig = buildSslHostConfig();
            if (!buildSslHostConfig.isEmpty()) {
                createDirectory(base, "conf");
            }
            buildSslHostConfig.forEach(sslHostConf -> {
                if (isCertificateFromClasspath(sslHostConf.getCertificateKeystoreFile())) {
                    copyCertificateToConfDir(sslHostConf.getCertificateKeystoreFile());
                    sslHostConf.setCertificateKeystoreFile(base.getAbsolutePath() + "/conf/" + sslHostConf.getCertificateKeystoreFile());
                }
                if (isCertificateFromClasspath(sslHostConf.getCertificateKeyFile())) {
                    copyCertificateToConfDir(sslHostConf.getCertificateKeyFile());
                    sslHostConf.setCertificateKeyFile(base.getAbsolutePath() + "/conf/" + sslHostConf.getCertificateKeyFile());
                    copyCertificateToConfDir(sslHostConf.getCertificateFile());
                    sslHostConf.setCertificateFile(base.getAbsolutePath() + "/conf/" + sslHostConf.getCertificateFile());
                }
                if (isCertificateFromClasspath(sslHostConf.getTruststoreFile())) {
                    copyCertificateToConfDir(sslHostConf.getTruststoreFile());
                    sslHostConf.setTruststoreFile(base.getAbsolutePath() + "/conf/" + sslHostConf.getTruststoreFile());
                }
                if (isCertificateFromClasspath(sslHostConf.getCertificateChainFile())) {
                    copyCertificateToConfDir(sslHostConf.getCertificateChainFile());
                    sslHostConf.setCertificateChainFile(base.getAbsolutePath() + "/conf/" + sslHostConf.getCertificateChainFile());
                }
            });

            buildSslHostConfig.forEach(httpsConnector::addSslHostConfig);

            if (configuration.getDefaultSSLHostConfigName() != null) {
                httpsConnector.setProperty("defaultSSLHostConfigName", configuration.getDefaultSSLHostConfigName());
            }
            tomcat.getService().addConnector(httpsConnector);
            if (configuration.isSkipHttp()) {
                tomcat.setConnector(httpsConnector);
            }
        }

        for (final Connector c : configuration.getConnectors()) {
            tomcat.getService().addConnector(c);
        }
        if (!configuration.isSkipHttp() && !configuration.isSsl() && !configuration.getConnectors().isEmpty()) {
            tomcat.setConnector(configuration.getConnectors().iterator().next());
        }

        if (configuration.getUsers() != null) {
            for (final Map.Entry user : configuration.getUsers().entrySet()) {
                tomcat.addUser(user.getKey(), user.getValue());
            }
        }
        if (configuration.getRoles() != null) {
            for (final Map.Entry user : configuration.getRoles().entrySet()) {
                for (final String role : user.getValue().split(" *, *")) {
                    tomcat.addRole(user.getKey(), role);
                }
            }
        }

        StreamSupport.stream(ServiceLoader.load(Meecrowave.InstanceCustomizer.class).spliterator(), false)
                .peek(i -> {
                    if (MeecrowaveAwareInstanceCustomizer.class.isInstance(i)) {
                        MeecrowaveAwareInstanceCustomizer.class.cast(i).setMeecrowave(this);
                    }
                })
                .sorted(Priotities::sortByPriority)
                .forEach(c -> c.accept(tomcat));
        configuration.getInstanceCustomizers().forEach(c -> c.accept(tomcat));

        StreamSupport.stream(ServiceLoader.load(Meecrowave.ContextCustomizer.class).spliterator(), false)
                .peek(i -> {
                    if (MeecrowaveAwareContextCustomizer.class.isInstance(i)) {
                        MeecrowaveAwareContextCustomizer.class.cast(i).setMeecrowave(this);
                    }
                })
                .sorted(Priotities::sortByPriority)
                .forEach(configuration::addGlobalContextCustomizer);

        beforeStart();


        if (configuration.isInitializeClientBus() && Cxfs.IS_PRESENT && !Cxfs.hasDefaultBus()) {
            clientBus = new ConfigurableBus();
            clientBus.initProviders(configuration,
                    ofNullable(Thread.currentThread().getContextClassLoader()).orElseGet(ClassLoader::getSystemClassLoader));
            clientBus.addClientLifecycleListener();
        }

        try {
            if (!initialized) {
                tomcat.init();
            }

            tomcat.getHost().addLifecycleListener(event -> {
                if (!Host.class.isInstance(event.getSource())) {
                    return;
                }
                broadcastHostEvent(event.getType(), Host.class.cast(event.getSource()));
            });

            tomcat.start();
        } catch (final LifecycleException e) {
            throw new IllegalStateException(e);
        }
        ofNullable(configuration.getPidFile()).ifPresent(pidFile -> {
            if (pidFile.getParentFile() != null && !pidFile.getParentFile().isDirectory() && !pidFile.getParentFile().mkdirs()) {
                throw new IllegalArgumentException("Can't create " + pidFile);
            }
            final String pid = ManagementFactory.getRuntimeMXBean().getName();
            final int at = pid.indexOf('@');
            try (final Writer w = new FileWriter(pidFile)) {
                w.write(at > 0 ? pid.substring(0, at) : pid);
            } catch (final IOException e) {
                throw new IllegalStateException("Can't write the pid in " + pid, 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 boolean isCertificateFromClasspath(final String certificate) {
        final BiPredicate equals = System.getProperty("os.name", "ignore").toLowerCase(ROOT).contains("win") ?
                String::equalsIgnoreCase : String::equals;
        return certificate != null && !(new File(certificate).exists())
                && !equals.test(
                Paths.get(System.getProperty("user.home")).resolve(".keystore").toAbsolutePath().normalize().toString(),
                Paths.get(certificate).toAbsolutePath().normalize().toString());
    }

    private void copyCertificateToConfDir(String certificate) {
        InputStream resourceAsStream = null;
        try {
            final Path dstFile = Paths.get(base.getAbsolutePath() + "/conf/" + certificate);
            resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(certificate);
            if (resourceAsStream == null) {
                resourceAsStream = new FileInputStream(new File((this.getClass().getResource("/").toString().replaceAll("file:", "") + "/" + certificate)));
            }
            Files.copy(resourceAsStream, dstFile, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        } finally {
            if (resourceAsStream != null) {
                try {
                    resourceAsStream.close();
                } catch (IOException e) {
                    throw new IllegalStateException(e);
                }
            }
        }
    }

    public ConfigurableBus getClientBus() {
        return clientBus;
    }

    /**
     * Store away the current system property for restoring it later
     * during shutdown.
     *
     * @param backupPropertyMap a Map to store away the previous value before setting the newValue
     * @param propertyKey
     * @param newValue
     */
    private void setSystemProperty(Map backupPropertyMap, String propertyKey, String newValue) {
        backupPropertyMap.put(propertyKey, System.getProperty(propertyKey));

        System.setProperty(propertyKey, newValue);
    }

    private void broadcastHostEvent(final String event, final Host host) {
        switch (event) {
            case Lifecycle.AFTER_START_EVENT: {
                final Connector connector = findFirstConnector();
                findContexts(host).forEach(ctx -> fire(new StartListening(connector, host, ctx), ctx.getLoader().getClassLoader()));
                break;
            }
            case Lifecycle.BEFORE_STOP_EVENT: {
                final Connector connector = findFirstConnector();
                findContexts(host).forEach(ctx -> fire(new StopListening(connector, host, ctx), ctx.getLoader().getClassLoader()));
                break;
            }
            default:
        }
    }

    private Connector findFirstConnector() {
        return Stream.of(tomcat.getServer().findServices())
                .flatMap(s -> Stream.of(s.findConnectors()))
                .findFirst()
                .orElse(null);
    }

    private Stream findContexts(final Host host) {
        return Stream.of(host.findChildren())
                .filter(Context.class::isInstance)
                .map(Context.class::cast)
                .filter(ctx -> ctx.getState().isAvailable());
    }

    private  void fire(final T event, final ClassLoader classLoader) {
        final Thread thread = Thread.currentThread();
        final ClassLoader loader = thread.getContextClassLoader();
        thread.setContextClassLoader(classLoader);
        try {
            WebBeansContext.currentInstance()
                    .getBeanManagerImpl()
                    .getEvent()
                    .select(Class.class.cast(event.getClass()))
                    .fire(event);
        } finally {
            thread.setContextClassLoader(loader);
        }
    }

    private void setupJmx(final boolean skip) {
        if (skip) {
            Registry.disableRegistry();
        }
    }

    /**
     * Syntax uses:
     * 
     * valves.myValve1._className = org.apache.meecrowave.tomcat.LoggingAccessLogPattern
     * valves.myValve1._order = 0
     * 

* valves.myValve1._className = SSOVa * valves.myValve1._order = 1 * valves.myValve1.showReportInfo = false * * * @return the list of valve from the properties. */ private List buildValves() { final List valves = new ArrayList<>(); configuration.getProperties().stringPropertyNames().stream() .filter(key -> key.startsWith("valves.") && key.endsWith("._className")) .sorted(comparing(key -> Integer.parseInt(configuration.getProperties() .getProperty(key.replaceFirst("\\._className$", "._order"), "0")))) .map(key -> key.split("\\.")) .filter(parts -> parts.length == 3) .forEach(key -> { final String prefix = key[0] + '.' + key[1] + '.'; final ObjectRecipe recipe = newRecipe(configuration.getProperties().getProperty(prefix + key[2])); configuration.getProperties().stringPropertyNames().stream() .filter(it -> it.startsWith(prefix) && !it.endsWith("._order") && !it.endsWith("._className")) .forEach(propKey -> { final String value = configuration.getProperties().getProperty(propKey); recipe.setProperty(propKey.substring(prefix.length()), value); }); valves.add(Valve.class.cast(recipe.create(Thread.currentThread().getContextClassLoader()))); }); return valves; } private List buildSslHostConfig() { final List sslHostConfigs = new ArrayList<>(); // Configures default SSLHostConfig final ObjectRecipe defaultSslHostConfig = newRecipe(SSLHostConfig.class.getName()); for (final String key : configuration.getProperties().stringPropertyNames()) { if (key.startsWith("connector.sslhostconfig.") && key.split("\\.").length == 3) { final String substring = key.substring("connector.sslhostconfig.".length()); defaultSslHostConfig.setProperty(substring, configuration.getProperties().getProperty(key)); } } if (!defaultSslHostConfig.getProperties().isEmpty()) { sslHostConfigs.add(SSLHostConfig.class.cast(defaultSslHostConfig.create())); } // Allows to add N Multiple SSLHostConfig elements not including the default one. final Collection itemNumbers = configuration.getProperties().stringPropertyNames() .stream() .filter(key -> (key.startsWith("connector.sslhostconfig.") && key.split("\\.").length == 4)) .map(key -> Integer.parseInt(key.split("\\.")[2])) .collect(toSet()); itemNumbers.stream().sorted().forEach(itemNumber -> { final ObjectRecipe recipe = newRecipe(SSLHostConfig.class.getName()); final String prefix = "connector.sslhostconfig." + itemNumber + '.'; configuration.getProperties().stringPropertyNames().stream() .filter(k -> k.startsWith(prefix)) .forEach(key -> { final String keyName = key.split("\\.")[3]; recipe.setProperty(keyName, configuration.getProperties().getProperty(key)); }); if (!recipe.getProperties().isEmpty()) { final SSLHostConfig sslHostConfig = SSLHostConfig.class.cast(recipe.create()); sslHostConfigs.add(sslHostConfig); new LogFacade(Meecrowave.class.getName()) .info("Created SSLHostConfig #" + itemNumber + " (" + sslHostConfig.getHostName() + ")"); } }); return sslHostConfigs; } protected void beforeStart() { // no-op } protected void beforeStop() { // no-op } public AutoCloseable inject(final T instance) { final BeanManager bm = CDI.current().getBeanManager(); final AnnotatedType annotatedType = bm.createAnnotatedType(instance.getClass()); final InjectionTarget injectionTarget = bm.createInjectionTarget(annotatedType); final CreationalContext creationalContext = bm.createCreationalContext(null); injectionTarget.inject(instance, creationalContext); return creationalContext::release; } @Override public synchronized 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 { Cxfs.resetDefaultBusIfEquals(clientBus); // after if runnables or listeners trigger CXF tomcat = null; // ensure we can call close() N times and not have side effects contexts.clear(); 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 { if (deleteBase && base != null) { IO.delete(base); } if (ownedTempDir != null) { IO.delete(ownedTempDir); } } catch (final IllegalArgumentException /*does not exist from the hook*/ e) { // no-op } finally { base = null; // not very important if we can't delete it since next restart will write another value normally ofNullable(configuration.getPidFile()).ifPresent(File::delete); } } } } protected Connector createConnector() { final Connector connector; final Properties properties = configuration.getProperties(); if (properties != null) { final Map attributes = new HashMap<>(); final ObjectRecipe recipe = newRecipe(Connector.class.getName()); 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 attr : attributes.entrySet()) { connector.setProperty(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 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 urls = new ResourceFinder("").getResourcesMap(resourceBase); for (final Map.Entry u : urls.entrySet()) { try (final InputStream is = u.getValue().openStream()) { final File to = new File(base, u.getKey()); final File parentFile = to.getParentFile(); createDirectory(parentFile.getParentFile(), parentFile.getName()); 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() { deleteBase = false; String dir = configuration.getDir(); if (dir != null) { final File dirFile = new File(dir); if (dirFile.exists()) { if (base != null && base.exists() && configuration.isDeleteBaseOnStartup()) { IO.delete(base); } return dir; } IO.mkdirs(dirFile); return dirFile.getAbsolutePath(); } final String base = System.getProperty("meecrowave.base"); if (base != null && new File(base).exists()) { return new File(base).getAbsolutePath(); } deleteBase = true; final List lookupPaths = new ArrayList<>(); lookupPaths.add("target"); lookupPaths.add("build"); final File file = lookupPaths.stream() .map(File::new) .filter(File::isDirectory) .findFirst() .map(file1 -> new File(file1, "meecrowave-" + System.nanoTime())).orElse(ownedTempDir); IO.mkdirs(file); return file.getAbsolutePath(); } public void await() { tomcat.getServer().await(); } private static ObjectRecipe newRecipe(final String clazz) { final ObjectRecipe recipe = new ObjectRecipe(clazz); recipe.allow(Option.FIELD_INJECTION); recipe.allow(Option.PRIVATE_PROPERTIES); return recipe; } // this class holds all the built-in config, // extension can use extensions feature (see cli.html) which is basically the same kind of bean // accessible through builder.getExtension(type) builder being accessible through the meecrowave.configuration // attribute of the ServletContext. public static class Builder extends Configuration /* inheritance for backward compatibility */ { // IMPORTANT: this must stay without any field, use parent class to add config please // this only holds builder methods public Builder() { // no-op } // mainly for backward compat public Builder(final Configuration configuration) { super(configuration); } @Override public Builder loadFrom(String resource) { super.loadFrom(resource); return this; } public Builder excludePackages(final String packages) { this.setScanningPackageExcludes(packages); return this; } /** * Only scan the very packages given (startsWith). * This will exclude all other packages from bean scanning */ public Builder includePackages(final String packages) { this.setScanningPackageIncludes(packages); this.setScanningPackageExcludes("*"); return this; } /** * Scan the very packages given (startsWith) in addition to the default rules. */ public Builder withPackages(final String packages) { this.setScanningPackageIncludes(packages); return this; } public Builder sharedLibraries(final String sharedLibraries) { setSharedLibraries(sharedLibraries); return this; } public Builder securityConstraints(final Meecrowave.SecurityConstaintBuilder securityConstraint) { if (getSecurityConstraints() == null) { setSecurityConstraints(new ArrayList<>()); } getSecurityConstraints().add(securityConstraint); return this; } public Builder randomHttpPort() { try (final ServerSocket serverSocket = new ServerSocket(0)) { setHttpPort(serverSocket.getLocalPort()); } catch (final IOException e) { throw new IllegalStateException(e); } return this; } public Builder randomHttpsPort() { try (final ServerSocket serverSocket = new ServerSocket(0)) { setHttpsPort(serverSocket.getLocalPort()); } catch (final IOException e) { throw new IllegalStateException(e); } return this; } public Builder property(final String key, final String value) { getProperties().setProperty(key, value); return this; } public Builder user(final String name, final String pwd) { if (getUsers() == null) { setUsers(new HashMap<>()); } getUsers().put(name, pwd); return this; } public Builder role(final String user, final String roles) { if (getRoles() == null) { setRoles(new HashMap<>()); } getRoles().put(user, roles); return this; } public Builder cxfServletParam(final String key, final String value) { if (getCxfServletParams() == null) { setCxfServletParams(new HashMap<>()); } getCxfServletParams().put(key, value); return this; } @Deprecated // withers are deprecated, we prefer the no fluent API public Builder withJsonpBufferStrategy(final String jsonpBufferStrategy) { setJsonpBufferStrategy(jsonpBufferStrategy); return this; } public Builder noShutdownHook() { setUseShutdownHook(false); return this; } public Builder instanceCustomizer(final Consumer customizer) { addInstanceCustomizer(customizer); return this; } public Builder pidFile(final Path pidFile) { setPidFile(pidFile.toFile()); return this; } public Builder watcherBouncing(final int watcherBouncing) { setWatcherBouncing(watcherBouncing); return this; } public Builder httpPort(final int httpPort) { if (httpPort <= 0) { randomHttpPort(); } else { setHttpPort(httpPort); } return this; } public Builder httpsPort(final int httpsPort) { if (httpsPort <= 0) { randomHttpsPort(); } else { setHttpsPort(httpsPort); } return this; } public Builder stopPort(final int stopPort) { setStopPort(stopPort); return this; } public Builder host(final String host) { setHost(host); return this; } public Builder dir(final String dir) { setDir(dir); return this; } public Builder serverXml(final Path serverXml) { setServerXml(serverXml.toFile()); return this; } public Builder keepServerXmlAsThis(final boolean keepServerXmlAsThis) { setKeepServerXmlAsThis(keepServerXmlAsThis); return this; } public Builder properties(final Properties properties) { getProperties().putAll(properties); return this; } public Builder quickSession(final boolean quickSession) { setQuickSession(quickSession); return this; } public Builder skipHttp(final boolean skipHttp) { setSkipHttp(skipHttp); return this; } public Builder ssl(final boolean ssl) { setSsl(ssl); return this; } public Builder keystoreFile(final String keystoreFile) { setKeystoreFile(keystoreFile); return this; } public Builder keystorePass(final String keystorePass) { setKeystorePass(keystorePass); return this; } public Builder keystoreType(final String keystoreType) { setKeystoreType(keystoreType); return this; } public Builder clientAuth(final String clientAuth) { setClientAuth(clientAuth); return this; } public Builder keyAlias(final String keyAlias) { setKeyAlias(keyAlias); return this; } public Builder sslProtocol(final String sslProtocol) { setSslProtocol(sslProtocol); return this; } public Builder webXml(final String webXml) { setWebXml(webXml); return this; } public Builder loginConfig(final LoginConfigBuilder loginConfig) { setLoginConfig(loginConfig); return this; } public Builder securityConstraint(final SecurityConstaintBuilder securityConstraint) { if (getSecurityConstraints() == null) { setSecurityConstraints(new ArrayList<>()); } getSecurityConstraints().add(securityConstraint); return this; } public Builder realm(final Realm realm) { setRealm(realm); return this; } public Builder users(final Map users) { if (getUsers() == null) { setUsers(new HashMap<>()); } getUsers().putAll(users); return this; } public Builder roles(final Map roles) { if (getRoles() == null) { setRoles(new HashMap<>()); } getRoles().putAll(roles); return this; } public Builder http2(final boolean http2) { setHttp2(http2); return this; } public Builder connector(final Connector connector) { getConnectors().add(connector); return this; } public Builder tempDir(final String tempDir) { setTempDir(tempDir); return this; } public Builder webResourceCached(final boolean webResourceCached) { setWebResourceCached(webResourceCached); return this; } public Builder conf(final String conf) { setConf(conf); return this; } public Builder deleteBaseOnStartup(final boolean deleteBaseOnStartup) { setDeleteBaseOnStartup(deleteBaseOnStartup); return this; } public Builder jaxrsMapping(final String jaxrsMapping) { setJaxrsMapping(jaxrsMapping); return this; } public Builder cdiConversation(final boolean cdiConversation) { setCdiConversation(cdiConversation); return this; } public Builder jaxrsProviderSetup(final boolean jaxrsProviderSetup) { setJaxrsProviderSetup(jaxrsProviderSetup); return this; } public Builder jaxrsDefaultProviders(final String jaxrsDefaultProviders) { setJaxrsDefaultProviders(jaxrsDefaultProviders); return this; } public Builder jaxrsAutoActivateBeanValidation(final boolean jaxrsAutoActivateBeanValidation) { setJaxrsAutoActivateBeanValidation(jaxrsAutoActivateBeanValidation); return this; } public Builder jaxrsLogProviders(final boolean jaxrsLogProviders) { setJaxrsLogProviders(jaxrsLogProviders); return this; } public Builder jsonpBufferStrategy(final String jsonpBufferStrategy) { setJsonpBufferStrategy(jsonpBufferStrategy); return this; } public Builder jsonpMaxStringLen(final int jsonpMaxStringLen) { setJsonpMaxStringLen(jsonpMaxStringLen); return this; } public Builder jsonpMaxReadBufferLen(final int jsonpMaxReadBufferLen) { setJsonpMaxReadBufferLen(jsonpMaxReadBufferLen); return this; } public Builder jsonpMaxWriteBufferLen(final int jsonpMaxWriteBufferLen) { setJsonpMaxWriteBufferLen(jsonpMaxWriteBufferLen); return this; } public Builder jsonpSupportsComment(final boolean jsonpSupportsComment) { setJsonpSupportsComment(jsonpSupportsComment); return this; } public Builder jsonpPrettify(final boolean jsonpPrettify) { setJsonpPrettify(jsonpPrettify); return this; } public Builder jsonbEncoding(final String jsonbEncoding) { setJsonbEncoding(jsonbEncoding); return this; } public Builder jsonbNulls(final boolean jsonbNulls) { setJsonbNulls(jsonbNulls); return this; } public Builder jsonbIJson(final boolean jsonbIJson) { setJsonbIJson(jsonbIJson); return this; } public Builder jsonbPrettify(final boolean jsonbPrettify) { setJsonbPrettify(jsonbPrettify); return this; } public Builder jsonbBinaryStrategy(final String jsonbBinaryStrategy) { setJsonbBinaryStrategy(jsonbBinaryStrategy); return this; } public Builder jsonbNamingStrategy(final String jsonbNamingStrategy) { setJsonbNamingStrategy(jsonbNamingStrategy); return this; } public Builder jsonbOrderStrategy(final String jsonbOrderStrategy) { setJsonbOrderStrategy(jsonbOrderStrategy); return this; } public Builder loggingGlobalSetup(final boolean loggingGlobalSetup) { setLoggingGlobalSetup(loggingGlobalSetup); return this; } public Builder tomcatScanning(final boolean tomcatScanning) { setTomcatScanning(tomcatScanning); return this; } public Builder tomcatAutoSetup(final boolean tomcatAutoSetup) { setTomcatAutoSetup(tomcatAutoSetup); return this; } public Builder tomcatJspDevelopment(final boolean tomcatJspDevelopment) { setTomcatJspDevelopment(tomcatJspDevelopment); return this; } public Builder useShutdownHook(final boolean useShutdownHook) { setUseShutdownHook(useShutdownHook); return this; } public Builder tomcatFilter(final String tomcatFilter) { setTomcatFilter(tomcatFilter); return this; } public Builder scanningIncludes(final String scanningIncludes) { setScanningIncludes(scanningIncludes); return this; } public Builder scanningExcludes(final String scanningExcludes) { setScanningExcludes(scanningExcludes); return this; } public Builder scanningPackageIncludes(final String scanningPackageIncludes) { setScanningPackageIncludes(scanningPackageIncludes); return this; } public Builder scanningPackageIncludes(final Collection scanningPackageIncludes) { setScanningPackageIncludes(String.join(",", scanningPackageIncludes)); return this; } public Builder scanningPackageExcludes(final String scanningPackageExcludes) { setScanningPackageExcludes(scanningPackageExcludes); return this; } public Builder scanningPackageExcludes(final Collection scanningPackageExcludes) { setScanningPackageExcludes(String.join(",", scanningPackageExcludes)); return this; } public Builder webSessionTimeout(final int webSessionTimeout) { setWebSessionTimeout(webSessionTimeout); return this; } public Builder webSessionCookieConfig(final String webSessionCookieConfig) { setWebSessionCookieConfig(webSessionCookieConfig); return this; } public Builder useTomcatDefaults(final boolean useTomcatDefaults) { setUseTomcatDefaults(useTomcatDefaults); return this; } public Builder tomcatWrapLoader(final boolean tomcatWrapLoader) { setTomcatWrapLoader(tomcatWrapLoader); return this; } public Builder tomcatNoJmx(final boolean tomcatNoJmx) { setTomcatNoJmx(tomcatNoJmx); return this; } public Builder useLog4j2JulLogManager(final boolean useLog4j2JulLogManager) { setUseLog4j2JulLogManager(useLog4j2JulLogManager); return this; } public Builder injectServletContainerInitializer(final boolean injectServletContainerInitializer) { setInjectServletContainerInitializer(injectServletContainerInitializer); return this; } public Builder tomcatAccessLogPattern(final String tomcatAccessLogPattern) { setTomcatAccessLogPattern(tomcatAccessLogPattern); return this; } public Builder meecrowaveProperties(final String meecrowaveProperties) { setMeecrowaveProperties(meecrowaveProperties); return this; } public Builder jaxwsSupportIfAvailable(final boolean jaxwsSupportIfAvailable) { setJaxwsSupportIfAvailable(jaxwsSupportIfAvailable); return this; } public Builder defaultSSLHostConfigName(final String defaultSSLHostConfigName) { setDefaultSSLHostConfigName(defaultSSLHostConfigName); return this; } public Builder initializeClientBus(final boolean initializeClientBus) { setInitializeClientBus(initializeClientBus); return this; } public Builder instanceCustomizers(final Consumer instanceCustomizer) { addInstanceCustomizer(instanceCustomizer); return this; } public Builder initializer(final ServletContainerInitializer initializer) { addServletContextInitializer(initializer); return this; } public Builder antiResourceLocking(final boolean antiResourceLocking) { setAntiResourceLocking(antiResourceLocking); return this; } public Builder contextConfigurer(final Consumer contextConfigurers) { addGlobalContextCustomizer(contextConfigurers); return this; } } public static class ValueTransformers implements Function { private final Map transformers = new HashMap<>(); @Override public String apply(final String value) { if (value.startsWith("decode:")) { if (transformers.isEmpty()) { // lazy loading transformers.put("Static3DES", new ValueTransformer() { // compatibility with tomee private final SecretKeySpec key = new SecretKeySpec(new byte[]{ (byte) 0x76, (byte) 0x6F, (byte) 0xBA, (byte) 0x39, (byte) 0x31, (byte) 0x2F, (byte) 0x0D, (byte) 0x4A, (byte) 0xA3, (byte) 0x90, (byte) 0x55, (byte) 0xFE, (byte) 0x55, (byte) 0x65, (byte) 0x61, (byte) 0x13, (byte) 0x34, (byte) 0x82, (byte) 0x12, (byte) 0x17, (byte) 0xAC, (byte) 0x77, (byte) 0x39, (byte) 0x19}, "DESede"); @Override public String name() { return "Static3DES"; } @Override public String apply(final String encodedPassword) { Objects.requireNonNull(encodedPassword, "value can't be null"); try { final byte[] cipherText = Base64.getDecoder().decode(encodedPassword); final Cipher cipher = Cipher.getInstance("DESede"); cipher.init(Cipher.DECRYPT_MODE, key); return new String(cipher.doFinal(cipherText), StandardCharsets.UTF_8); } catch (final Exception e) { throw new IllegalArgumentException(e); } } }); for (final ValueTransformer t : ServiceLoader.load(ValueTransformer.class)) { transformers.put(t.name(), t); } } final String substring = value.substring("decode:".length()); final int sep = substring.indexOf(':'); if (sep < 0) { throw new IllegalArgumentException("No transformer algorithm for " + value); } final String algo = substring.substring(0, sep); return Objects.requireNonNull(transformers.get(algo), "No ValueTransformer for value '" + value + "'").apply(substring.substring(sep + 1)); } return value; } } 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 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 values = new TreeMap(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 consumer; private final Consumer redeployCallback; public DeploymentMeta(final String context, final File docBase, final Consumer consumer, final Consumer redeployCallback) { this.context = context; this.docBase = docBase; this.consumer = consumer; this.redeployCallback = redeployCallback; } } // just to type it and allow some extensions to use a ServiceLoader public interface ConfigurationCustomizer extends Consumer { } /** * SPI to customize Tomcat instance. They are sorted by Priority, default being 0. */ public interface InstanceCustomizer extends Consumer { } /** * SPI to customize context instances. They are sorted by Priority, default being 0. */ public interface ContextCustomizer extends Consumer { } public interface MeecrowaveAwareContextCustomizer extends ContextCustomizer { void setMeecrowave(Meecrowave meecrowave); } // since it is too early to have CDI and lookup the instance we must set it manually public interface MeecrowaveAwareInstanceCustomizer extends InstanceCustomizer { void setMeecrowave(Meecrowave meecrowave); } private static final class MeecrowaveContainerLoader extends URLClassLoader { private MeecrowaveContainerLoader(final URL[] urls, final ClassLoader parent) { super(urls, parent); } } }