/* * 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.magma.tools.maven; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import net.sourceforge.cobertura.instrument.CoberturaClassTransformer; import org.apache.magma.tools.classloading.AspectJClassTransformer; import org.apache.magma.tools.classloading.ClassLoaderBytecodeProvider; import org.apache.magma.tools.classloading.JPAClassTransformer; import org.apache.magma.tools.classloading.JettyTransformingClassLoader; import org.apache.magma.tools.classloading.TransformingProvider; import org.apache.magma.tools.classloading.caching.ClassCache; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.mortbay.jetty.Handler; import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.ContextHandlerCollection; import org.mortbay.jetty.handler.DefaultHandler; import org.mortbay.jetty.handler.HandlerCollection; import org.mortbay.jetty.nio.SelectChannelConnector; import org.mortbay.jetty.webapp.WebAppClassLoader; import org.mortbay.jetty.webapp.WebAppContext; import org.mortbay.resource.Resource; /** * @goal run * @requiresDependencyResolution runtime * @execute phase="compile" * @description Runs a jetty 6 server, with proper load time weaving classloader */ public class MagmaJettyRun extends AbstractMojo { protected static Server server; /** * The maven project. * * @parameter expression="${executedProject}" * @required * @readonly */ private MavenProject project; /** * The context path for the webapp. Defaults to the name of the webapp's * artifact. * * @parameter expression="/${project.artifactId}" * @required */ private String contextPath; /** * Whether to background execution (letting other maven stuff to run) or not. * * @parameter expression="false" * @required */ private boolean background = false; /** * The temporary directory to use for the webapp. Defaults to * target/work * * @parameter expression="${project.build.directory}/work" * @required */ private File tmpDirectory; /** * The directory containing generated classes. * * @parameter expression="${project.build.outputDirectory}" * @required * */ private File classesDirectory; /** * Whether to setup cobertura instrumentation. * * @parameter expression="false" * @required */ private boolean coverage = false; /** * The serialization file for cobertura data. * * @parameter expression="${project.build.directory}/cobertura.ser" * @required */ private String coverageDataFile = null; /** * Class patterns to include in coverage data * * @parameter */ private String[] coverageIncludes = null; /** * Class patterns to exclude in coverage data * * @parameter */ private String[] coverageExcludes = null; public String PORT_SYSPROPERTY = "jetty.port"; public static int DEFAULT_PORT = 8080; public static int DEFAULT_MAX_IDLE_TIME = 30000; @SuppressWarnings("unchecked") public void execute() throws MojoExecutionException, MojoFailureException { System.setProperty("magma.env", System.getProperty("magma.env", "devel")); server = new Server(); server.setStopAtShutdown(true); //make sure Jetty does not use URLConnection caches with the plugin Resource.setDefaultUseCaches(false); // Create the webapp WebAppContext webapp = new WebAppContext(); webapp.setContextPath(this.contextPath); if (this.classesDirectory.exists()) { webapp.setWar(this.classesDirectory.getAbsolutePath()); } else if (new File(project.getBuild().getTestOutputDirectory()).exists()) { webapp.setWar(project.getBuild().getTestOutputDirectory()); } else { throw new MojoExecutionException("no root for war to run"); } if (!tmpDirectory.exists()) { if (!tmpDirectory.mkdirs()) { throw new MojoExecutionException("Cannot create temporary directory " + tmpDirectory.getAbsolutePath()); } } webapp.setTempDirectory(tmpDirectory); tmpDirectory.deleteOnExit(); webapp.addServlet("org.apache.magma.website.Dispatch", "/*"); // Setup the classloader chain List urls = new ArrayList(); try { List resources = project.getResources(); for (Iterator iterator = resources.iterator(); iterator.hasNext();) { org.apache.maven.model.Resource res = (org.apache.maven.model.Resource) iterator.next(); File resdir = new File(res.getDirectory()); if (resdir.exists()) { urls.add(resdir.toURI().toURL()); } } // Check for a local paths file File locpaths = new File(project.getBasedir(), "magma.locals"); if (locpaths.exists()) { FileReader fr = new FileReader(locpaths); BufferedReader br = new BufferedReader(fr); String line = null; while ((line = br.readLine()) != null) { File lpf = null; if (line.startsWith(File.separator)) { lpf = new File(line); } else { lpf = new File(project.getBasedir(), line); } if (lpf.exists() && lpf.isDirectory()) { File pom = new File(lpf, "pom.xml"); if (pom.exists()) { lpf = new File(lpf, "src/main/resources/"); } if (lpf.exists()) { urls.add(lpf.toURI().toURL()); } else { getLog().warn("Cannot add to classpath " + lpf.getAbsolutePath() + " : it points to a maven project without main resources"); } } else { getLog().warn("Cannot add to classpath " + lpf.getAbsolutePath() + " : it is non existing or not a directory"); } } } if (this.classesDirectory.exists()) { urls.add(this.classesDirectory.toURI().toURL()); } Set artifacts = project.getArtifacts(); for (Iterator iterator = artifacts.iterator(); iterator.hasNext();) { Artifact art = (Artifact) iterator.next(); urls.add(art.getFile().toURI().toURL()); } } catch (IOException e) { throw new MojoExecutionException("Error setting up classloader url list", e); } URL[] urlarray = (URL[]) urls.toArray(new URL[urls.size()]); List aspects = new ArrayList(); for (URL url : urlarray) { try { File f = new File(url.toURI()); if (f.isDirectory()) { aspects.add(url); } else { ZipFile zf = new ZipFile(f); ZipEntry entry = zf.getEntry("META-INF/aop-ajc.xml"); if (entry != null) aspects.add(url); } } catch (Exception e) { e.printStackTrace(); } } StringBuilder jettypath = new StringBuilder(); for (URL acurl : urlarray) { // TODO remove this, workaround for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=282379 //jettypath.append(acurl.toString()); jettypath.append(URLDecoder.decode(acurl.toString())); jettypath.append(','); } webapp.setExtraClasspath(jettypath.toString()); getLog().debug("Starting jetty context with classpath : " + jettypath); String[] systemClasses = webapp.getSystemClasses(); List sysclasseslist = new ArrayList(Arrays.asList(systemClasses)); sysclasseslist.add("javax.net."); sysclasseslist.add("javax.management."); sysclasseslist.add("javax.imageio."); webapp.setSystemClasses(sysclasseslist.toArray(new String[0])); try { WebAppClassLoader rootrepo = new WebAppClassLoader(webapp); URLClassLoader limitedrepo = new URLClassLoader(urlarray, null); JettyTransformingClassLoader root = new JettyTransformingClassLoader(Thread.currentThread().getContextClassLoader(), webapp); root.setRawResources(rootrepo); ClassLoaderBytecodeProvider rootprovider = new ClassLoaderBytecodeProvider(limitedrepo); // TODO remove this, workaround for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=282379 for (int i = 0; i < aspects.size(); i++) { try { aspects.set(i, new URL(URLDecoder.decode(aspects.get(i).toExternalForm(), "UTF-8"))); } catch (Exception e) { throw new RuntimeException("Error sanitizing the aspect URL", e); } } // end workaround AspectJClassTransformer ajctransformer = new AspectJClassTransformer(rootrepo, aspects.toArray(new URL[] {})); TransformingProvider ajcprovider = new TransformingProvider("ajc", ajctransformer); root.setChainProvider(ajcprovider); ajcprovider.setChainProvider(rootprovider); Class support = null; TransformingProvider jpaprovider = null; try { support = root.loadClass("org.apache.magma.database.openjpa.SupportTransformer"); } catch (ClassNotFoundException e) {} if (support != null) { StringBuilder paths = new StringBuilder(); for (URL acurl : urlarray) { // TODO remove this, workaround for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=282379 //paths.append(acurl.toString()); paths.append(URLDecoder.decode(acurl.toString())); paths.append(';'); } Class jpact = root.loadClass(JPAClassTransformer.class.getName()); JPAClassTransformer jpatransformer = (JPAClassTransformer) jpact. getConstructor(ClassLoader.class, String.class, String.class) .newInstance(root, "org.apache.magma.database.openjpa.SupportTransformer", paths.toString()); jpaprovider = new TransformingProvider("jpa", jpatransformer); jpaprovider.setChainProvider(ajcprovider); root.setChainProvider(jpaprovider); } if (coverage) { System.setProperty("net.sourceforge.cobertura.datafile", coverageDataFile); CoberturaClassTransformer coberturatransformer = new CoberturaClassTransformer(coverageDataFile); if (coverageExcludes != null) { for (String patt : coverageExcludes) { coberturatransformer.addClassExclude(patt); } } if (coverageIncludes != null) { for (String patt : coverageIncludes) { coberturatransformer.addClassInclude(patt); } } TransformingProvider coberturaprovider = new TransformingProvider("cobertura", coberturatransformer); if (jpaprovider != null) { coberturaprovider.setChainProvider(jpaprovider); } else { coberturaprovider.setChainProvider(ajcprovider); } root.setChainProvider(coberturaprovider); } webapp.setClassLoader(root); } catch (Exception e) { throw new MojoExecutionException("Error setting up classloaders ", e); } // Setup the handler chains ContextHandlerCollection contexts = new ContextHandlerCollection(); contexts.addHandler(webapp); HandlerCollection handlers = new HandlerCollection(); Handler defaultHandler = new DefaultHandler(); handlers.setHandlers(new Handler[]{contexts, defaultHandler}); server.setHandler(handlers); // Create the default connector SelectChannelConnector connector = new SelectChannelConnector(); String portnum = System.getProperty(PORT_SYSPROPERTY, null); int port = ((portnum==null||portnum.equals(""))?DEFAULT_PORT:Integer.parseInt(portnum.trim())); connector.setAcceptors(5); connector.setPort(port); connector.setMaxIdleTime(DEFAULT_MAX_IDLE_TIME); server.addConnector(connector); try { server.start(); if (!background) { server.join(); } } catch (Exception e) { throw new MojoExecutionException("Error starting server", e); } } }