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 static org.codehaus.plexus.util.AbstractScanner.DEFAULTEXCLUDES;
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.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.StringUtils;
/**
* Goal which run a GWT module in the GWT Hosted mode.
*
* @goal run
* @execute phase=process-classes goal:war:exploded
* @requiresDirectInvocation
* @requiresDependencyResolution test
* @description Runs the the project in the GWT Hosted mode for development.
* @author ccollins
* @author cooper
* @version $Id$
*/
public class RunMojo
extends AbstractGwtWebMojo
{
/**
* Location of the hosted-mode web application structure.
*
* @parameter default-value="${project.build.directory}/${project.build.finalName}"
*/
// Parameter shared with EclipseMojo
private File hostedWebapp;
/**
* The MavenProject executed by the "compile" phase
* @parameter expression="${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 expression="${runTarget}"
* @required
*/
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 default-value="8888"
*/
private int port;
/**
* Specify the location on the filesystem for the generated embedded Tomcat directory.
*
* @parameter default-value="${project.build.directory}/tomcat"
*/
private File tomcat;
/**
* Location of the compiled classes.
*
* @parameter default-value="${project.build.outputDirectory}"
* @required
* @readOnly
*/
private File buildOutputDirectory;
/**
* Source Tomcat context.xml for GWT shell - copied to /gwt/localhost/ROOT.xml (used as the context.xml for the
* SHELL - requires Tomcat 5.0.x format - hence no default).
*
* @parameter
*/
private File contextXml;
/**
* 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 default-value="false" expression="${gwt.noserver}"
*/
private boolean noServer;
/**
* Specifies a different embedded web server to run (must implement ServletContainerLauncher)
*
* @parameter expression="${gwt.server}"
*/
private String server;
/**
* Set GWT shell protocol/host whitelist.
* <p>
* Can be set from command line using '-Dgwt.whitelist=...'
*
* @parameter expression="${gwt.whitelist}"
*/
private String whitelist;
/**
* Set GWT shell protocol/host blacklist.
* <p>
* Can be set from command line using '-Dgwt.blacklist=...'
*
* @parameter expression="${gwt.blacklist}"
*/
private String blacklist;
/**
* List of System properties to pass when running the hosted mode.
*
* @parameter
* @since 1.2
*/
private Map<String, String> systemProperties;
/**
* Copies the contents of warSourceDirectory to hostedWebapp.
* <p>
* Can be set from command line using '-Dgwt.copyWebapp=...'
* </p>
* @parameter default-value="false" expression="${gwt.copyWebapp}"
* @since 2.1.0-1
*/
private boolean copyWebapp;
/**
* set the appengine sdk to use
* <p>
* Artifact will be downloaded with groupId : {@link #appEngineGroupId}
* and artifactId {@link #appEngineArtifactId}
* <p>
* @parameter default-value="1.3.8" expression="${gwt.appEngineVersion}"
* @since 2.1.0-1
*/
private String appEngineVersion;
/**
* <p>
* List of {@link Pattern} jars to exclude from the classPath when running
* dev mode
* </p>
* @parameter
* @since 2.1.0-1
*/
private List<String> runClasspathExcludes;
/**
* <p>
* Location to find appengine sdk or to unzip downloaded one see {@link #appEngineVersion}
* </p>
* @parameter default-value="${project.build.directory}/appengine-sdk/" expression="${gwt.appEngineHome}"
* @since 2.1.0-1
*/
private File appEngineHome;
/**
* <p>
* groupId to download appengine sdk from maven repo
* </p>
* @parameter default-value="com.google.appengine" expression="${gwt.appEngineGroupId}"
* @since 2.1.0-1
*/
private String appEngineGroupId;
/**
* <p>
* groupId to download appengine sdk from maven repo
* </p>
* @parameter default-value="appengine-java-sdk" expression="${gwt.appEngineArtifactId}"
* @since 2.1.0-1
*/
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 expression="${gwt.bindAddress}"
*/
private String bindAddress;
public String getRunTarget()
{
return this.runTarget;
}
/**
* @return the GWT module to run (gwt 1.6+) -- expected to be unique
*/
public String getRunModule()
throws MojoExecutionException
{
String[] modules = getModules();
if ( noServer )
{
if (modules.length != 1)
{
getLog().error(
"Running in 'noserver' mode you must specify the single module to run using -Dgwt.module=..." );
throw new MojoExecutionException( "No single module specified" );
}
return modules[0];
}
if ( modules.length == 1 )
{
// A single module is set, no ambiguity
return modules[0];
}
int dash = runTarget.indexOf( '/' );
if ( dash > 0 )
{
return runTarget.substring( 0, dash );
}
// The runTarget MUST start with the full GWT module path
throw new MojoExecutionException(
"Unable to choose a GWT module to run. Please specify your module(s) in the configuration" );
}
/**
* @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;
}
protected String getFileName()
{
return "run";
}
public void doExecute( )
throws MojoExecutionException, MojoFailureException
{
try
{
JavaCommand cmd = new JavaCommand( "com.google.gwt.dev.DevMode" );
if ( gwtSdkFirstInClasspath )
{
cmd.withinClasspath( getGwtUserJar() ).withinClasspath( getGwtDevJar() );
}
cmd.withinScope( Artifact.SCOPE_RUNTIME );
addCompileSourceArtifacts( cmd );
if ( !gwtSdkFirstInClasspath )
{
cmd.withinClasspath( getGwtUserJar() ).withinClasspath( getGwtDevJar() );
}
cmd.arg( "-war", hostedWebapp.getAbsolutePath() )
.arg( "-gen", getGen().getAbsolutePath() )
.arg( "-logLevel", getLogLevel() )
.arg( "-port", Integer.toString( getPort() ) )
.arg( "-startupUrl", getStartupUrl() )
.arg( noServer, "-noserver" );
if ( server != null )
{
cmd.arg( "-server", server );
}
if ( whitelist != null && whitelist.length() > 0 )
{
cmd.arg( "-whitelist", whitelist );
}
if ( blacklist != null && blacklist.length() > 0 )
{
cmd.arg( "-blacklist", blacklist );
}
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 ( !noServer )
{
setupExplodedWar();
}
else
{
getLog().info( "noServer is set! Skipping exploding war file..." );
}
for ( String module : getModules() )
{
cmd.arg( module );
}
cmd.execute();
}
catch ( IOException 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 );
if ( !isAppEngine )
{
return;
}
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 );
}
}
}
}
private void setupExplodedWar()
throws MojoExecutionException
{
getLog().info( "create exploded Jetty webapp in " + hostedWebapp );
if ( copyWebapp && !warSourceDirectory.getAbsolutePath().equals( hostedWebapp.getAbsolutePath() ) )
{
try
{
String excludes = StringUtils.join( DEFAULTEXCLUDES, "," );
FileUtils.copyDirectory( warSourceDirectory, hostedWebapp, "**", excludes );
}
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 = getProjectArtifacts();
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 File getContextXml()
{
return this.contextXml;
}
public int getPort()
{
return this.port;
}
public File getTomcat()
{
return this.tomcat;
}
/**
* @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;
}
}