package org.ops4j.pax.construct.inherit;
/*
* Copyright 2007 Stuart McCulloch
*
* Licensed 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.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
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.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaSource;
/**
* Support mojo inheritance by merging the local plugin metadata with metadata from dependent plugins.
*
* Mojo inheritance is requested using a custom javadoc tag:
*
* <code><pre>
* ...
* @extendsPlugin archetype
* @goal create
* ...
* </pre></code>
*
* By default the current goal is taken as the goal to be extended. To extend a different goal use:
*
* <code><pre>
* ...
* @extendsPlugin archetype
* @extendsGoal create
* @goal create-project
* ...
* </pre></code>
*
* @goal inherit
* @phase compile
* @requiresDependencyResolution compile
*/
public class InheritMojo extends AbstractMojo
{
/**
* classic mojo tag
*/
private static final String GOAL = "goal";
/**
* new inheritance mojo tag
*/
private static final String EXTENDS_PLUGIN = "extendsPlugin";
/**
* new inheritance mojo tag
*/
private static final String EXTENDS_GOAL = "extendsGoal";
/**
* local plugin project
*
* @parameter expression="${project}"
* @readonly
*/
private MavenProject m_project;
/**
* output directory for the plugin project
*
* @parameter expression="${project.build.outputDirectory}"
* @readonly
*/
private File m_outputDirectory;
/**
* support for accessing archives
*
* @component
*/
private ArchiverManager m_archiverManager;
/**
* support for artifact resolution
*
* @parameter default-value="${localRepository}" alias="local.repository"
*/
ArtifactRepository m_localRepository;
/**
* support for artifact resolution
*
* @component
*/
ArtifactResolver m_resolver;
/**
* Maven plugin entry-point
*/
public void execute()
throws MojoExecutionException
{
// pre-load available plugin metadata - local and dependencies
PluginXml targetPlugin = loadPluginMetadata( m_outputDirectory );
Map dependentPluginsByName = loadDependentPluginMetaData();
// select maven source directories
JavaDocBuilder builder = new JavaDocBuilder();
for( Iterator i = m_project.getCompileSourceRoots().iterator(); i.hasNext(); )
{
builder.addSourceTree( new File( (String) i.next() ) );
}
// scan local source for javadoc tags
JavaSource[] javaSources = builder.getSources();
for( int i = 0; i < javaSources.length; i++ )
{
JavaClass mojoClass = javaSources[i].getClasses()[0];
// need plugin inheritance
DocletTag extendsTag = mojoClass.getTagByName( EXTENDS_PLUGIN );
if( null != extendsTag )
{
String pluginName = extendsTag.getValue();
getLog().info( "Extending " + pluginName + " plugin" );
// lookup using simple plugin name (ie. compiler, archetype, etc.)
PluginXml superPlugin = (PluginXml) dependentPluginsByName.get( pluginName );
if( null == superPlugin )
{
throw new MojoExecutionException( pluginName + " plugin is not a dependency" );
}
else
{
mergePluginMojo( mojoClass, targetPlugin, superPlugin );
}
}
}
try
{
targetPlugin.write();
}
catch( IOException e )
{
throw new MojoExecutionException( "cannot update local plugin metadata", e );
}
}
/**
* Loads plugin metadata for the given plugin location
*
* @param pluginDir root directory for the compiled plugin
* @return plugin metadata
* @throws MojoExecutionException
*/
private PluginXml loadPluginMetadata( File pluginDir )
throws MojoExecutionException
{
File metadata = new File( pluginDir, "META-INF/maven/plugin.xml" );
try
{
return new PluginXml( metadata );
}
catch( IOException e )
{
throw new MojoExecutionException( "cannot read plugin metadata " + metadata, e );
}
catch( XmlPullParserException e )
{
throw new MojoExecutionException( "cannot parse plugin metadata " + metadata, e );
}
}
/**
* Loads plugin metadata for all plugins found in this project's dependencies
*
* @return mapping from simple plugin name to plugin metadata
* @throws MojoExecutionException
*/
private Map loadDependentPluginMetaData()
throws MojoExecutionException
{
File buildArea = new File( m_project.getBuild().getDirectory(), "plugins" );
Map pluginsByName = new HashMap();
for( Iterator i = m_project.getDependencyArtifacts().iterator(); i.hasNext(); )
{
Artifact artifact = (Artifact) i.next();
if( "maven-plugin".equals( artifact.getType() ) )
{
resolve( artifact );
if ( artifact.getFile() == null )
{
continue;
}
File unpackDir = new File( buildArea, artifact.getArtifactId() );
unpackPlugin( artifact, unpackDir );
// extract simple plugin name by applying the standard maven naming rules in reverse
String name = artifact.getArtifactId().replaceAll( "(?:maven-)?(\\w+)(?:-maven)?-plugin", "$1" );
PluginXml pluginXml = loadPluginMetadata( unpackDir );
pluginsByName.put( artifact.getArtifactId(), pluginXml );
pluginsByName.put( name, pluginXml ); // short form
}
}
return pluginsByName;
}
private void resolve(Artifact artifact)
{
try
{
m_resolver.resolve(artifact, m_project.getRemoteArtifactRepositories(), m_localRepository);
}
catch ( ArtifactResolutionException e )
{
return;
}
catch (ArtifactNotFoundException e)
{
return;
}
}
/**
* Unpacks a maven plugin artifact to the given directory
*
* @param artifact maven plugin
* @param unpackDir directory to unpack to
* @throws MojoExecutionException
*/
private void unpackPlugin( Artifact artifact, File unpackDir )
throws MojoExecutionException
{
File pluginFile = artifact.getFile();
unpackDir.mkdirs();
try
{
UnArchiver unArchiver = m_archiverManager.getUnArchiver( pluginFile );
unArchiver.setDestDirectory( unpackDir );
unArchiver.setSourceFile( pluginFile );
unArchiver.extract();
}
catch( NoSuchArchiverException e )
{
throw new MojoExecutionException( "cannot find unarchiver for " + pluginFile, e );
}
catch( IOException e )
{
throw new MojoExecutionException( "problem reading file " + pluginFile, e );
}
catch( ArchiverException e )
{
throw new MojoExecutionException( "problem unpacking file " + pluginFile, e );
}
}
/**
* Inherits a mojo descriptor from a dependent plugin and merge it with the local plugin metadata
*
* @param mojoClass local mojo code requiring inheritance
* @param targetPlugin local plugin metadata
* @param superPlugin plugin metadata being extended
* @throws MojoExecutionException
*/
private void mergePluginMojo( JavaClass mojoClass, PluginXml targetPlugin, PluginXml superPlugin )
throws MojoExecutionException
{
DocletTag goalTag = mojoClass.getTagByName( GOAL );
if( null == goalTag )
{
return;
}
DocletTag superGoalTag = mojoClass.getTagByName( EXTENDS_GOAL );
if( null == superGoalTag )
{
superGoalTag = goalTag;
}
String goal = goalTag.getValue();
String superGoal = superGoalTag.getValue();
getLog().info( superGoal + " => " + goal );
Xpp3Dom targetMojoXml = targetPlugin.findMojo( goal );
Xpp3Dom superMojoXml = superPlugin.findMojo( superGoal );
if( null == superMojoXml )
{
throw new MojoExecutionException( "cannot find " + superGoal + " goal in " + superPlugin );
}
PluginXml.mergeMojo( targetMojoXml, superMojoXml );
}
}