Java tutorial
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; } }