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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.mojo.gwt.AbstractGwtModuleMojo;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineTimeOutException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
/**
* Support running GWT SDK Tools as forked JVM with classpath set according to project source/resource directories and
* dependencies.
*
* @author ccollins
* @author cooper
* @author willpugh
* @version $Id$
*/
public abstract class AbstractGwtShellMojo
extends AbstractGwtModuleMojo
{
/**
* Location on filesystem where GWT will write generated content for review (-gen option to GWTCompiler).
* <p>
* Can be set from command line using '-Dgwt.gen=...'
* </p>
* @parameter default-value="${project.build.directory}/.generated" expression="${gwt.gen}"
*/
private File gen;
/**
* GWT logging level (-logLevel ERROR, WARN, INFO, TRACE, DEBUG, SPAM, or ALL).
* <p>
* Can be set from command line using '-Dgwt.logLevel=...'
* </p>
* @parameter default-value="INFO" expression="${gwt.logLevel}"
*/
private String logLevel;
/**
* GWT JavaScript compiler output style (-style OBF[USCATED], PRETTY, or DETAILED).
* <p>
* Can be set from command line using '-Dgwt.style=...'
* </p>
* @parameter default-value="OBF" expression="${gwt.style}"
*/
private String style;
/**
* Extra JVM arguments that are passed to the GWT-Maven generated scripts (for compiler, shell, etc - typically use
* -Xmx512m here, or -XstartOnFirstThread, etc).
* <p>
* Can be set from command line using '-Dgwt.extraJvmArgs=...', defaults to setting max Heap size to be large enough
* for most GWT use cases.
* </p>
*
* @parameter expression="${gwt.extraJvmArgs}" default-value="-Xmx512m"
*/
private String extraJvmArgs;
/**
* Option to specify the jvm (or path to the java executable) to use with the forking scripts. For the default, the
* jvm will be the same as the one used to run Maven.
*
* @parameter expression="${gwt.jvm}"
* @since 1.1
*/
private String jvm;
/**
* Forked process execution timeOut. Usefull to avoid maven to hang in continuous integration server.
*
* @parameter
*/
private int timeOut;
/**
*
* Artifacts to be included as source-jars in GWTCompiler Classpath. Removes the restriction that source code must
* be bundled inside of the final JAR when dealing with external utility libraries not designed exclusivelly for
* GWT. The plugin will download the source.jar if necessary.
*
* This option is a workaround to avoid packaging sources inside the same JAR when splitting and application into
* modules. A smaller JAR can then be used on server classpath and distributed without sources (that may not be
* desirable).
*
*
* @parameter
*/
private String[] compileSourcesArtifacts;
// methods
/**
* {@inheritDoc}
*
* @see org.apache.maven.plugin.Mojo#execute()
*/
public final void execute()
throws MojoExecutionException, MojoFailureException
{
doExecute();
}
public abstract void doExecute()
throws MojoExecutionException, MojoFailureException;
protected String getExtraJvmArgs()
{
return extraJvmArgs;
}
protected File getGen()
{
return this.gen;
}
protected String getLogLevel()
{
return this.logLevel;
}
protected String getStyle()
{
return this.style;
}
protected String getJvm()
{
return jvm;
}
/**
* hook to post-process the dependency-based classpath
*/
protected void postProcessClassPath( Collection<File> classpath )
throws MojoExecutionException
{
// Nothing to do in most case
}
private List<String> getJvmArgs()
{
List<String> extra = new ArrayList<String>();
String userExtraJvmArgs = getExtraJvmArgs();
if ( userExtraJvmArgs != null )
{
for ( String extraArg : userExtraJvmArgs.split( " " ) )
{
extra.add( extraArg );
}
}
return extra;
}
private String getJavaCommand()
throws MojoExecutionException
{
if ( StringUtils.isEmpty( jvm ) )
{
// use the same JVM as the one used to run Maven (the "java.home" one)
jvm = System.getProperty( "java.home" );
}
// does-it exists ? is-it a directory or a path to a java executable ?
File jvmFile = new File( jvm );
if ( !jvmFile.exists() )
{
throw new MojoExecutionException( "the configured jvm " + jvm
+ " doesn't exists please check your environnement" );
}
if ( jvmFile.isDirectory() )
{
// it's a directory we construct the path to the java executable
return jvmFile.getAbsolutePath() + File.separator + "bin" + File.separator + "java";
}
getLog().debug( "use jvm " + jvm );
return jvm;
}
/**
* @param timeOut the timeOut to set
*/
public void setTimeOut( int timeOut )
{
this.timeOut = timeOut;
}
/**
* Add sources.jar artifacts for project dependencies listed as compileSourcesArtifacts. This is a GWT hack to avoid
* packaging java source files into JAR when sharing code between server and client. Typically, some domain model
* classes or business rules may be packaged as a separate Maven module. With GWT packaging this requires to
* distribute such classes with code, that may not be desirable.
* <p>
* The hack can also be used to include utility code from external librariries that may not have been designed for
* GWT.
*/
protected void addCompileSourceArtifacts(JavaCommand cmd)
throws MojoExecutionException
{
if ( compileSourcesArtifacts == null )
{
return;
}
for ( String include : compileSourcesArtifacts )
{
List<String> parts = new ArrayList<String>();
parts.addAll( Arrays.asList(include.split(":")) );
if ( parts.size() == 2 )
{
// type is optional as it will mostly be "jar"
parts.add( "jar" );
}
String dependencyId = StringUtils.join( parts.iterator(), ":" );
boolean found = false;
for ( Artifact artifact : getProjectArtifacts() )
{
getLog().debug( "compare " + dependencyId + " with " + artifact.getDependencyConflictId() );
if ( artifact.getDependencyConflictId().equals( dependencyId ) )
{
getLog().debug( "Add " + dependencyId + " sources.jar artifact to compile classpath" );
Artifact sources =
resolve( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
"jar", "sources" );
cmd.withinClasspath( sources.getFile() );
found = true;
break;
}
}
if ( !found )
getLog().warn(
"Declared compileSourcesArtifact was not found in project dependencies " + dependencyId );
}
}
/**
* A plexus-util StreamConsumer to redirect messages to plugin log
*/
protected StreamConsumer out = new StreamConsumer()
{
public void consumeLine( String line )
{
getLog().info( line );
}
};
/**
* A plexus-util StreamConsumer to redirect errors to plugin log
*/
private StreamConsumer err = new StreamConsumer()
{
public void consumeLine( String line )
{
getLog().error( line );
}
};
/**
* @deprecated use the new {@link JavaCommand}
* Create a command to execute using builder pattern
*
* @author <a href="mailto:nicolas@apache.org">Nicolas De Loof</a>
*/
public class JavaCommand
{
private String className;
private List<File> classpath = new LinkedList<File>();
private List<String> args = new ArrayList<String>();
private Properties systemProperties = new Properties();
private Properties env = new Properties();
public JavaCommand( String className )
{
this.className = className;
}
public JavaCommand withinScope( String scope )
throws MojoExecutionException
{
classpath.addAll( getClasspath( scope ) );
postProcessClassPath( classpath );
return this;
}
public JavaCommand withinClasspath( File... path )
{
for ( File file : path )
{
classpath.add( file );
}
return this;
}
public JavaCommand arg( String arg )
{
args.add( arg );
return this;
}
public JavaCommand arg( String arg, String value )
{
args.add( arg );
args.add( value );
return this;
}
public JavaCommand arg( boolean condition, String arg )
{
if ( condition )
{
args.add( arg );
}
return this;
}
public JavaCommand systemProperty( String name, String value )
{
systemProperties.setProperty( name, value );
return this;
}
public JavaCommand environment( String name, String value )
{
env.setProperty( name, value );
return this;
}
public void execute()
throws MojoExecutionException
{
List<String> command = new ArrayList<String>();
command.addAll( getJvmArgs() );
command.add( "-classpath" );
List<String> path = new ArrayList<String>( classpath.size() );
for ( File file : classpath )
{
path.add( file.getAbsolutePath() );
}
command.add( StringUtils.join( path.iterator(), File.pathSeparator ) );
if ( systemProperties != null )
{
for ( Map.Entry entry : systemProperties.entrySet() )
{
command.add( "-D" + entry.getKey() + "=" + entry.getValue() );
}
}
command.add( className );
command.addAll( args );
try
{
String[] arguments = (String[]) command.toArray( new String[command.size()] );
// On windows, the default Shell will fall into command line length limitation issue
// On Unixes, not using a Shell breaks the classpath (NoClassDefFoundError:
// com/google/gwt/dev/Compiler).
Commandline cmd =
Os.isFamily( Os.FAMILY_WINDOWS ) ? new Commandline( new JavaShell() ) : new Commandline();
cmd.setExecutable( getJavaCommand() );
cmd.addArguments( arguments );
if ( env != null )
{
for ( Map.Entry entry : env.entrySet() )
{
getLog().debug( "add env " + (String) entry.getKey() + " with value " + (String) entry.getValue() );
cmd.addEnvironment( (String) entry.getKey(), (String) entry.getValue() );
}
}
getLog().debug( "Execute command :\n" + cmd.toString() );
int status;
if ( timeOut > 0 )
{
status = CommandLineUtils.executeCommandLine( cmd, out, err, timeOut );
}
else
{
status = CommandLineUtils.executeCommandLine( cmd, out, err );
}
if ( status != 0 )
{
throw new ForkedProcessExecutionException( "Command [[\n" + cmd.toString()
+ "\n]] failed with status " + status );
}
}
catch ( CommandLineTimeOutException e )
{
if ( timeOut > 0 )
{
getLog().warn( "Forked JVM has been killed on time-out after " + timeOut + " seconds" );
return;
}
throw new MojoExecutionException( "Time-out on command line execution :\n" + command, e );
}
catch ( CommandLineException e )
{
throw new MojoExecutionException( "Failed to execute command line :\n" + command, e );
}
}
public void withinClasspathFirst( File oophmJar )
{
classpath.add( 0, oophmJar );
}
}
}