
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.FileNotFoundException;
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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeMap;
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.Manager;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Realm;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
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.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.text.StrLookup;
import org.apache.commons.text.StrSubstitutor;
import org.apache.coyote.http2.Http2Protocol;
import org.apache.cxf.BusFactory;
import org.apache.johnzon.core.BufferStrategy;
import org.apache.meecrowave.api.StartListening;
import org.apache.meecrowave.api.StopListening;
import org.apache.meecrowave.cxf.ConfigurableBus;
import org.apache.meecrowave.cxf.CxfCdiAutoSetup;
import org.apache.meecrowave.io.IO;
import org.apache.meecrowave.logging.jul.Log4j2Logger;
import org.apache.meecrowave.logging.log4j2.Log4j2Shutdown;
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.service.ValueTransformer;
import org.apache.meecrowave.tomcat.CDIInstanceManager;
import org.apache.meecrowave.tomcat.LoggingAccessLogPattern;
import org.apache.meecrowave.tomcat.NoDescriptorRegistry;
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 Builder configuration;
protected ConfigurableBus clientBus;
protected File base;
protected final File ownedTempDir;
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 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 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)));
}
// shortcut
public Meecrowave deployClasspath() {
return deployClasspath("");
}
// shortcut
public Meecrowave bake(final Consumer customizer) {
start();
return deployClasspath(new DeploymentMeta("", null, customizer));
}
// 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
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.tomcatFilter).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) -> {
new OWBAutoSetup().onStartup(c, ctx1);
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));
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.webResourceCached);
break;
case Lifecycle.BEFORE_INIT_EVENT:
ctx.getServletContext().setAttribute("meecrowave.configuration", configuration);
ctx.getServletContext().setAttribute("meecrowave.instance", Meecrowave.this);
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(meecrowaveInitializer, emptySet());
if (configuration.isUseTomcatDefaults()) {
ctx.setSessionTimeout(configuration.getWebSessionTimeout() != null ? configuration.getWebSessionTimeout() : 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);
}
} else if (configuration.getWebSessionTimeout() != null) {
ctx.setSessionTimeout(configuration.getWebSessionTimeout());
}
ofNullable(meta.consumer).ifPresent(c -> c.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()) { // /!\ 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.loggingGlobalSetup) {
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 = () -> {
new Log4j2Shutdown().shutodwn();
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;
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 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 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 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 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);
}
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.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.setScheme("https");
httpsConnector.setProperty("SSLEnabled", "true");
if (configuration.sslProtocol != null) {
configuration.property("connector.sslhostconfig.sslProtocol", configuration.sslProtocol);
}
if (configuration.properties.getProperty("connector.sslhostconfig.hostName") != null) {
httpsConnector.setAttribute("defaultSSLHostConfigName", configuration.properties.getProperty("connector.sslhostconfig.hostName"));
}
if (configuration.keystoreFile != null) {
configuration.property("connector.sslhostconfig.certificateKeystoreFile", configuration.keystoreFile);
}
if (configuration.keystorePass != null) {
configuration.property("connector.sslhostconfig.certificateKeystorePassword", configuration.keystorePass);
}
configuration.property("connector.sslhostconfig.certificateKeystoreType", configuration.keystoreType);
if (configuration.clientAuth != null) {
httpsConnector.setAttribute("clientAuth", configuration.clientAuth);
}
if (configuration.keyAlias != null) {
configuration.property("connector.sslhostconfig.certificateKeyAlias", configuration.keyAlias);
}
if (configuration.http2) {
httpsConnector.addUpgradeProtocol(new Http2Protocol());
}
final List buildSslHostConfig = buildSslHostConfig();
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.defaultSSLHostConfigName != null) {
httpsConnector.setAttribute("defaultSSLHostConfigName", configuration.defaultSSLHostConfigName);
}
tomcat.getService().addConnector(httpsConnector);
if (configuration.skipHttp) {
tomcat.setConnector(httpsConnector);
}
}
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 user : configuration.users.entrySet()) {
tomcat.addUser(user.getKey(), user.getValue());
}
}
if (configuration.roles != null) {
for (final Map.Entry user : configuration.roles.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);
}
})
.forEach(c -> c.accept(tomcat));
configuration.instanceCustomizers.forEach(c -> c.accept(tomcat));
beforeStart();
if (configuration.initializeClientBus && BusFactory.getDefaultBus(false) == null) {
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);
}
}
}
}
/**
* 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) {
try {
final Field registry = Registry.class.getDeclaredField("registry");
registry.setAccessible(true);
registry.set(null, skip ? new NoDescriptorRegistry() : new Registry());
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}
/**
* 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.properties.stringPropertyNames().stream()
.filter(key -> key.startsWith("valves.") && key.endsWith("._className"))
.sorted(comparing(key -> Integer.parseInt(configuration.properties
.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.properties.getProperty(prefix + key[2]));
configuration.properties.stringPropertyNames().stream()
.filter(it -> it.startsWith(prefix) && !it.endsWith("._order") && !it.endsWith("._className"))
.forEach(propKey -> {
final String value = configuration.properties.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.properties.stringPropertyNames()) {
if (key.startsWith("connector.sslhostconfig.") && key.split("\\.").length == 3) {
final String substring = key.substring("connector.sslhostconfig.".length());
defaultSslHostConfig.setProperty(substring, configuration.properties.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.properties.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.properties.stringPropertyNames().stream()
.filter(k -> k.startsWith(prefix))
.forEach(key -> {
final String keyName = key.split("\\.")[3];
recipe.setProperty(keyName, configuration.properties.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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy