org.apache.tomcat.maven.plugin.tomcat8.run.RunMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.tomcat.maven.plugin.tomcat8.run.RunMojo.java

Source

package org.apache.tomcat.maven.plugin.tomcat8.run;
/*
 * 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.
 */

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.catalina.Context;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.webresources.EmptyResource;
import org.apache.catalina.webresources.FileResource;
import org.apache.catalina.webresources.FileResourceSet;
import org.apache.catalina.webresources.JarResource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.shared.filtering.MavenFileFilterRequest;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculator;
import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculatorRequest;
import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculatorResult;
import org.apache.tomcat.maven.common.run.TomcatRunException;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.Xpp3DomWriter;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Runs the current project as a dynamic web application using an embedded Tomcat server.
 *
 * @author Olivier Lamy
 * @since 2.0
 */
@Mojo(name = "run", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
@Execute(phase = LifecyclePhase.PROCESS_CLASSES)
public class RunMojo extends AbstractRunMojo {
    // ----------------------------------------------------------------------
    // Mojo Parameters
    // ----------------------------------------------------------------------

    /**
     * The set of dependencies for the web application being run.
     */
    @Parameter(defaultValue = "${project.artifacts}", required = true, readonly = true)
    private Set<Artifact> dependencies;

    /**
     * The web resources directory for the web application being run.
     */
    @Parameter(defaultValue = "${basedir}/src/main/webapp", property = "tomcat.warSourceDirectory")
    private File warSourceDirectory;

    /**
     * Set the "follow standard delegation model" flag used to configure our ClassLoader.
     *
     * @see http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/loader/WebappLoader.html#setDelegate(boolean)
     * @since 1.0
     */
    @Parameter(property = "tomcat.delegate", defaultValue = "true")
    private boolean delegate = true;

    /**
     * @since 2.0
     */
    @Component
    private ClassLoaderEntriesCalculator classLoaderEntriesCalculator;

    /**
     * will add /WEB-INF/lib/*.jar and /WEB-INF/classes from war dependencies in the webappclassloader
     *
     * @since 2.0
     */
    @Parameter(property = "maven.tomcat.addWarDependenciesInClassloader", defaultValue = "true")
    private boolean addWarDependenciesInClassloader;

    /**
     * will use the test classpath rather than the compile one and will add test dependencies too
     *
     * @since 2.0
     */
    @Parameter(property = "maven.tomcat.useTestClasspath", defaultValue = "false")
    private boolean useTestClasspath;

    /**
     * Additional optional directories to add to the embedded tomcat classpath.
     *
     * @since 2.0
     */
    @Parameter(alias = "additionalClassesDirs")
    private List<String> additionalClasspathDirs;

    public final File getWarSourceDirectory() {
        return warSourceDirectory;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected File getDocBase() throws IOException {
        // https://issues.apache.org/jira/browse/MTOMCAT-239
        // when running a jar docBase doesn't exists so create a fake one
        if (!warSourceDirectory.exists()) {
            // we create a temporary file in build.directory
            final File tempDocBase = createTempDirectory(new File(project.getBuild().getDirectory()));
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    try {
                        FileUtils.deleteDirectory(tempDocBase);
                    } catch (Exception e) {
                        // we can consider as safe to ignore as it's located in build directory
                    }
                }
            });
            return tempDocBase;
        }
        return warSourceDirectory;
    }

    private static File createTempDirectory(File baseTmpDirectory) throws IOException {
        final File temp = File.createTempFile("temp", Long.toString(System.nanoTime()), baseTmpDirectory);

        if (!(temp.delete())) {
            throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
        }

        if (!(temp.mkdir())) {
            throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
        }

        return temp;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected File getContextFile() throws MojoExecutionException {
        File temporaryContextFile = null;

        //----------------------------------------------------------------------------
        // context attributes backgroundProcessorDelay reloadable cannot be modified at runtime.
        // It looks only values from the file are used
        // so here we create a temporary file with values modified
        //----------------------------------------------------------------------------
        FileReader fr = null;
        FileWriter fw = null;
        StringWriter sw = new StringWriter();
        try {
            temporaryContextFile = File.createTempFile("tomcat-maven-plugin", "temp-ctx-file");
            temporaryContextFile.deleteOnExit();

            // format to modify/create <Context backgroundProcessorDelay="5" reloadable="false">
            if (contextFile != null && contextFile.exists()) {
                MavenFileFilterRequest mavenFileFilterRequest = new MavenFileFilterRequest();
                mavenFileFilterRequest.setFrom(contextFile);
                mavenFileFilterRequest.setTo(temporaryContextFile);
                mavenFileFilterRequest.setMavenProject(project);
                mavenFileFilterRequest.setMavenSession(session);
                mavenFileFilterRequest.setFiltering(true);

                mavenFileFilter.copyFile(mavenFileFilterRequest);

                fr = new FileReader(temporaryContextFile);
                Xpp3Dom xpp3Dom = Xpp3DomBuilder.build(fr);
                xpp3Dom.setAttribute("backgroundProcessorDelay", Integer.toString(backgroundProcessorDelay));
                xpp3Dom.setAttribute("reloadable", Boolean.toString(isContextReloadable()));
                fw = new FileWriter(temporaryContextFile);
                Xpp3DomWriter.write(fw, xpp3Dom);
                Xpp3DomWriter.write(sw, xpp3Dom);
                getLog().debug(" generated context file " + sw.toString());
            } else {
                if (contextReloadable) {
                    // don't care about using a complicated xml api to create one xml line :-)
                    StringBuilder sb = new StringBuilder("<Context ").append("backgroundProcessorDelay=\"")
                            .append(Integer.toString(backgroundProcessorDelay)).append("\"")
                            .append(" reloadable=\"" + Boolean.toString(isContextReloadable()) + "\"/>");

                    getLog().debug(" generated context file " + sb.toString());
                    fw = new FileWriter(temporaryContextFile);
                    fw.write(sb.toString());
                } else {
                    // no user context file and contextReloadable false so no need about creating a hack one
                    return null;
                }
            }
        } catch (IOException e) {
            getLog().error("error creating fake context.xml : " + e.getMessage(), e);
            throw new MojoExecutionException("error creating fake context.xml : " + e.getMessage(), e);
        } catch (XmlPullParserException e) {
            getLog().error("error creating fake context.xml : " + e.getMessage(), e);
            throw new MojoExecutionException("error creating fake context.xml : " + e.getMessage(), e);
        } catch (MavenFilteringException e) {
            getLog().error("error filtering context.xml : " + e.getMessage(), e);
            throw new MojoExecutionException("error filtering context.xml : " + e.getMessage(), e);
        } finally {
            IOUtil.close(fw);
            IOUtil.close(fr);
            IOUtil.close(sw);
        }

        return temporaryContextFile;
    }

    /**
     * {@inheritDoc}
     *
     * @throws MojoExecutionException
     */
    @Override
    protected WebappLoader createWebappLoader() throws IOException, MojoExecutionException {
        WebappLoader loader = super.createWebappLoader();

        if (useSeparateTomcatClassLoader) {
            loader.setDelegate(delegate);
        }

        return loader;
    }

    @Override
    protected void enhanceContext(final Context context) throws MojoExecutionException {
        super.enhanceContext(context);

        try {
            ClassLoaderEntriesCalculatorRequest request = new ClassLoaderEntriesCalculatorRequest() //
                    .setDependencies(dependencies) //
                    .setLog(getLog()) //
                    .setMavenProject(project) //
                    .setAddWarDependenciesInClassloader(addWarDependenciesInClassloader) //
                    .setUseTestClassPath(useTestClasspath);
            final ClassLoaderEntriesCalculatorResult classLoaderEntriesCalculatorResult = classLoaderEntriesCalculator
                    .calculateClassPathEntries(request);
            final List<String> classLoaderEntries = classLoaderEntriesCalculatorResult.getClassPathEntries();
            final List<File> tmpDirectories = classLoaderEntriesCalculatorResult.getTmpDirectories();

            final List<String> jarPaths = extractJars(classLoaderEntries);

            List<URL> urls = new ArrayList<URL>(jarPaths.size());

            for (String jarPath : jarPaths) {
                try {
                    urls.add(new File(jarPath).toURI().toURL());
                } catch (MalformedURLException e) {
                    throw new MojoExecutionException(e.getMessage(), e);
                }
            }

            getLog().debug("classLoaderEntriesCalculator urls: " + urls);

            final URLClassLoader urlClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]));

            final ClassRealm pluginRealm = getTomcatClassLoader();

            context.setResources(
                    new MyDirContext(new File(project.getBuild().getOutputDirectory()).getAbsolutePath(), //
                            getPath(), //
                            getLog()) {
                        @Override
                        public WebResource getClassLoaderResource(String path) {

                            log.debug("RunMojo#getClassLoaderResource: " + path);
                            URL url = urlClassLoader.getResource(StringUtils.removeStart(path, "/"));
                            // search in parent (plugin) classloader
                            if (url == null) {
                                url = pluginRealm.getResource(StringUtils.removeStart(path, "/"));
                            }

                            if (url == null) {
                                // try in reactors
                                List<WebResource> webResources = findResourcesInDirectories(path, //
                                        classLoaderEntriesCalculatorResult.getBuildDirectories());

                                // so we return the first one
                                if (!webResources.isEmpty()) {
                                    return webResources.get(0);
                                }
                            }

                            if (url == null) {
                                return new EmptyResource(this, getPath());
                            }

                            return urlToWebResource(url, path);
                        }

                        @Override
                        public WebResource getResource(String path) {
                            log.debug("RunMojo#getResource: " + path);
                            return super.getResource(path);
                        }

                        @Override
                        public WebResource[] getResources(String path) {
                            log.debug("RunMojo#getResources: " + path);
                            return super.getResources(path);
                        }

                        @Override
                        protected WebResource[] getResourcesInternal(String path, boolean useClassLoaderResources) {
                            log.debug("RunMojo#getResourcesInternal: " + path);
                            return super.getResourcesInternal(path, useClassLoaderResources);
                        }

                        @Override
                        public WebResource[] getClassLoaderResources(String path) {
                            try {
                                Enumeration<URL> enumeration = urlClassLoader
                                        .findResources(StringUtils.removeStart(path, "/"));
                                List<URL> urlsFound = new ArrayList<URL>();
                                List<WebResource> webResources = new ArrayList<WebResource>();
                                while (enumeration.hasMoreElements()) {
                                    URL url = enumeration.nextElement();
                                    urlsFound.add(url);
                                    webResources.add(urlToWebResource(url, path));
                                }
                                log.debug("RunMojo#getClassLoaderResources: " + path + " found : "
                                        + urlsFound.toString());

                                webResources.addAll(findResourcesInDirectories(path,
                                        classLoaderEntriesCalculatorResult.getBuildDirectories()));

                                return webResources.toArray(new WebResource[webResources.size()]);

                            } catch (IOException e) {
                                throw new RuntimeException(e.getMessage(), e);
                            }
                        }

                        private List<WebResource> findResourcesInDirectories(String path,
                                List<String> directories) {
                            try {
                                List<WebResource> webResources = new ArrayList<WebResource>();

                                for (String directory : directories) {

                                    File file = new File(directory, path);
                                    if (file.exists()) {
                                        webResources.add(urlToWebResource(file.toURI().toURL(), path));
                                    }

                                }

                                return webResources;
                            } catch (MalformedURLException e) {
                                throw new RuntimeException(e.getMessage(), e);
                            }
                        }

                        private WebResource urlToWebResource(URL url, String path) {
                            JarFile jarFile = null;

                            try {
                                // url.getFile is
                                // file:/Users/olamy/mvn-repo/org/springframework/spring-web/4.0.0.RELEASE/spring-web-4.0.0.RELEASE.jar!/org/springframework/web/context/ContextLoaderListener.class

                                int idx = url.getFile().indexOf('!');

                                if (idx >= 0) {
                                    String filePath = StringUtils.removeStart(url.getFile().substring(0, idx),
                                            "file:");

                                    jarFile = new JarFile(filePath);

                                    JarEntry jarEntry = jarFile.getJarEntry(StringUtils.removeStart(path, "/"));

                                    return new JarResource(this, //
                                            getPath(), //
                                            filePath, //
                                            url.getPath().substring(0, idx), //
                                            jarEntry, //
                                            "", //
                                            null);
                                } else {
                                    return new FileResource(this, webAppPath, new File(url.getFile()), true);
                                }

                            } catch (IOException e) {
                                throw new RuntimeException(e.getMessage(), e);
                            } finally {
                                IOUtils.closeQuietly(jarFile);
                            }
                        }

                    });

            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    for (File tmpDir : tmpDirectories) {
                        try {
                            FileUtils.deleteDirectory(tmpDir);
                        } catch (IOException e) {
                            // ignore
                        }
                    }
                }
            });

            if (classLoaderEntries != null) {
                WebResourceSet webResourceSet = new FileResourceSet() {
                    @Override
                    public WebResource getResource(String path) {

                        if (StringUtils.startsWithIgnoreCase(path, "/WEB-INF/LIB")) {
                            File file = new File(StringUtils.removeStartIgnoreCase(path, "/WEB-INF/LIB"));
                            return new FileResource(context.getResources(), getPath(), file, true);
                        }
                        if (StringUtils.equalsIgnoreCase(path, "/WEB-INF/classes")) {
                            return new FileResource(context.getResources(), getPath(),
                                    new File(project.getBuild().getOutputDirectory()), true);
                        }

                        File file = new File(project.getBuild().getOutputDirectory(), path);
                        if (file.exists()) {
                            return new FileResource(context.getResources(), getPath(), file, true);
                        }

                        //if ( StringUtils.endsWith( path, ".class" ) )
                        {
                            // so we search the class file in the jars
                            for (String jarPath : jarPaths) {
                                File jar = new File(jarPath);
                                if (!jar.exists()) {
                                    continue;
                                }

                                try (JarFile jarFile = new JarFile(jar)) {
                                    JarEntry jarEntry = (JarEntry) jarFile
                                            .getEntry(StringUtils.removeStart(path, "/"));
                                    if (jarEntry != null) {
                                        return new JarResource(context.getResources(), //
                                                getPath(), //
                                                jarFile.getName(), //
                                                jar.toURI().toString(), //
                                                jarEntry, //
                                                path, //
                                                jarFile.getManifest());
                                    }
                                } catch (IOException e) {
                                    getLog().debug("skip error building jar file: " + e.getMessage(), e);
                                }

                            }
                        }

                        return new EmptyResource(null, path);
                    }

                    @Override
                    public String[] list(String path) {
                        if (StringUtils.startsWithIgnoreCase(path, "/WEB-INF/LIB")) {
                            return jarPaths.toArray(new String[jarPaths.size()]);
                        }
                        if (StringUtils.equalsIgnoreCase(path, "/WEB-INF/classes")) {
                            return new String[] { new File(project.getBuild().getOutputDirectory()).getPath() };
                        }
                        return super.list(path);
                    }

                    @Override
                    public Set<String> listWebAppPaths(String path) {

                        if (StringUtils.equalsIgnoreCase("/WEB-INF/lib/", path)) {
                            // adding outputDirectory as well?
                            return new HashSet<String>(jarPaths);
                        }

                        File filePath = new File(getWarSourceDirectory(), path);

                        if (filePath.isDirectory()) {
                            Set<String> paths = new HashSet<String>();

                            String[] files = filePath.list();
                            if (files == null) {
                                return paths;
                            }

                            for (String file : files) {
                                paths.add(file);
                            }

                            return paths;

                        } else {
                            return Collections.emptySet();
                        }
                    }

                    @Override
                    public boolean mkdir(String path) {
                        return super.mkdir(path);
                    }

                    @Override
                    public boolean write(String path, InputStream is, boolean overwrite) {
                        return super.write(path, is, overwrite);
                    }

                    @Override
                    protected void checkType(File file) {
                        //super.checkType( file );
                    }

                };

                context.getResources().addJarResources(webResourceSet);
            }

        } catch (TomcatRunException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

    }

    /**
     * extract List of path which are files (removing directories from the initial list)
     *
     * @param classLoaderEntries
     * @return
     */
    private List<String> extractJars(List<String> classLoaderEntries) throws MojoExecutionException {

        List<String> jarPaths = new ArrayList<String>();

        try {
            for (String classLoaderEntry : classLoaderEntries) {
                URI uri = new URI(classLoaderEntry);
                File file = new File(uri);
                if (!file.isDirectory()) {
                    jarPaths.add(file.getAbsolutePath());
                }
            }
        } catch (URISyntaxException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

        return jarPaths;

    }
}