/**
* Flexmojos is a set of maven goals to allow maven users to compile, optimize and test Flex SWF, Flex SWC, Air SWF and Air SWC.
* Copyright (C) 2008-2012 Marvin Froeder <marvin@flexmojos.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.flexmojos.oss.plugin.htmlwrapper;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.codehaus.plexus.util.FileUtils;
import net.flexmojos.oss.plugin.AbstractMavenMojo;
import net.flexmojos.oss.plugin.utilities.FileInterpolationUtil;
import net.flexmojos.oss.plugin.utilities.MavenUtils;
import eu.cedarsoft.utils.ZipExtractor;
/**
* This goal generate the html wrapper to Flex applications, like what is done by flex builder.
*
* @author Marvin Herman Froeder (velo.br@gmail.com)
* @since 1.0
* @phase generate-resources
* @goal wrapper
* @author marvin
*/
public class HtmlWrapperMojo
extends AbstractMavenMojo
{
private static final String INDEX_TEMPLATE_HTML = "index.template.html";
/**
* final name of html file<br/>
* <br/>
* This is now deprecated, and only supplied for backwards compatibility.
*
* @parameter default-value="${project.build.finalName}"
* @deprecated
*/
private String htmlName;
/**
* output Directory to store final html <br/>
* <br/>
* This is ignored if running in project with war packaging.
*
* @parameter default-value="${project.build.directory}"
*/
private File outputDirectory;
/**
* Used to define parameters that will be replaced. Usage:
*
* <pre>
* <parameters>
* <swf>${build.finalName}</swf>
* <width>100%</width>
* <height>100%</height>
* </parameters>
* </pre>
*
* The following prameters wil be injected if not defined:
* <ul>
* title
* </ul>
* <ul>
* version_major
* </ul>
* <ul>
* version_minor
* </ul>
* <ul>
* version_revision
* </ul>
* <ul>
* swf
* </ul>
* <ul>
* width
* </ul>
* <ul>
* height
* </ul>
* <ul>
* bgcolor
* </ul>
* <ul>
* application
* </ul>
* If you are using a custom template, and wanna some extra parameters, this is the right place to define it. <br/>
* <br/>
* This is ignored if running in project with war packaging.
*
* @parameter
*/
private Map<String, String> parameters;
/**
* @component
*/
private ProjectBuilder projectBuilder;
/**
* An external pom that provides wrapper parameters in place of the current one.
*/
private MavenProject sourceProject;
/**
* specifies the version of the player the application is targeting. Features requiring a later version will not be
* compiled into the application. The minimum value supported is "9.0.0". If not defined will take the default value
* from current playerglobal dependency.
*
* @parameter
*/
private String targetPlayer;
/**
* Files to not interpolate while copying files. Usually binary files. Accepts wild cards. By default, many common
* binary formats are excluded (see useDefaultBinaryExcludes). Usage:
*
* <pre>
* <templateExclusions>
* <String>**/*.xml</String>
* <String>some-directory/</String>
* <String>another-directory/**/*.jsp</String>
* </templateExclusions>
* </pre>
*
* In the above, the following applies in order:
* <ol>
* <li>Exclude all xml files</li>
* <li>Exclude everything in the directory 'some-directory'</li>
* <li>Exclude all jsp files in the directory 'another-directory'</li>
* </ol>
*
* @parameter
*/
private String[] templateExclusions;
/**
* Files to interpolate while copying files. Accepts wild cards. By default includes all files. Any patterns defined
* in templateExclusions, the default binaries excludes, or default plexus excludes (svn, cvs, temp files, etc) will
* be applied on top of this, so matching a pattern here does not force that file to be wrapped. Usage:
*
* <pre>
* <templateExclusions>
* <String>**/*.xml</String>
* <String>some-directory/</String>
* <String>another-directory/**/*.jsp</String>
* </templateExclusions>
* </pre>
*
* In the above, the following applies in order:
* <ol>
* <li>Include all xml files</li>
* <li>Include everything in the directory 'some-directory'</li>
* <li>Include all jsp files in the directory 'another-directory'</li>
* </ol>
*
* @parameter
*/
private String[] templateInclusions;
/**
* output Directory to store final html <br/>
* <br/>
* This is ignored if running in project with war packaging.
*
* @parameter default-value="${project.build.directory}/html-wrapper-template"
*/
private File templateOutputDirectory;
/**
* The template URI.
* <p>
* You can point to a zip file, a folder or use one of the following embed templates:
* <ul>
* embed:client-side-detection
* </ul>
* <ul>
* embed:client-side-detection-with-history
* </ul>
* <ul>
* embed:express-installation
* </ul>
* <ul>
* embed:express-installation-with-history
* </ul>
* <ul>
* embed:no-player-detection
* </ul>
* <ul>
* embed:no-player-detection-with-history
* </ul>
* To point to a zip file you must use a URI like this:
*
* <pre>
* zip:/myTemplateFolder/template.zip
* zip:c:/myTemplateFolder/template.zip
* </pre>
*
* To point to a folder use a URI like this:
*
* <pre>
* folder:/myTemplateFolder/
* folder:c:/myTemplateFolder/
* </pre>
* <p>
* This mojo will look for <tt>index.template.html</tt> for replace parameters. <br/>
* <br/>
* This is ignored if running in project with war packaging.
*
* @parameter default-value="embed:express-installation-with-history"
*/
private String templateURI;
/**
* Controls whether or not common binary file types are excluded by default when choosing what files to wrap. Useful
* to set to false if for some reason you decide to name a wrapped file something like "index.exe" or
* "html-wrapper.png" for some unanticipated reason.
*
* @parameter default-value="true"
*/
private boolean useDefaultBinaryExcludes;
/**
* In the context of a war project, this specifies the external artifact that the wrapper parameters will be
* extracted from. Usage:
*
* <pre>
* <wrapperArtifact>
* <groupId>com.company</groupId>
* <artifactId>some-project</artifactId>
* <version>3.2.7%</version>
* <classifier>prod%</classifier>
* </wrapperArtifact>
* </pre>
*
* Both groupId and artifactId are required, but version and classifier are optional and can be inferred from a
* dependency if present (for example when the copy-flex-resources goal is executed).
*
* @parameter
*/
private Map<String, String> wrapperArtifact;
private Artifact convertToArtifact( Dependency dependency )
{
return repositorySystem.createArtifactWithClassifier( dependency.getGroupId(), dependency.getArtifactId(),
dependency.getVersion(), dependency.getType(),
dependency.getClassifier() );
}
private void copyEmbedTemplate( String path )
throws MojoExecutionException
{
URL url = getClass().getResource( "/templates/wrapper/" + path + ".zip" );
File template = new File( templateOutputDirectory, "template.zip" );
try
{
FileUtils.copyURLToFile( url, template );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to copy template to: " + template, e );
}
extractZipTemplate( templateOutputDirectory, template );
}
private void copyFolderTemplate( String path )
throws MojoExecutionException
{
File source = new File( path );
if ( !source.isAbsolute() )
{
source = new File( project.getBasedir(), path );
}
if ( !source.exists() || !source.isDirectory() )
{
throw new MojoExecutionException( "Template folder doesn't exists. " + source );
}
try
{
FileUtils.copyDirectoryStructure( source, templateOutputDirectory );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to copy template to: " + templateOutputDirectory, e );
}
}
private void copyIndexTemplate()
throws MojoExecutionException
{
File indexTemplate = new File( templateOutputDirectory, INDEX_TEMPLATE_HTML );
if ( !indexTemplate.isFile() )
{
getLog().debug( "No index.template.html" );
return;
}
File index = new File( outputDirectory, htmlName + ".html" );
try
{
FileInterpolationUtil.copyFile( indexTemplate, index, parameters );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to write " + index, e );
}
}
private void copySurroundingFiles()
throws MojoExecutionException
{
try
{
FileInterpolationUtil.copyDirectory( templateOutputDirectory, outputDirectory, parameters,
templateExclusions, templateInclusions, useDefaultBinaryExcludes );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to create templates.", e );
}
// XXX shouldn't copy template, but there isn't a fast fix for that right know
File template = new File( outputDirectory, INDEX_TEMPLATE_HTML );
if ( template.exists() )
{
template.delete();
}
}
private void copyZipTemplate( String path )
throws MojoExecutionException
{
File source = new File( path );
if ( !source.exists() || !source.isFile() )
{
throw new MojoExecutionException( "Zip template doesn't exists. " + source );
}
extractZipTemplate( templateOutputDirectory, source );
}
public void execute()
throws MojoExecutionException, MojoFailureException
{
String packaging = project.getPackaging();
if ( !"swf".equals( packaging ) )
{
loadExternalParams();
if ( "war".equals( packaging ) )
{
rewireForWar();
}
}
executeInternal();
}
private void executeInternal()
throws MojoExecutionException, MojoFailureException
{
getLog().info( "flexmojos " + MavenUtils.getFlexMojosVersion()
+ " - GNU GPL License (NO WARRANTY) - See COPYRIGHT file" );
init();
extractTemplate();
copySurroundingFiles();
copyIndexTemplate();
}
private void extractTemplate()
throws MojoExecutionException
{
getLog().info( "Extracting template" );
templateOutputDirectory.mkdirs();
URI uri;
try
{
if ( MavenUtils.isWindows() )
{
// Shake bars to avoid URI syntax problems
templateURI = templateURI.replace( '\\', '/' );
}
templateURI = URIUtil.encodePath( templateURI );
uri = new URI( templateURI );
}
catch ( Exception e )
{
throw new MojoExecutionException( "Invalid template URI.", e );
}
String scheme = uri.getScheme();
if ( "embed".equals( scheme ) )
{
copyEmbedTemplate( uri.getSchemeSpecificPart() );
}
else if ( "zip".equals( scheme ) )
{
copyZipTemplate( uri.getSchemeSpecificPart() );
}
else if ( "folder".equals( scheme ) )
{
copyFolderTemplate( uri.getSchemeSpecificPart() );
}
else
{
throw new MojoExecutionException( "Invalid URI scheme: " + scheme );
}
}
private void extractZipTemplate( File outputDir, File template )
throws MojoExecutionException
{
try
{
ZipExtractor ze = new ZipExtractor( template );
ze.extract( outputDir );
}
catch ( IOException e )
{
throw new MojoExecutionException( "An error happens when trying to extract html-template.", e );
}
}
/*
* Copied from CopyMojo... move to net.flexmojos.oss.utilities.MavenUtils?
*/
private Artifact findArtifact( MavenProject project, String groupId, String artifactId, String version,
String type, String classifier )
throws MojoExecutionException
{
// Dependencies must be traversed instead of artifacts here because of the execution phase of the mojo
List<Dependency> dependencies = project.getDependencies();
for ( Dependency dependency : dependencies )
{
String matchGroupId = dependency.getGroupId();
String matchArtifactId = dependency.getArtifactId();
String matchType = dependency.getType();
if ( groupId.equals( matchGroupId ) && artifactId.equals( matchArtifactId ) && type.equals( matchType ) )
{
if ( version != null )
{
String matchVersion = dependency.getVersion();
if ( version.equals( matchVersion ) )
{
if ( classifier != null )
{
String matchClassifier = dependency.getClassifier();
if ( classifier.equals( matchClassifier ) )
{
return convertToArtifact( dependency );
}
else
{
getLog().warn( "Wrapper found matching artifact with classifier [" + matchClassifier
+ "], but did not match requested classifier [" + classifier
+ "] so it is being ignored" );
}
}
else
{
return convertToArtifact( dependency );
}
}
else
{
getLog().warn( "Wrapper found matching artifact with version [" + matchVersion
+ "], but did not match requested version [" + version
+ "] so it is being ignored" );
}
}
else
{
return convertToArtifact( dependency );
}
}
}
return null;
}
private void init()
{
/*
* If sourceProject is defined, then parameters are from an external project and that project (sourceProject)
* should be used as reference for default values rather than this project.
*/
MavenProject project = this.project;
if ( sourceProject != null )
{
project = sourceProject;
}
if ( parameters == null )
{
parameters = new HashMap<String, String>();
}
if ( !parameters.containsKey( "title" ) )
{
parameters.put( "title", project.getName() );
}
String[] nodes = targetPlayer != null ? targetPlayer.split( "\\." ) : new String[] { "9", "0", "0" };
if ( !parameters.containsKey( "version_major" ) )
{
parameters.put( "version_major", nodes[0] );
}
if ( !parameters.containsKey( "version_minor" ) )
{
parameters.put( "version_minor", nodes[1] );
}
if ( !parameters.containsKey( "version_revision" ) )
{
parameters.put( "version_revision", nodes[2] );
}
if ( !parameters.containsKey( "swf" ) )
{
parameters.put( "swf", project.getBuild().getFinalName() );
}
if ( !parameters.containsKey( "width" ) )
{
parameters.put( "width", "100%" );
}
if ( !parameters.containsKey( "height" ) )
{
parameters.put( "height", "100%" );
}
if ( !parameters.containsKey( "application" ) )
{
parameters.put( "application", project.getArtifactId() );
}
if ( !parameters.containsKey( "bgcolor" ) )
{
parameters.put( "bgcolor", "#869ca7" );
}
}
/**
* Loads the parameters value (from plugin configuration) from an externally referenced dependency pom rather than
* the pom for the current project.
*
* @throws MojoExecutionException
* @throws MojoFailureException
*/
private void loadExternalParams()
throws MojoExecutionException
{
Artifact sourceArtifact;
if ( wrapperArtifact != null )
{
String groupId = wrapperArtifact.get( "groupId" );
String artifactId = wrapperArtifact.get( "artifactId" );
String version = wrapperArtifact.get( "version" );
String classifier = wrapperArtifact.get( "classifier" );
if ( groupId == null || artifactId == null )
{
throw new MojoExecutionException(
"Both groupId and artifactId are required within the wrapperArtifact configuration " );
}
// Version is optional at this point
Artifact swfArtifact = findArtifact( project, groupId, artifactId, version, "swf", classifier );
if ( swfArtifact != null )
{
// Found matching dependency, so use this as the basis for the target external pom artifact
sourceArtifact =
repositorySystem.createArtifactWithClassifier( groupId, artifactId, swfArtifact.getVersion(),
"pom", swfArtifact.getClassifier() );
}
else
{
// Could not find a matching dependency, so try to build from scratch
if ( version == null )
{
throw new MojoExecutionException(
"Can't find a matching swf dependency, and no version was provided. "
+ "Therefore, no external artifact can be located to wrap" );
}
sourceArtifact =
repositorySystem.createArtifactWithClassifier( groupId, artifactId, version, "pom", classifier );
}
}
else
{
throw new MojoExecutionException(
"The wrapperArtifact configuartion is required when wrapping an external swf " );
}
getLog().info( "Wrapping with external artifact: " + sourceArtifact.toString() );
resolveArtifact( sourceArtifact );
this.sourceProject = loadProject( sourceArtifact );
// Does source pom contain flexmojos plugin?
Map<String, Plugin> sourcePlugins = sourceProject.getBuild().getPluginsAsMap();
Plugin sourceFlexmojos = sourcePlugins.get( "net.flexmojos.oss:flexmojos-maven-plugin" );
if ( sourceFlexmojos == null )
{
throw new MojoExecutionException( "Could not locate flexmojos plugin in wrapper source pom" );
}
this.parameters = MavenPluginUtil.extractParameters( sourceFlexmojos );
}
/**
* Tries to construct project for the provided artifact
*
* @param artifact
* @return MavenProject for the given artifact
* @throws MojoExecutionException
*/
private MavenProject loadProject( Artifact artifact )
throws MojoExecutionException
{
try
{
ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
request.setLocalRepository( localRepository );
request.setRemoteRepositories( remoteRepositories );
request.setResolveDependencies( true );
request.setRepositorySession( session.getRepositorySession() );
return projectBuilder.build( artifact, request ).getProject();
}
catch ( ProjectBuildingException ex )
{
throw new MojoExecutionException( "Problems building project for: " + artifact.getId(), ex );
}
}
/**
* Attempts to find the provided artifact within the current repository path
*
* @param artifact
* @throws MojoExecutionException
*/
private void resolveArtifact( Artifact artifact )
throws MojoExecutionException
{
if ( !artifact.isResolved() )
{
ArtifactResolutionRequest req = new ArtifactResolutionRequest();
req.setArtifact( artifact );
req.setLocalRepository( localRepository );
req.setRemoteRepositories( remoteRepositories );
// FIXME need to check isSuccess
repositorySystem.resolve( req ).isSuccess();
}
}
/**
* Insert flexmojos wrapper process into maven-war-plugin's process by re-routing its warSourceDirectory
* configuration to this.outputDirectory and using its original warSourceDirectory as the value for this.templateURI
*
* @throws MojoExecutionException
* @throws MojoFailureException
*/
private void rewireForWar()
throws MojoExecutionException
{
// Fetch war plugin configuration
Map<String, Plugin> plugins = project.getBuild().getPluginsAsMap();
Plugin warPlugin = plugins.get( "org.apache.maven.plugins:maven-war-plugin" );
if ( warPlugin == null )
{
throw new MojoExecutionException( "Flexmojos HtmlWrapperMojo could not find the war plugin" );
}
Xpp3DomMap config = MavenPluginUtil.getParameters( warPlugin );
// Map this.templateURI to folder:{warPlugin.warSourceDirectory)
String warSourceDirectory = config.get( "warSourceDirectory" );
if ( warSourceDirectory == null )
{
warSourceDirectory = project.getBasedir() + "/src/main/webapp";
}
this.templateURI = "folder:" + warSourceDirectory;
// Map outputDirectory/templateOutputDirectory to warPlugin.workDirectory
// so that they don't get packaged in war accidentally
String workDirectory = config.get( "workDirectory" );
if ( workDirectory == null )
{
workDirectory = project.getBuild().getDirectory() + "/war/work";
}
this.templateOutputDirectory = new File( workDirectory, "extracted-template" );
this.outputDirectory = new File( workDirectory, "wrapped-template" );
// Map warPlugin.warSourceDirectory to this.outputDirectory
config.put( "warSourceDirectory", outputDirectory.getAbsolutePath() );
}
}