package org.codehaus.mojo.gwt.shell; /* * 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.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; 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.project.MavenProject; import org.codehaus.mojo.gwt.utils.GwtModuleReaderException; import org.codehaus.plexus.archiver.ArchiverException; import org.codehaus.plexus.archiver.UnArchiver; import org.codehaus.plexus.archiver.manager.ArchiverManager; import org.codehaus.plexus.archiver.manager.NoSuchArchiverException; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.DirectoryScanner; /** * Runs the project in the GWT (Classic or Super) Dev Mode for development. * * @author ccollins * @author cooper * @version $Id$ */ @Mojo(name = "run", requiresDirectInvocation = true, requiresDependencyResolution = ResolutionScope.TEST) @Execute(phase = LifecyclePhase.PROCESS_CLASSES, goal = "war:exploded") public class RunMojo extends AbstractGwtWebMojo { /** * Location of the hosted-mode web application structure. */ @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}") // Parameter shared with EclipseMojo private File hostedWebapp; /** * The MavenProject executed by the "compile" phase */ @Parameter(defaultValue = "${executedProject}") private MavenProject executedProject; /** * URL that should be automatically opened in the GWT shell. For example com.myapp.gwt.Module/Module.html. * <p> * When the host page is outside the module "public" folder (for example, at webapp root), the module MUST be * specified (using a single <module> in configuration or by setting <code>-Dgwt.module=..</code>) and the * runTarget parameter can only contain the host page URI. * <p> * When the GWT module host page is part of the module "public" folder, the runTarget MAY define the full GWT module * path (<code>com.myapp.gwt.Module/Module.html</code>) that will be automatically converted according to the * <code>rename-to</code> directive into <code>renamed/Module.html</code>. */ @Parameter(property = "runTarget", required = true) private String runTarget; /** * Forked process execution timeOut (in seconds). Primary used for integration-testing. */ @Parameter @SuppressWarnings("unused") private int runTimeOut; /** * Runs the embedded GWT server on the specified port. */ @Parameter(defaultValue = "8888", property = "gwt.port") private int port; /** * Runs the code server on the specified port. */ @Parameter(defaultValue = "9997", property = "gwt.codeServerPort") private int codeServerPort; /** * Location of the compiled classes. */ @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true) private File buildOutputDirectory; /** * Prevents the embedded GWT Tomcat server from running (even if a port is specified). * <p> * Can be set from command line using '-Dgwt.noserver=...' */ @Parameter(defaultValue = "false", property = "gwt.noserver") private boolean noServer; /** * Specifies a different embedded web server to run (must implement ServletContainerLauncher) */ @Parameter(property = "gwt.server") private String server; /** * List of System properties to pass when running the hosted mode. * * @since 1.2 */ @Parameter private Map<String, String> systemProperties; /** * Copies the contents of warSourceDirectory to hostedWebapp. * <p> * Can be set from command line using '-Dgwt.copyWebapp=...' * </p> * * @since 2.1.0-1 */ @Parameter(defaultValue = "false", property = "gwt.copyWebapp") private boolean copyWebapp; /** * set the appengine sdk to use * <p> * Artifact will be downloaded with groupId : {@link #appEngineGroupId} * and artifactId {@link #appEngineArtifactId} * <p> * * @since 2.1.0-1 */ @Parameter(defaultValue = "1.3.8", property = "gwt.appEngineVersion") private String appEngineVersion; /** * <p> * List of {@link Pattern} jars to exclude from the classPath when running * dev mode * </p> * * @since 2.1.0-1 */ @Parameter private List<String> runClasspathExcludes; /** * <p> * Location to find appengine sdk or to unzip downloaded one see {@link #appEngineVersion} * </p> * * @since 2.1.0-1 */ @Parameter(defaultValue = "${project.build.directory}/appengine-sdk/", property = "gwt.appEngineHome") private File appEngineHome; /** * groupId to download appengine sdk from maven repo * * @since 2.1.0-1 */ @Parameter(defaultValue = "com.google.appengine", property = "gwt.appEngineGroupId") private String appEngineGroupId; /** * groupId to download appengine sdk from maven repo * * @since 2.1.0-1 */ @Parameter(defaultValue = "appengine-java-sdk", property = "gwt.appEngineArtifactId") private String appEngineArtifactId; /** * To look up Archiver/UnArchiver implementations * @since 2.1.0-1 */ @Component protected ArchiverManager archiverManager; /** * Set GWT shell bindAddress. * <p> * Can be set from command line using '-Dgwt.bindAddress=...' * @since 2.1.0-1 */ @Parameter(property = "gwt.bindAddress") private String bindAddress; /** * EXPERIMENTAL: Cache results of generators with stable output. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.cacheGeneratorResults") private boolean cacheGeneratorResults; /** * The compiler's working directory for internal use (must be writeable; defaults to a system temp dir) * * @since 2.6.0-rc1 */ @Parameter private File workDir; /** * Logs to a file in the given directory, as well as graphically * * @since 2.6.0-rc1 */ @Parameter private File logDir; /** * Specifies Java source level. * <p> * The default value depends on the JVM used to launch Maven. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "auto", property = "maven.compiler.source") private String sourceLevel; /** * Runs Super Dev Mode instead of classic Development Mode. * * @since 2.7.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.superDevMode") private boolean superDevMode; /** * Compiles faster by reusing data from the previous compile. * * @since 2.7.0-rc1 */ @Parameter(alias = "compilePerFile", defaultValue = "true", property = "gwt.compiler.incremental") private boolean incremental; /** * Generate exports for JsInterop purposes. * * @since 2.8.0-rc1 */ @Parameter(alias = "generateJsInteropExports", defaultValue = "false", property = "gwt.compiler.generateJsInteropExports") private boolean generateJsInteropExports; /** * EXPERIMENTAL: Emit extra information allow chrome dev tools to display Java identifiers in many places instead of JavaScript functions. * <p> * Value can be one of NONE, ONLY_METHOD_NAME, ABBREVIATED or FULL. * * @since 2.7.0-rc1 */ @Parameter(defaultValue = "NONE", property = "gwt.compiler.methodNameDisplayMode") private String methodNameDisplayMode; /** * @return the startup URL to open in hosted browser (gwt 1.6+) */ public String getStartupUrl() throws MojoExecutionException { if ( noServer ) { return runTarget; } int dash = runTarget.indexOf( '/' ); if ( dash > 0 ) { String prefix = runTarget.substring( 0, dash ); // runTarget includes the GWT module full path. // Lets retrieve the GWT module and apply the rename-to directive String[] modules = getModules(); for ( String module : modules ) { if ( prefix.equals( module ) ) { try { return readModule( module ).getPath() + '/' + runTarget.substring( dash + 1 ); } catch ( GwtModuleReaderException e ) { throw new MojoExecutionException( e.getMessage(), e ); } } } } return runTarget; } public void doExecute( ) throws MojoExecutionException, MojoFailureException { JavaCommand cmd = createJavaCommand() .setMainClass( "com.google.gwt.dev.DevMode" ); if ( gwtSdkFirstInClasspath ) { cmd.addToClasspath( getGwtUserJar() ) .addToClasspath( getGwtDevJar() ); } cmd.addToClasspath( getClasspath( Artifact.SCOPE_RUNTIME ) ); addCompileSourceArtifacts( cmd ); addArgumentDeploy(cmd); addArgumentGen( cmd ); addPersistentUnitCache(cmd); if ( !gwtSdkFirstInClasspath ) { cmd.addToClasspath( getGwtUserJar() ) .addToClasspath( getGwtDevJar() ); } cmd.arg( "-war", hostedWebapp.getAbsolutePath() ) .arg( "-logLevel", getLogLevel() ) .arg( "-port", Integer.toString( getPort() ) ) .arg( "-codeServerPort" , Integer.toString( codeServerPort )) .arg( "-startupUrl", getStartupUrl() ) .arg( noServer, "-nostartServer" ) .arg( !cacheGeneratorResults, "-XnocacheGeneratorResults" ) .arg( !superDevMode, "-nosuperDevMode" ) .arg( !incremental, "-noincremental" ) .arg( generateJsInteropExports, "-generateJsInteropExports" ) .arg( "-sourceLevel", sourceLevel ); if ( style != null && style.length() > 0 ) { cmd.arg( "-style", style ); } if ( methodNameDisplayMode != null && methodNameDisplayMode.length() > 0 && !methodNameDisplayMode.equals( "NONE" )) { cmd.arg( "-XmethodNameDisplayMode", methodNameDisplayMode ); } if ( workDir != null ) { cmd.arg( "-workDir", workDir.getAbsolutePath() ); } if ( logDir != null ) { cmd.arg( "-logdir", logDir.getAbsolutePath() ); } if ( server != null ) { cmd.arg( "-server", server ); } if ( systemProperties != null && !systemProperties.isEmpty() ) { for ( String key : systemProperties.keySet() ) { String value = systemProperties.get( key ); if ( value != null ) { getLog().debug( " " + key + "=" + value ); cmd.systemProperty( key, value ); } else { getLog().debug( "skip sysProps " + key + " with empty value" ); } } } if ( bindAddress != null && bindAddress.length() > 0 ) { cmd.arg( "-bindAddress" ).arg( bindAddress ); } if ( modulePathPrefix != null && !modulePathPrefix.isEmpty() ) { cmd.arg( "-modulePathPrefix" ).arg( modulePathPrefix ); } if ( !noServer ) { setupExplodedWar(); } else { getLog().info( "noServer is set! Skipping exploding war file..." ); } for ( String module : getModules() ) { cmd.arg( module ); } try { cmd.execute(); } catch ( JavaCommandException e ) { throw new MojoExecutionException( e.getMessage(), e ); } } @Override protected void postProcessClassPath( Collection<File> classPath ) { boolean isAppEngine = "com.google.appengine.tools.development.gwt.AppEngineLauncher".equals( server ); List<Pattern> patternsToExclude = new ArrayList<Pattern>(); if ( runClasspathExcludes != null && !runClasspathExcludes.isEmpty() ) { for ( String runClasspathExclude : runClasspathExcludes ) { patternsToExclude.add( Pattern.compile( runClasspathExclude ) ); } } Iterator<File> it = classPath.iterator(); while ( it.hasNext() ) { String name = it.next().getName(); if ( !patternsToExclude.isEmpty() ) { for ( Pattern pattern : patternsToExclude ) { if ( pattern.matcher( name ).find() ) { getLog().info( "remove jar " + name + " from system classpath" ); it.remove(); continue; } } } } // TODO refactor this a little if ( isAppEngine ) { File appEngineToolsApi = new File( appEngineHome, "/lib/appengine-tools-api.jar" ); File appEngineLocalRuntime = new File( appEngineHome, "/lib/impl/appengine-local-runtime.jar" ); File appEngineAgent = new File( appEngineHome, "/lib/agent/appengine-agent.jar" ); if ( appEngineHome.exists() && appEngineToolsApi.exists() && appEngineLocalRuntime.exists() && appEngineAgent.exists() ) { classPath.add( appEngineToolsApi ); classPath.add( appEngineLocalRuntime ); classPath.add( appEngineAgent ); } else { try { if ( !appEngineHome.exists() ) { appEngineHome.mkdirs(); // force addition of appengine SDK in a exploded SDK repository location Artifact appEngineSdk = resolve( appEngineGroupId, appEngineArtifactId, appEngineVersion, "zip", "" ); // sdk extraction UnArchiver unArchiver = archiverManager.getUnArchiver( appEngineSdk.getFile() ); unArchiver.setSourceFile( appEngineSdk.getFile() ); unArchiver.setDestDirectory( appEngineHome ); getLog().info( "extract appengine " + appEngineVersion + " sdk to " + appEngineHome.getPath() ); unArchiver.extract(); } else { getLog().info( "use existing appengine sdk from " + appEngineHome.getPath() ); } appEngineToolsApi = new File( appEngineHome, "appengine-java-sdk-" + appEngineVersion + "/lib/appengine-tools-api.jar" ); if ( !appEngineToolsApi.exists() ) { throw new RuntimeException( appEngineToolsApi.getPath() + " not exists" ); } classPath.add( appEngineToolsApi ); getLog().debug( "add " + appEngineToolsApi.getPath() + " to the classpath" ); appEngineLocalRuntime = new File( appEngineHome, "appengine-java-sdk-" + appEngineVersion + "/lib/impl/appengine-local-runtime.jar" ); if ( !appEngineLocalRuntime.exists() ) { throw new RuntimeException( appEngineLocalRuntime.getPath() + " not exists" ); } classPath.add( appEngineLocalRuntime ); getLog().debug( "add " + appEngineLocalRuntime.getPath() + " to the classpath" ); appEngineAgent = new File( appEngineHome, "appengine-java-sdk-" + appEngineVersion + "/lib/agent/appengine-agent.jar" ); classPath.add( appEngineAgent ); getLog().debug( "add " + appEngineAgent.getPath() + " to the classpath" ); } catch ( MojoExecutionException e ) { // FIXME add throws MojoExecutionException in postProcessClassPath throw new RuntimeException( e.getMessage(), e ); } catch ( ArchiverException e ) { // FIXME add throws MojoExecutionException in postProcessClassPath throw new RuntimeException( e.getMessage(), e ); } catch ( NoSuchArchiverException e ) { // FIXME add throws MojoExecutionException in postProcessClassPath throw new RuntimeException( e.getMessage(), e ); } } } } /** * Copied a directory structure with deafault exclusions (.svn, CVS, etc) * * @param sourceDir The source directory to copy, must not be <code>null</code>. * @param destDir The target directory to copy to, must not be <code>null</code>. * @throws java.io.IOException If the directory structure could not be copied. */ private void copyDirectoryStructureIfModified(File sourceDir, File destDir) throws IOException { DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir( sourceDir ); scanner.addDefaultExcludes(); scanner.scan(); /* * NOTE: Make sure the destination directory is always there (even if empty) to support POM-less ITs. */ destDir.mkdirs(); String[] includedDirs = scanner.getIncludedDirectories(); for ( int i = 0; i < includedDirs.length; ++i ) { File clonedDir = new File( destDir, includedDirs[i] ); clonedDir.mkdirs(); } String[] includedFiles = scanner.getIncludedFiles(); for ( int i = 0; i < includedFiles.length; ++i ) { File sourceFile = new File(sourceDir, includedFiles[i]); File destFile = new File(destDir, includedFiles[i]); FileUtils.copyFileIfModified(sourceFile, destFile); } } private void setupExplodedWar() throws MojoExecutionException { getLog().info( "create exploded Jetty webapp in " + hostedWebapp ); if ( copyWebapp && !warSourceDirectory.getAbsolutePath().equals( hostedWebapp.getAbsolutePath() ) ) { try { // can't use FileUtils.copyDirectoryStructureIfModified because it does not // excludes the DEFAULTEXCLUDES copyDirectoryStructureIfModified(warSourceDirectory, hostedWebapp); } catch ( IOException e ) { throw new MojoExecutionException( "Failed to copy warSourceDirectory to " + hostedWebapp, e ); } } File classes = new File( hostedWebapp, "WEB-INF/classes" ); classes.mkdirs(); if ( !buildOutputDirectory.getAbsolutePath().equals( classes.getAbsolutePath() ) ) { getLog().warn( "Your POM <build><outputdirectory> does not match your " + "hosted webapp WEB-INF/classes folder for GWT Hosted browser to see your classes." ); try { FileUtils.copyDirectoryStructure( buildOutputDirectory, classes ); } catch ( IOException e ) { throw new MojoExecutionException( "Failed to copy classes to " + classes , e ); } } File lib = new File( hostedWebapp, "WEB-INF/lib" ); lib.mkdirs(); Collection<Artifact> artifacts = getProjectRuntimeArtifacts(); for ( Artifact artifact : artifacts ) { try { // Using m2eclipse with "resolve workspace dependencies" the artifact is the buildOutputDirectory if ( ! artifact.getFile().isDirectory() ) { FileUtils.copyFileToDirectory( artifact.getFile(), lib ); } } catch ( IOException e ) { throw new MojoExecutionException( "Failed to copy runtime dependency " + artifact, e ); } } } public int getPort() { return this.port; } /** * @param runTimeOut the runTimeOut to set */ public void setRunTimeOut( int runTimeOut ) { setTimeOut( runTimeOut ); } public void setExecutedProject( MavenProject executedProject ) { this.executedProject = executedProject; } @Override public MavenProject getProject() { return executedProject; } }