blob: 82c65265a8630db68a20a7df8edcf23da023f884 [file] [log] [blame]
/*
* 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.tomee.gradle.embedded;
import org.apache.tomee.gradle.embedded.classloader.FilterGradleClassLoader;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.UnknownConfigurationException;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.gradle.util.GFileUtils;
import java.io.File;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
public class TomEEEmbeddedTask extends DefaultTask {
@Optional
@Input
private int httpPort = 8080;
@Optional
@Input
private int httpsPort = 8443;
@Optional
@Input
private int ajpPort = 8009;
@Optional
@Input
private int stopPort = 8005;
@Optional
@Input
private String host = "localhost";
@Optional
@Input
private String keystoreFile;
@Optional
@Input
private String keystorePass;
@Optional
@Input
private String keystoreType = "JKS";
@Optional
@Input
private String clientAuth;
@Optional
@Input
private String keyAlias;
@Optional
@Input
private String sslProtocol;
@Optional
@Input
private File serverXml;
@Optional
@Input
private boolean singleClassloader = false;
@Optional
@Input
private boolean ssl = false;
@Optional
@Input
private boolean withEjbRemote = false;
@Optional
@Input
private boolean quickSession = true;
@Optional
@Input
private boolean skipHttp = false;
@Optional
@Input
private Collection<String> applicationScopes = new HashSet<>(asList("compile", "runtime"));
@Optional
@Input
private Collection<String> classloaderFilteredPackages;
@Optional
@Input
private Collection<String> customWebResources;
@Optional
@Input
private boolean webResourceCached = true;
@Optional
@Input
private String context = null;
@Optional
@Input
private Map<String, String> containerProperties;
@Optional
@Input
private boolean keepServerXmlAsThis = false;
@Optional
@Input
private Map<String, String> users;
@Optional
@Input
private Map<String, String> roles;
@Optional
@Input
private boolean forceJspDevelopment = true;
@Optional
@Input
private String inlinedServerXml;
@Optional
@Input
private String inlinedTomEEXml;
@Optional
@Input
private File workDir;
@Optional
@Input
private List<File> modules;
@Optional
@Input
private File docBase;
@Optional
@Input
private String dir;
@Optional
@Input
private String conf;
/* TODO if needed
@Parameter //a dvanced config but a simple boolean will be used for defaults (withLiveReload)
private LiveReload liveReload;
@Parameter(property = "tomee-plugin.liveReload", defaultValue = "false")
private boolean withLiveReload;
*/
private Configuration classpath;
@TaskAction
public void runTomEEEmbedded() {
fixConfig();
final Thread thread = Thread.currentThread();
final ClassLoader tccl = thread.getContextClassLoader();
thread.setContextClassLoader(createLoader(tccl));
try {
doRun();
} finally {
thread.setContextClassLoader(tccl);
}
}
private void fixConfig() {
final Project project = getProject();
// defaults
if (classpath == null) {
try {
classpath.add(project.getConfigurations().getByName(TomEEEmbeddedExtension.ALIAS).fileCollection());
} catch (final UnknownConfigurationException uce) {
classpath = project.getConfigurations().getByName(TomEEEmbeddedExtension.NAME);
}
}
if (docBase == null) {
docBase = new File(project.getProjectDir(), "src/main/webapp");
}
if (workDir == null) {
workDir = new File(project.getBuildDir(), "tomee-embedded/work");
}
if (dir == null) {
dir = new File(project.getBuildDir(), "tomee-embedded/run").getAbsolutePath();
}
if (modules == null || modules.isEmpty()) {
final File main = new File(project.getBuildDir(), "classes/main");
if (main.isDirectory()) {
modules = new ArrayList<>(singletonList(main));
}
}
// extension override
for (final String name : asList(TomEEEmbeddedExtension.NAME, TomEEEmbeddedExtension.ALIAS)) {
final TomEEEmbeddedExtension extension = TomEEEmbeddedExtension.class.cast(project.getExtensions().findByName(name));
if (extension != null) {
for (final Field f : TomEEEmbeddedTask.class.getDeclaredFields()) {
if (f.isAnnotationPresent(Input.class)) {
try {
final Field extField = TomEEEmbeddedExtension.class.getDeclaredField(f.getName());
if (!extField.isAccessible()) {
extField.setAccessible(true);
}
final Object val = extField.get(extension);
if (val != null) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
f.set(this, val);
}
} catch (final IllegalAccessException | NoSuchFieldException e) {
getLogger().warn("No field " + f.getName() + " in " + extension, e);
}
}
}
}
}
}
private void doRun() {
final Properties originalSystProp = new Properties();
originalSystProp.putAll(System.getProperties());
final Thread thread = Thread.currentThread();
final ClassLoader loader = thread.getContextClassLoader();
if (inlinedServerXml != null && !inlinedServerXml.trim().isEmpty()) {
if (serverXml != null && serverXml.exists()) {
throw new GradleException("you can't define a server.xml and an inlinedServerXml");
}
try {
GFileUtils.mkdirs(workDir);
serverXml = new File(workDir, "server.xml_dump");
GFileUtils.writeFile(inlinedServerXml, serverXml);
} catch (final Exception e) {
throw new GradleException(e.getMessage(), e);
}
}
final AtomicBoolean running = new AtomicBoolean();
AutoCloseable container;
Thread hook;
try {
final Class<?> containerClass = loader.loadClass("org.apache.tomee.embedded.Container");
final Class<?> configClass = loader.loadClass("org.apache.tomee.embedded.Configuration");
final Class<?> parentLoaderFinderClass = loader.loadClass("org.apache.openejb.core.ParentClassLoaderFinder");
final Class<?> loaderFinderClass = loader.loadClass("org.apache.openejb.core.ProvidedClassLoaderFinder");
final Class<?> systemInstanceClass = loader.loadClass("org.apache.openejb.loader.SystemInstance");
container = AutoCloseable.class.cast(containerClass.newInstance());
final Object config = getConfig(configClass);
containerClass.getMethod("setup", configClass).invoke(container, config);
if (inlinedTomEEXml != null && inlinedTomEEXml.trim().isEmpty()) {
try {
final File conf = new File(dir, "conf");
GFileUtils.mkdirs(conf);
GFileUtils.writeFile(inlinedTomEEXml, new File(conf, "tomee.xml"));
} catch (final Exception e) {
throw new GradleException(e.getMessage(), e);
}
}
final AutoCloseable finalContainer = container;
hook = new Thread() {
@Override
public void run() {
if (running.compareAndSet(true, false)) {
final Thread thread = Thread.currentThread();
final ClassLoader old = thread.getContextClassLoader();
thread.setContextClassLoader(loader);
try {
finalContainer.close();
} catch (final NoClassDefFoundError noClassDefFoundError) {
// debug cause it is too late to shutdown properly so don't pollute logs
getLogger().debug("can't stop TomEE", noClassDefFoundError);
} catch (final Exception e) {
getLogger().error("can't stop TomEE", e);
} finally {
thread.setContextClassLoader(old);
}
}
}
};
hook.setName("TomEE-Embedded-ShutdownHook");
running.set(true); // yes should be done after but we can't help much if we don't do it there for auto shutdown
containerClass.getMethod("start").invoke(container);
// SystemInstance.get().setComponent(ParentClassLoaderFinder.class, new ProvidedClassLoaderFinder(loader));
final Object providedLoaderFinder = loaderFinderClass.getConstructor(ClassLoader.class).newInstance(loader);
final Object systemInstance = systemInstanceClass.getMethod("get").invoke(null);
systemInstanceClass.getMethod("setComponent", Class.class, Object.class)
.invoke(systemInstance, parentLoaderFinderClass, providedLoaderFinder);
Runtime.getRuntime().addShutdownHook(hook);
containerClass.getMethod("deployClasspathAsWebApp", String.class, File.class, boolean.class).invoke(container, context, docBase, singleClassloader);
getLogger().info("TomEE embedded started on " + configClass.getMethod("getHost").invoke(config) + ":" + configClass.getMethod("getHttpPort").invoke(config));
} catch (final Exception e) {
throw new GradleException(e.getMessage(), e);
}
// installLiveReloadEndpointIfNeeded();
try {
String line;
final Scanner scanner = new Scanner(System.in);
while ((line = scanner.nextLine()) != null) {
final String cmd = line.trim().toLowerCase(Locale.ENGLISH);
switch (cmd) {
case "exit":
case "quit":
running.set(false);
Runtime.getRuntime().removeShutdownHook(hook);
container.close();
return;
default:
getLogger().warn("Unknown: '" + cmd + "', use 'exit' or 'quit'");
}
}
} catch (final Exception e) {
Thread.interrupted();
} finally {
thread.setContextClassLoader(loader);
System.setProperties(originalSystProp);
}
}
private Object getConfig(final Class<?> configClass) throws Exception {
final Object config = configClass.newInstance();
for (final Field field : TomEEEmbeddedTask.class.getDeclaredFields()) {
try {
final Field configField = configClass.getDeclaredField(field.getName());
if (!field.isAccessible()) {
field.setAccessible(true);
}
final Object value = field.get(this);
if (value != null) {
if (!configField.isAccessible()) {
configField.setAccessible(true);
}
configField.set(config, value);
getLogger().debug("using " + field.getName() + " = " + value);
}
} catch (final NoSuchFieldException nsfe) {
// ignored
} catch (final Exception e) {
getLogger().warn("can't initialize attribute " + field.getName());
}
}
if (containerProperties == null) {
containerProperties = new HashMap<>();
}
if (forceJspDevelopment) {
containerProperties.put("tomee.jsp-development", "true");
}
containerProperties.put("openejb.log.factory", "slf4j"); // like gradle
{ // ensure we don't scan gradle
final String original = containerProperties.get("openejb.additional.exclude");
final String additional =
"gradle,ant,jna,native-platform,reflectasm,bsh,jetty,rhino," +
"aws,core-3,bcpg,jsch,pmaven,sonar,bndlib,jatl,simple-,snakeyaml,jcl-over-slf4j,ivy," +
"jarjar,jul-to-slf4j,jaxen,minlog,jcip-annotations,kryo,objenesis";
if (original == null) {
containerProperties.put("openejb.additional.exclude", additional);
} else {
containerProperties.put("openejb.additional.exclude", original + ',' + additional);
}
}
if (containerProperties != null) {
final Properties props = new Properties();
props.putAll(containerProperties);
configClass.getMethod("setProperties", Properties.class)
.invoke(config, props);
}
return config;
}
private ClassLoader createLoader(final ClassLoader parent) {
getLogger().info("Resolving tomee-embedded classpath...");
final Collection<URL> urls = new LinkedHashSet<>(64);
addFiles(modules, urls);
for (final Configuration cc : getProject().getConfigurations()) {
if (applicationScopes.contains(cc.getName())) {
addFiles(cc.getFiles(), urls);
}
}
addFiles(classpath.getFiles(), urls);
// use JVM loader to avoid the noise of gradle and its plugins
return new URLClassLoader(urls.toArray(new URL[urls.size()]), new FilterGradleClassLoader(parent, classloaderFilteredPackages));
}
private void addFiles(final Collection<File> files, final Collection<URL> urls) {
if (files == null || files.isEmpty()) {
return;
}
for (final File f : files) {
final String name = f.getName();
if (name.startsWith("slf4j-api") || name.startsWith("slf4j-jdk14")) {
continue; // use gradle
}
try {
urls.add(f.toURI().toURL());
} catch (final MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
}
public int getHttpPort() {
return httpPort;
}
public void setHttpPort(final int httpPort) {
this.httpPort = httpPort;
}
public int getHttpsPort() {
return httpsPort;
}
public void setHttpsPort(final int httpsPort) {
this.httpsPort = httpsPort;
}
public int getAjpPort() {
return ajpPort;
}
public void setAjpPort(final int ajpPort) {
this.ajpPort = ajpPort;
}
public int getStopPort() {
return stopPort;
}
public void setStopPort(final int stopPort) {
this.stopPort = stopPort;
}
public String getHost() {
return host;
}
public void setHost(final String host) {
this.host = host;
}
public String getKeystoreFile() {
return keystoreFile;
}
public void setKeystoreFile(final String keystoreFile) {
this.keystoreFile = keystoreFile;
}
public String getKeystorePass() {
return keystorePass;
}
public void setKeystorePass(final String keystorePass) {
this.keystorePass = keystorePass;
}
public String getKeystoreType() {
return keystoreType;
}
public void setKeystoreType(final String keystoreType) {
this.keystoreType = keystoreType;
}
public String getClientAuth() {
return clientAuth;
}
public void setClientAuth(final String clientAuth) {
this.clientAuth = clientAuth;
}
public String getKeyAlias() {
return keyAlias;
}
public void setKeyAlias(final String keyAlias) {
this.keyAlias = keyAlias;
}
public String getSslProtocol() {
return sslProtocol;
}
public void setSslProtocol(final String sslProtocol) {
this.sslProtocol = sslProtocol;
}
public File getServerXml() {
return serverXml;
}
public void setServerXml(final File serverXml) {
this.serverXml = serverXml;
}
public boolean isSsl() {
return ssl;
}
public void setSsl(final boolean ssl) {
this.ssl = ssl;
}
public boolean isWithEjbRemote() {
return withEjbRemote;
}
public void setWithEjbRemote(final boolean withEjbRemote) {
this.withEjbRemote = withEjbRemote;
}
public boolean isQuickSession() {
return quickSession;
}
public void setQuickSession(final boolean quickSession) {
this.quickSession = quickSession;
}
public boolean isSkipHttp() {
return skipHttp;
}
public void setSkipHttp(final boolean skipHttp) {
this.skipHttp = skipHttp;
}
public Collection<String> getApplicationScopes() {
return applicationScopes;
}
public void setApplicationScopes(final Collection<String> applicationScopes) {
this.applicationScopes = applicationScopes;
}
public boolean isWebResourceCached() {
return webResourceCached;
}
public void setWebResourceCached(final boolean webResourceCached) {
this.webResourceCached = webResourceCached;
}
public String getContext() {
return context;
}
public void setContext(final String context) {
this.context = context;
}
public Map<String, String> getContainerProperties() {
return containerProperties;
}
public void setContainerProperties(final Map<String, String> containerProperties) {
this.containerProperties = containerProperties;
}
public boolean isKeepServerXmlAsThis() {
return keepServerXmlAsThis;
}
public void setKeepServerXmlAsThis(final boolean keepServerXmlAsThis) {
this.keepServerXmlAsThis = keepServerXmlAsThis;
}
public Map<String, String> getUsers() {
return users;
}
public void setUsers(final Map<String, String> users) {
this.users = users;
}
public Map<String, String> getRoles() {
return roles;
}
public void setRoles(final Map<String, String> roles) {
this.roles = roles;
}
public boolean isForceJspDevelopment() {
return forceJspDevelopment;
}
public void setForceJspDevelopment(final boolean forceJspDevelopment) {
this.forceJspDevelopment = forceJspDevelopment;
}
public String getInlinedServerXml() {
return inlinedServerXml;
}
public void setInlinedServerXml(final String inlinedServerXml) {
this.inlinedServerXml = inlinedServerXml;
}
public String getInlinedTomEEXml() {
return inlinedTomEEXml;
}
public void setInlinedTomEEXml(final String inlinedTomEEXml) {
this.inlinedTomEEXml = inlinedTomEEXml;
}
public File getWorkDir() {
return workDir;
}
public void setWorkDir(final File workDir) {
this.workDir = workDir;
}
public List<File> getModules() {
return modules;
}
public void setModules(final List<File> modules) {
this.modules = modules;
}
public File getDocBase() {
return docBase;
}
public void setDocBase(final File docBase) {
this.docBase = docBase;
}
public String getDir() {
return dir;
}
public void setDir(final String dir) {
this.dir = dir;
}
public Configuration getClasspath() {
return classpath;
}
public void setClasspath(final Configuration classpath) {
this.classpath = classpath;
}
public void setSingleClassloader(final boolean singleClassloader) {
this.singleClassloader = singleClassloader;
}
public Collection<String> getCustomWebResources() {
return customWebResources;
}
public void setCustomWebResources(final Collection<String> customWebResources) {
this.customWebResources = customWebResources;
}
}