/* * 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; } }