git-svn-id: http://maven-alfresco-archetypes.googlecode.com/svn/trunk@127 04253f4f-3451-0410-a141-5562f1e59037
This commit is contained in:
mindthegab
2009-02-18 00:00:26 +00:00
parent 64d15e0bbd
commit 7bdb4e5d23
32 changed files with 5091 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<document>
<properties>
<title>Maven AMP plugin</title>
</properties>
<body>
<release version="2.0.0" date="2008-07-06" description="Refactored Public release">
<action dev="mindthegab" type="add">
Now consistent maven plugin properties usage and support for AMP overlays and full AMP lifecycle
</action>
<action dev="mindthegab" type="add">
Added UnArchiver to mimic MMT behavior. Not needed anymore as AMPs gets properly unpacked into a WAR artifact.
</action>
<action dev="mindthegab" type="add">
Added plugin site documentation
</action>
<action dev="mindthegab" type="add">
Deployed on maven repository: http://repository.sourcesense.com/maven2/org/alfresco/maven/plugins
</action>
</release>
</body>
</document>

View File

@@ -0,0 +1,765 @@
package org.alfresco.maven.plugin.amp;
/*
* 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 org.alfresco.maven.plugin.amp.overlay.OverlayManager;
import org.alfresco.maven.plugin.amp.packaging.AmpPackagingContext;
import org.alfresco.maven.plugin.amp.packaging.AmpPackagingTask;
import org.alfresco.maven.plugin.amp.packaging.AmpPostPackagingTask;
import org.alfresco.maven.plugin.amp.packaging.AmpProjectPackagingTask;
import org.alfresco.maven.plugin.amp.packaging.OverlayPackagingTask;
import org.alfresco.maven.plugin.amp.packaging.SaveAmpStructurePostPackagingTask;
import org.alfresco.maven.plugin.amp.util.AmpStructure;
import org.alfresco.maven.plugin.amp.util.AmpStructureSerializer;
import org.alfresco.maven.plugin.amp.util.CompositeMap;
import org.alfresco.maven.plugin.amp.util.PropertyUtils;
import org.alfresco.maven.plugin.amp.util.ReflectionProperties;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public abstract class AbstractAmpMojo extends AbstractMojo
{
/**
* Returns a string array of the classes and resources to be excluded from the jar excludes to be used
* when assembling/copying the AMP.
*
* @return an array of tokens to exclude
*/
protected String[] getExcludes()
{
List excludeList = new ArrayList();
if ( StringUtils.isNotEmpty( mAmpJarExcludes ) )
{
excludeList.addAll( Arrays.asList( StringUtils.split( mAmpJarExcludes, "," ) ) );
}
return (String[]) excludeList.toArray( EMPTY_STRING_ARRAY );
}
/**
* Returns a string array of the classes and resources to be included from the jar assembling/copying the war.
*
* @return an array of tokens to include
*/
protected String[] getIncludes()
{
return StringUtils.split( StringUtils.defaultString( mAmpJarIncludes ), "," );
}
/**
* Returns a string array of the resources to be included in the AMP web/ folder.
*
* @return an array of tokens to include
*/
protected String[] getWebIncludes()
{
return StringUtils.split( StringUtils.defaultString( mAmpWebIncludes ), "," );
}
/**
* Returns a string array of the resources to be excluded in the AMP web/ folder.
*
* @return an array of tokens to exclude
*/
protected String[] getWebExcludes()
{
List excludeList = new ArrayList();
if ( StringUtils.isNotEmpty( mAmpWebExcludes ) )
{
excludeList.addAll( Arrays.asList( StringUtils.split( mAmpWebExcludes, "," ) ) );
}
return (String[]) excludeList.toArray( EMPTY_STRING_ARRAY );
}
/**
* Returns a string array of the excludes to be used
* when adding dependent AMPs as an overlay onto this AMP.
*
* @return an array of tokens to exclude
*/
protected String[] getDependentAmpExcludes()
{
String[] excludes;
if ( StringUtils.isNotEmpty( dependentAmpExcludes ) )
{
excludes = StringUtils.split( dependentAmpExcludes, "," );
}
else
{
excludes = EMPTY_STRING_ARRAY;
}
return excludes;
}
/**
* Returns a string array of the includes to be used
* when adding dependent AMP as an overlay onto this AMP.
*
* @return an array of tokens to include
*/
protected String[] getDependentAmpIncludes()
{
return StringUtils.split( StringUtils.defaultString( dependentAmpIncludes ), "," );
}
public void buildExplodedAmp( File webappDirectory )
throws MojoExecutionException, MojoFailureException
{
webappDirectory.mkdirs();
try
{
buildAmp( mProject, webappDirectory );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Could not build AMP", e );
}
}
/**
* Builds the webapp for the specified project with the new packaging task
* thingy
* <p/>
* Classes, libraries and tld files are copied to
* the <tt>webappDirectory</tt> during this phase.
*
* @param project the maven project
* @param webappDirectory the target directory
* @throws MojoExecutionException if an error occured while packaging the webapp
* @throws MojoFailureException if an unexpected error occured while packaging the webapp
* @throws IOException if an error occured while copying the files
*/
public void buildAmp( MavenProject project, File webappDirectory )
throws MojoExecutionException, MojoFailureException, IOException
{
AmpStructure cache;
if ( mUseCache && mCacheFile.exists() )
{
cache = new AmpStructure( webappStructureSerialier.fromXml( mCacheFile ) );
}
else
{
cache = new AmpStructure( null );
}
final long startTime = System.currentTimeMillis();
getLog().info( "Assembling AMP [" + project.getArtifactId() + "] in [" + webappDirectory + "]" );
final OverlayManager overlayManager =
new OverlayManager( mOverlays, project, dependentAmpIncludes, dependentAmpExcludes );
final List packagingTasks = getPackagingTasks( overlayManager );
final AmpPackagingContext context = new DefaultAmpPackagingContext( webappDirectory, cache, overlayManager );
final Iterator it = packagingTasks.iterator();
while ( it.hasNext() )
{
AmpPackagingTask ampPackagingTask = (AmpPackagingTask) it.next();
ampPackagingTask.performPackaging( context );
}
// Post packaging
final List postPackagingTasks = getPostPackagingTasks();
final Iterator it2 = postPackagingTasks.iterator();
while ( it2.hasNext() )
{
AmpPostPackagingTask task = (AmpPostPackagingTask) it2.next();
task.performPostPackaging( context );
}
getLog().info( "AMP assembled in[" + ( System.currentTimeMillis() - startTime ) + " msecs]" );
}
/**
* Returns a <tt>List</tt> of the {@link org.alfresco.maven.plugin.amp.packaging.AmpPackagingTask}
* instances to invoke to perform the packaging.
*
* @param overlayManager the overlay manager
* @return the list of packaging tasks
* @throws MojoExecutionException if the packaging tasks could not be built
*/
private List getPackagingTasks( OverlayManager overlayManager )
throws MojoExecutionException
{
final List packagingTasks = new ArrayList();
final List resolvedOverlays = overlayManager.getOverlays();
final Iterator it = resolvedOverlays.iterator();
while ( it.hasNext() )
{
Overlay overlay = (Overlay) it.next();
if ( overlay.isCurrentProject() )
{
packagingTasks.add( new AmpProjectPackagingTask( mAmpResources, mModuleProperties) );
}
else
{
packagingTasks.add( new OverlayPackagingTask( overlay ) );
}
}
return packagingTasks;
}
/**
* Returns a <tt>List</tt> of the {@link org.alfresco.maven.plugin.amp.packaging.AmpPostPackagingTask}
* instances to invoke to perform the post-packaging.
*
* @return the list of post packaging tasks
*/
private List getPostPackagingTasks()
{
final List postPackagingTasks = new ArrayList();
if ( mUseCache )
{
postPackagingTasks.add( new SaveAmpStructurePostPackagingTask( mCacheFile ) );
}
// TODO add lib scanning to detect duplicates
return postPackagingTasks;
}
/**
* The maven project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject mProject;
/**
* The directory containing generated classes.
*
* @parameter expression="${project.build.outputDirectory}"
* @required
* @readonly
*/
private File mClassesDirectory;
/**
* The Jar archiver needed for archiving classes directory into jar file under WEB-INF/lib.
*
* @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
* @required
*/
private JarArchiver mJarArchiver;
/**
* The directory where the webapp is built.
*
* @parameter expression="${project.build.directory}/${project.build.finalName}"
* @required
*/
private File mAmpDirectory;
/**
* Single directory for extra files to include in the AMP.
*
* @parameter expression="${project.build.outputDirectory}"
* @required
*/
private File mAmpConfigDirectory;
/**
* Single directory for extra files to include in the AMP.
*
* @parameter expression="${basedir}/src/main/webapp"
* @required
*/
private File mAmpWebDirectory;
/**
* The list of webResources we want to transfer.
*
* @parameter
*/
private Resource[] mAmpResources;
/**
* Filters (property files) to include during the interpolation of the pom.xml.
*
* @parameter expression="${project.build.filters}"
*/
private List filters;
/**
* The path to the web.xml file to use.
*
* @parameter expression="${maven.amp.moduleProperties}" default-value="${project.basedir}/module.properties"
*/
private File mModuleProperties;
/**
* Directory to unpack dependent AMPs into if needed
*
* @parameter expression="${project.build.directory}/amp/work"
* @required
*/
private File mWorkDirectory;
/**
* The file name mapping to use to copy libraries and tlds. If no file mapping is
* set (default) the file is copied with its standard name.
*
* @parameter
* @since 2.0.3
*/
private String mOutputFileNameMapping;
/**
* The file containing the webapp structure cache.
*
* @parameter expression="${project.build.directory}/amp/work/amp-cache.xml"
* @required
* @since 2.1
*/
private File mCacheFile;
/**
* Whether the cache should be used to save the status of the webapp
* accross multiple runs.
*
* @parameter expression="${useCache}" default-value="true"
* @since 2.1
*/
private boolean mUseCache = true;
/**
* To look up Archiver/UnArchiver implementations
*
* @parameter expression="${component.org.codehaus.plexus.archiver.manager.ArchiverManager}"
* @required
*/
protected ArchiverManager mArchiverManager;
private static final String META_INF = "META-INF";
public static final String DEFAULT_FILE_NAME_MAPPING_CLASSIFIER =
"${artifactId}-${version}-${classifier}.${extension}";
public static final String DEFAULT_FILE_NAME_MAPPING = "${artifactId}-${version}.${extension}";
/**
* The comma separated list of tokens to include in the AMP internal JAR. Default **.
* Default is '**'.
*
* @parameter alias="includes"
*/
private String mAmpJarIncludes = "**";
/**
* The comma separated list of tokens to exclude from the AMP created JAR file. By default module configuration is left outside jars.
*
* @parameter alias="excludes" default-value="alfresco/module/**"
*/
private String mAmpJarExcludes;
/**
* The comma separated list of tokens to include in the AMP internal JAR. Default **.
* Default is '**'.
*
* @parameter alias="webIncludes" default-value="**"
*/
private String mAmpWebIncludes;
/**
* The comma separated list of tokens to exclude from the AMP created JAR file. By default module configuration is left outside jars.
*
* @parameter alias="webExcludes"
*/
private String mAmpWebExcludes;
/**
* The comma separated list of tokens to include when doing
* a AMP overlay.
* Default is '**'
*
* @parameter
*/
private String dependentAmpIncludes = "**/**";
/**
* The comma separated list of tokens to exclude when doing
* a AMP overlay.
*
* @parameter
*/
private String dependentAmpExcludes = "META-INF/**";
/**
* The overlays to apply.
*
* @parameter
* @since 2.1
*/
private List mOverlays = new ArrayList();
/**
* The maven archive configuration to use.
*
* @parameter
*/
protected MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
private static final String[] EMPTY_STRING_ARRAY = {};
private final AmpStructureSerializer webappStructureSerialier = new AmpStructureSerializer();
// AMP packaging implementation
private class DefaultAmpPackagingContext
implements AmpPackagingContext
{
private final AmpStructure webappStructure;
private final File mAmpDirectory;
private final OverlayManager overlayManager;
public DefaultAmpPackagingContext( File webappDirectory, final AmpStructure webappStructure,
final OverlayManager overlayManager )
{
this.mAmpDirectory = webappDirectory;
this.webappStructure = webappStructure;
this.overlayManager = overlayManager;
// This is kinda stupid but if we loop over the current overlays and we request the path structure
// it will register it. This will avoid wrong warning messages in a later phase
final Iterator it = overlayManager.getOverlayIds().iterator();
while ( it.hasNext() )
{
String overlayId = (String) it.next();
webappStructure.getStructure( overlayId );
}
}
public MavenProject getProject()
{
return mProject;
}
public File getAmpDirectory()
{
return mAmpDirectory;
}
public File getClassesDirectory()
{
return mClassesDirectory;
}
public Log getLog()
{
return AbstractAmpMojo.this.getLog();
}
public String getOutputFileNameMapping()
{
return mOutputFileNameMapping;
}
public File getAmpWebDirectory()
{
return mAmpWebDirectory;
}
public String[] getAmpJarIncludes()
{
return getIncludes();
}
public String[] getAmpJarExcludes()
{
return getExcludes();
}
public File getOverlaysWorkDirectory()
{
return mWorkDirectory;
}
public ArchiverManager getArchiverManager()
{
return mArchiverManager;
}
public MavenArchiveConfiguration getArchive()
{
return archive;
}
public JarArchiver getJarArchiver()
{
return mJarArchiver;
}
public List getFilters()
{
return filters;
}
public Map getFilterProperties()
throws MojoExecutionException
{
Map filterProperties = new Properties();
// System properties
filterProperties.putAll( System.getProperties() );
// Project properties
filterProperties.putAll( mProject.getProperties() );
for ( Iterator i = filters.iterator(); i.hasNext(); )
{
String filtersfile = (String) i.next();
try
{
Properties properties = PropertyUtils.loadPropertyFile( new File( filtersfile ), true, true );
filterProperties.putAll( properties );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error loading property file '" + filtersfile + "'", e );
}
}
// can't putAll, as ReflectionProperties doesn't enumerate - so we make a composite map with the project variables as dominant
return new CompositeMap( new ReflectionProperties( mProject ), filterProperties );
}
public AmpStructure getAmpStructure()
{
return webappStructure;
}
public List getOwnerIds()
{
return overlayManager.getOverlayIds();
}
/**
* @see org.alfresco.maven.plugin.amp.packaging.AmpPackagingContext#getAmpConfigDirectory()
*/
public File getAmpConfigDirectory()
{
return mAmpConfigDirectory;
}
public String[] getAmpWebExcludes() {
return getWebExcludes();
}
public String[] getAmpWebIncludes() {
return getWebIncludes();
}
}
public MavenProject getProject()
{
return mProject;
}
public void setProject( MavenProject project )
{
this.mProject = project;
}
public File getClassesDirectory()
{
return mClassesDirectory;
}
public void setClassesDirectory( File classesDirectory )
{
this.mClassesDirectory = classesDirectory;
}
public File getAmpDirectory()
{
return mAmpDirectory;
}
public void setAmpDirectory( File webappDirectory )
{
this.mAmpDirectory = webappDirectory;
}
public File getAmpSourceDirectory()
{
return mAmpWebDirectory;
}
public void setAmpSourceDirectory( File ampSourceDirectory )
{
this.mAmpWebDirectory = ampSourceDirectory;
}
public File getWebXml()
{
return mModuleProperties;
}
public void setWebXml( File webXml )
{
this.mModuleProperties = webXml;
}
public String getOutputFileNameMapping()
{
return mOutputFileNameMapping;
}
public void setOutputFileNameMapping( String outputFileNameMapping )
{
this.mOutputFileNameMapping = outputFileNameMapping;
}
public List getOverlays()
{
return mOverlays;
}
public void setOverlays( List overlays )
{
this.mOverlays = overlays;
}
public void addOverlay( Overlay overlay )
{
mOverlays.add( overlay );
}
public JarArchiver getJarArchiver()
{
return mJarArchiver;
}
public void setJarArchiver( JarArchiver jarArchiver )
{
this.mJarArchiver = jarArchiver;
}
public Resource[] getAmpResources()
{
return mAmpResources;
}
public void setAmpResources( Resource[] webResources )
{
this.mAmpResources = webResources;
}
public List getFilters()
{
return filters;
}
public void setFilters( List filters )
{
this.filters = filters;
}
public File getWorkDirectory()
{
return mWorkDirectory;
}
public void setWorkDirectory( File workDirectory )
{
this.mWorkDirectory = workDirectory;
}
public File getCacheFile()
{
return mCacheFile;
}
public void setCacheFile( File cacheFile )
{
this.mCacheFile = cacheFile;
}
public void setAmpSourceIncludes( String ampSourceIncludes )
{
this.mAmpJarIncludes = ampSourceIncludes;
}
public String getAmpJarExcludes()
{
return mAmpJarExcludes;
}
public void setAmpJarExcludes( String ampJarExcludes )
{
this.mAmpJarExcludes = ampJarExcludes;
}
public boolean isUseCache()
{
return mUseCache;
}
public void setUseCache( boolean useCache )
{
this.mUseCache = useCache;
}
}

View File

@@ -0,0 +1,42 @@
package org.alfresco.maven.plugin.amp;
/*
* 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 org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
/**
* Generate the exploded webapp
*
* @goal explodedAmp
* @phase package
* @requiresDependencyResolution runtime
*/
public class AmpExplodedMojo extends AbstractAmpMojo
{
public void execute()
throws MojoExecutionException, MojoFailureException
{
getLog().info( "Exploding AMP" );
this.buildExplodedAmp( this.getAmpDirectory() );
}
}

View File

@@ -0,0 +1,348 @@
package org.alfresco.maven.plugin.amp;
/*
* 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 org.alfresco.plexus.archiver.AmpArchiver;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import java.io.File;
import java.io.IOException;
/**
* Build a amp/webapp.
* <p>
* Note this is a modification of Emmanuel Venisse's (evenisse@apache.org) WAR
* Mojo and has been adapted to build an Alfresco AMP.
* @version $Id:$
* @goal amp
* @phase package
* @requiresDependencyResolution runtime
*/
public class AmpMojo extends AbstractAmpMojo
{
/* ====================================================================== */
// constructors
/* ====================================================================== */
/**
* default constructor
*/
public AmpMojo()
{
this.setAmpArchiver(null);
this.setAmpName(null);
this.setOutputDirectory(null);
}
/* ====================================================================== */
// properties
/* ====================================================================== */
/**
* Whether this is the main artifact being built. Set to <code>false</code> if you don't want to install or
* deploy it to the local repository instead of the default one in an execution.
*
* @parameter expression="${primaryArtifact}" default-value="true"
*/
private boolean mPrimaryArtifact;
/**
* get the the internal value for the <code>ampArchiver</code> property.
* <p>
* The <code>ampArchiver</code> property
* @return Returns the internal value for the ampArchiver property.
*/
protected AmpArchiver getAmpArchiver()
{
return this.mAmpArchiver;
}
/**
* set the internal value for the <code>ampArchiver</code> property
* @param pAmpArchiver The <code>ampArchiver</code> to set.
*/
protected void setAmpArchiver(AmpArchiver pAmpArchiver)
{
this.mAmpArchiver = pAmpArchiver;
}
/**
* get the the internal value for the <code>ampName</code> property.
* <p>
* The <code>ampName</code> property
* @return Returns the internal value for the ampName property.
*/
protected String getAmpName()
{
return this.mAmpName;
}
/**
* set the internal value for the <code>ampName</code> property
* @param pAmpName The <code>ampName</code> to set.
*/
protected void setAmpName(String pAmpName)
{
this.mAmpName = pAmpName;
}
/**
* get the the internal value for the <code>outputDirectory</code> property.
* <p>
* The <code>outputDirectory</code> property
* @return Returns the internal value for the outputDirectory property.
*/
protected String getOutputDirectory()
{
return this.mOutputDirectory;
}
/**
* set the internal value for the <code>outputDirectory</code> property
* @param pOutputDirectory The <code>outputDirectory</code> to set.
*/
protected void setOutputDirectory(String pOutputDirectory)
{
this.mOutputDirectory = pOutputDirectory;
}
/**
* get the the internal value for the <code>primaryArtifact</code> property.
* <p>
* The <code>primaryArtifact</code> property
* @return Returns the internal value for the primaryArtifact property.
*/
protected boolean isPrimaryArtifact()
{
return this.mPrimaryArtifact;
}
/**
* set the internal value for the <code>primaryArtifact</code> property
* @param pPrimaryArtifact The <code>primaryArtifact</code> to set.
*/
protected void setPrimaryArtifact(boolean pPrimaryArtifact)
{
this.mPrimaryArtifact = pPrimaryArtifact;
}
/**
* get the the internal value for the <code>projectHelper</code> property.
* <p>
* The <code>projectHelper</code> property
* @return Returns the internal value for the projectHelper property.
*/
protected MavenProjectHelper getProjectHelper()
{
return this.mProjectHelper;
}
/**
* set the internal value for the <code>projectHelper</code> property
* @param pProjectHelper The <code>projectHelper</code> to set.
*/
protected void setProjectHelper(MavenProjectHelper pProjectHelper)
{
this.mProjectHelper = pProjectHelper;
}
/**
* Overload this to produce a test-war, for example.
*/
protected String getClassifier()
{
return mClassifier;
}
/**
* set the internal value for the <code>classifier</code> property
* @param pClassifier The <code>classifier</code> to set.
*/
protected void setClassifier(String pClassifier)
{
this.mClassifier = pClassifier;
}
/* ====================================================================== */
// public methods
/* ====================================================================== */
/**
* Executes the WarMojo on the current project.
*
* @throws MojoExecutionException if an error occured while building the webapp
*/
public void execute()
throws MojoExecutionException,
MojoFailureException
{
File vAmpFile = AmpMojo.getAmpFile(new File( getOutputDirectory() ), getAmpName(), getClassifier());
try
{
this.performPackaging(vAmpFile);
}
catch (Exception eAssemblyFailure)
{
/* behavior is the same for the following exceptions:
* DependencyResolutionRequiredException
* ManifestException
* IOException
* ArchiverException
*/
throw new MojoExecutionException( "Error assembling AMP: " + eAssemblyFailure.getMessage(), eAssemblyFailure );
}
}
/* ====================================================================== */
// protected methods
/* ====================================================================== */
/**
* composes the full file name for the AMP and gets a file handle for that file
* TODO: what happens when nulls are passed in
* TODO: what does a null response mean?
* @param pBaseDir Base directory for AMP
* @param pFileName Final Name of AMP
* @param pClassifier TODO: fill this in
*/
protected static File getAmpFile( File pBasedir, String pFinalName, String pClassifier )
{
String vClassifier = pClassifier;
if (vClassifier == null)
{
vClassifier = "";
}
else if (vClassifier.trim().length() > 0 && !vClassifier.startsWith( "-" ) )
{
vClassifier = "-" + vClassifier;
}
return new File(pBasedir, pFinalName + vClassifier + ".amp" );
}
/**
* Generates the webapp according to the <tt>mode</tt> attribute.
*
* @param pAmpFile the target AMP file
* @throws IOException
* @throws ArchiverException
* @throws ManifestException
* @throws DependencyResolutionRequiredException
*
*/
protected void performPackaging(File pAmpFile)
throws IOException,
ArchiverException,
ManifestException,
DependencyResolutionRequiredException,
MojoExecutionException, MojoFailureException
{
getLog().info( "Packaging Alfresco AMP (" + this.getAmpName() + ")" );
this.buildExplodedAmp(this.getAmpDirectory());
/* create and setup an archiver */
MavenArchiver vArchiver = new MavenArchiver();
vArchiver.setArchiver(this.getAmpArchiver());
vArchiver.setOutputFile(pAmpFile);
/* setup amp Archiver */
this.getAmpArchiver().addDirectory(this.getAmpDirectory(), this.getIncludes(), this.getExcludes());
// create archive
vArchiver.createArchive(this.getProject(), archive );
String vClassifier = this.getClassifier();
if ( vClassifier != null )
{
this.getProjectHelper().attachArtifact( this.getProject(), "amp", vClassifier, pAmpFile );
}
else
{
Artifact vArtifact = this.getProject().getArtifact();
if ( this.isPrimaryArtifact() )
{
vArtifact.setFile(pAmpFile);
}
else if(vArtifact.getFile() == null || vArtifact.getFile().isDirectory() )
{
vArtifact.setFile(pAmpFile);
}
}
}
/* ====================================================================== */
// member fields
/* ====================================================================== */
/**
* The directory for the generated AMP.
*
* @parameter expression="${project.build.directory}"
* @required
*/
private String mOutputDirectory;
/**
* The name of the generated AMP.
*
* @parameter expression="${project.build.finalName}"
* @required
*/
private String mAmpName;
/**
* Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.
*
* @parameter
*/
private String mClassifier;
/**
* The AMP archiver.
* @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#amp}"
* @required
*/
private AmpArchiver mAmpArchiver;
/**
* @component
*/
private MavenProjectHelper mProjectHelper;
}

View File

@@ -0,0 +1,309 @@
package org.alfresco.maven.plugin.amp;
/*
* 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 org.apache.maven.artifact.Artifact;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* An overlay is a skeleton war added to another war project in order to inject a
* functionnality, resources or any other shared component.
* <p/>
* Note that a particlar war dependency can be added multiple times as an overlay
* with different includes/excludes filter; this allows building a fine grained
* overwriting policy.
* <p/>
* The current project can also be described as an overlay and could not be specified
* twice. An overlay with no groupId and no artifactId is detected as defining the
* current project.
*
* @author Stephane Nicoll
*/
public class Overlay
{
public static final String[] DEFAULT_INCLUDES = new String[]{"**/**"};
public static final String[] DEFAULT_EXCLUDES = new String[]{"META-INF/MANIFEST.MF"};
private static Overlay currentProjectInstance;
private String id;
private String groupId;
private String artifactId;
private String classifier = null;
private String[] includes = DEFAULT_INCLUDES;
private String[] excludes = DEFAULT_EXCLUDES;
private boolean filtered = false;
private boolean skip = false;
private Artifact artifact;
private String targetPath;
/** default overlay type is war */
private String type = "amp";
public Overlay()
{
super();
}
public Overlay( String groupId, String artifactId )
{
this();
this.groupId = groupId;
this.artifactId = artifactId;
}
/**
* Specify whether this overlay represents the current project or not.
*
* @return true if the overlay represents the current project, false otherwise
*/
public boolean isCurrentProject()
{
return ( groupId == null && artifactId == null );
}
/**
* Creates an overlay of the current project.
*
* @return the current project as an overlay
*/
public static Overlay currentProjectInstance()
{
if ( currentProjectInstance == null )
{
currentProjectInstance = new Overlay();
currentProjectInstance.setId( "currentBuild" );
}
return currentProjectInstance;
}
// Getters and Setters
public String getId()
{
if ( id == null )
{
final StringBuffer sb = new StringBuffer();
sb.append( getGroupId() ).append( ":" ).append( getArtifactId() );
if ( getClassifier() != null )
{
sb.append( ":" ).append( getClassifier() );
}
id = sb.toString();
}
return id;
}
public void setId( String id )
{
this.id = id;
}
public String getGroupId()
{
return groupId;
}
public void setGroupId( String groupId )
{
this.groupId = groupId;
}
public String getArtifactId()
{
return artifactId;
}
public void setArtifactId( String artifactId )
{
this.artifactId = artifactId;
}
public String getClassifier()
{
return classifier;
}
public void setClassifier( String classifier )
{
this.classifier = classifier;
}
public String[] getIncludes()
{
return includes;
}
public void setIncludes( String includes )
{
this.includes = parse( includes );
}
public void setIncludes( String[] includes )
{
this.includes = includes;
}
public String[] getExcludes()
{
return excludes;
}
public void setExcludes( String excludes )
{
this.excludes = parse( excludes );
}
public void setExcludes( String[] excludes )
{
this.excludes = excludes;
}
public boolean isFiltered()
{
return filtered;
}
public void setFiltered( boolean filtered )
{
this.filtered = filtered;
}
public boolean shouldSkip()
{
return skip;
}
public void setSkip( boolean skip )
{
this.skip = skip;
}
public Artifact getArtifact()
{
return artifact;
}
public void setArtifact( Artifact artifact )
{
this.artifact = artifact;
}
public String getTargetPath()
{
return targetPath;
}
public void setTargetPath( String targetPath )
{
this.targetPath = targetPath;
}
public String getType()
{
return type;
}
public void setType( String type )
{
this.type = type;
}
public String toString()
{
return " id " + getId();
}
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || getClass() != o.getClass() )
{
return false;
}
Overlay overlay = (Overlay) o;
if ( excludes != null ? !Arrays.equals( excludes, overlay.excludes ) : overlay.excludes != null )
{
return false;
}
if ( getId() != null ? !getId().equals( overlay.getId() ) : overlay.getId() != null )
{
return false;
}
if ( includes != null ? !Arrays.equals( includes, overlay.includes ) : overlay.includes != null )
{
return false;
}
return true;
}
public int hashCode()
{
int result;
result = ( getId() != null ? getId().hashCode() : 0 );
result = 31 * result + ( includes != null ? includes.hashCode() : 0 );
result = 31 * result + ( excludes != null ? excludes.hashCode() : 0 );
return result;
}
private String[] parse( String s )
{
final List result = new ArrayList();
if ( s == null )
{
return (String[]) result.toArray( new String[result.size()] );
}
else
{
String[] tokens = s.split( "," );
for ( int i = 0; i < tokens.length; i++ )
{
String token = tokens[i];
result.add( token.trim() );
}
return (String[]) result.toArray( new String[result.size()] );
}
}
}

View File

@@ -0,0 +1,62 @@
package org.alfresco.maven.plugin.amp.overlay;
/*
* 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 org.apache.maven.artifact.Artifact;
import org.alfresco.maven.plugin.amp.Overlay;
/**
* A default overlay implementation based on an {@link Artifact}.
*
* @author Stephane Nicoll
*/
public class DefaultOverlay
extends Overlay
{
/**
* Creates an overlay for the specified artifact.
*
* @param a the artifact
*/
public DefaultOverlay( Artifact a )
{
super();
setGroupId( a.getGroupId() );
setArtifactId( a.getArtifactId() );
setClassifier( a.getClassifier() );
setArtifact( a );
setType( a.getType() );
}
/**
* Creates an overlay for the specified artifact.
*
* @param a the artifact
* @param includes the includes to use
* @param excludes the excludes to use
*/
public DefaultOverlay( Artifact a, String includes, String excludes )
{
this( a );
setIncludes( includes );
setExcludes( excludes );
}
}

View File

@@ -0,0 +1,44 @@
package org.alfresco.maven.plugin.amp.overlay;
import org.apache.maven.plugin.MojoExecutionException;
/*
* 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.
*/
/**
* Thrown if the overlay configuration is invalid.
*
* @author Stephane Nicoll
*/
public class InvalidOverlayConfigurationException
extends MojoExecutionException
{
private static final long serialVersionUID = 1L;
public InvalidOverlayConfigurationException( String string )
{
super( string );
}
public InvalidOverlayConfigurationException( String string, Throwable throwable )
{
super( string, throwable );
}
}

View File

@@ -0,0 +1,257 @@
package org.alfresco.maven.plugin.amp.overlay;
/*
* 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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.alfresco.maven.plugin.amp.Overlay;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;
/**
* Manages the overlays.
*
*
* @author Stephane Nicoll
*/
public class OverlayManager
{
private final List overlays;
private final MavenProject project;
private final List artifactsOverlays;
/**
* Creates a manager with the specified overlays.
* <p/>
* Note that the list is potentially updated by the
* manager so a new list is created based on the overlays.
*
* @param overlays the overlays
* @param project the maven project
* @param defaultIncludes the default includes to use
* @param defaultExcludes the default excludes to use
* @throws InvalidOverlayConfigurationException
* if the config is invalid
*/
public OverlayManager( List overlays, MavenProject project, String defaultIncludes, String defaultExcludes )
throws InvalidOverlayConfigurationException
{
this.overlays = new ArrayList();
if ( overlays != null )
{
this.overlays.addAll( overlays );
}
this.project = project;
this.artifactsOverlays = getOverlaysAsArtifacts();
// Initialize
initialize( defaultIncludes, defaultExcludes );
}
/**
* Returns the resolved overlays.
*
* @return the overlays
*/
public List getOverlays()
{
return overlays;
}
/**
* Returns the id of the resolved overlays.
*
* @return the overlay ids
*/
public List getOverlayIds()
{
final Iterator it = overlays.iterator();
final List result = new ArrayList();
while ( it.hasNext() )
{
Overlay overlay = (Overlay) it.next();
result.add( overlay.getId() );
}
return result;
}
/**
* Intializes the manager and validates the overlays configuration.
*
* @param defaultIncludes the default includes to use
* @param defaultExcludes the default excludes to use
* @throws InvalidOverlayConfigurationException
* if the configuration is invalid
*/
void initialize( String defaultIncludes, String defaultExcludes )
throws InvalidOverlayConfigurationException
{
// Build the list of configured artifacts and makes sure that each overlay
// refer to a valid artifact
final List configuredWarArtifacts = new ArrayList();
final ListIterator it = overlays.listIterator();
while ( it.hasNext() )
{
Overlay overlay = (Overlay) it.next();
if ( overlay == null )
{
throw new InvalidOverlayConfigurationException( "overlay could not be null." );
}
// If it's the current project, return the project instance
if ( overlay.isCurrentProject() )
{
overlay = Overlay.currentProjectInstance();
it.set( overlay );
}
// default includes/excludes - only if the overlay uses the default settings
if ( Overlay.DEFAULT_INCLUDES.equals( overlay.getIncludes() ) &&
Overlay.DEFAULT_EXCLUDES.equals( overlay.getExcludes() ) )
{
overlay.setIncludes( defaultIncludes );
overlay.setExcludes( defaultExcludes );
}
final Artifact artifact = getAssociatedArtifact( overlay );
if ( artifact != null )
{
configuredWarArtifacts.add( artifact );
overlay.setArtifact( artifact );
}
}
// Build the list of missing overlays
final Iterator it2 = artifactsOverlays.iterator();
while ( it2.hasNext() )
{
Artifact artifact = (Artifact) it2.next();
if ( !configuredWarArtifacts.contains( artifact ) )
{
// Add a default overlay for the given artifact which will be applied after
// the ones that have been configured
overlays.add( new DefaultOverlay( artifact, defaultIncludes, defaultExcludes ) );
}
}
// Final validation, make sure that the current project is in there. Otherwise add it first
final Iterator it3 = overlays.iterator();
while ( it3.hasNext() )
{
Overlay overlay = (Overlay) it3.next();
if ( overlay.equals( Overlay.currentProjectInstance() ) )
{
return;
}
}
overlays.add( 0, Overlay.currentProjectInstance() );
}
/**
* Returns the Artifact associated to the specified overlay.
* <p/>
* If the overlay defines the current project, <tt>null</tt> is
* returned. If no artifact could not be found for the overlay
* a InvalidOverlayConfigurationException is thrown.
*
* @param overlay an overlay
* @return the artifact associated to the overlay
* @throws org.apache.maven.plugin.war.overlay.InvalidOverlayConfigurationException
* if the overlay does not have an associated artifact
*/
Artifact getAssociatedArtifact( final Overlay overlay )
throws InvalidOverlayConfigurationException
{
if ( overlay.isCurrentProject() )
{
return null;
}
for ( Iterator iterator = artifactsOverlays.iterator(); iterator.hasNext(); )
{
// Handle classifier dependencies properly (clash management)
Artifact artifact = (Artifact) iterator.next();
if ( compareOverlayWithArtifact(overlay, artifact) )
{
return artifact;
}
}
// maybe its a project dependencies zip or an other type
Set projectArtifacts = this.project.getDependencyArtifacts();
if (projectArtifacts != null)
{
for( Iterator iterator = projectArtifacts.iterator();iterator.hasNext();)
{
Artifact artifact = (Artifact) iterator.next();
if ( compareOverlayWithArtifact(overlay, artifact) )
{
return artifact;
}
}
}
throw new InvalidOverlayConfigurationException(
"overlay[" + overlay + "] is not a dependency of the project." );
}
private boolean compareOverlayWithArtifact(Overlay overlay, Artifact artifact)
{
return ( StringUtils.equals( overlay.getGroupId(), artifact.getGroupId() )
&& StringUtils.equals( overlay.getArtifactId(), artifact.getArtifactId() )
&& StringUtils.equals( overlay.getType(), artifact.getType() ) && ( overlay.getClassifier() == null || ( StringUtils
.equals( overlay.getClassifier(), artifact.getClassifier() ) ) ) );
}
/**
* Returns a list of war {@link org.apache.maven.artifact.Artifact} describing
* the overlays of the current project.
*
* @return the overlays as artifacts objects
*/
private List getOverlaysAsArtifacts()
{
ScopeArtifactFilter filter = new ScopeArtifactFilter( Artifact.SCOPE_RUNTIME );
final Set artifacts = project.getArtifacts();
final Iterator it = artifacts.iterator();
final List result = new ArrayList();
while ( it.hasNext() )
{
Artifact artifact = (Artifact) it.next();
if ( !artifact.isOptional() && filter.include( artifact ) && ( "amp".equals( artifact.getType() ) ) )
{
result.add( artifact );
}
}
return result;
}
}

View File

@@ -0,0 +1,409 @@
package org.alfresco.maven.plugin.amp.packaging;
/*
* 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 org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.alfresco.maven.plugin.amp.AbstractAmpMojo;
import org.alfresco.maven.plugin.amp.util.MappingUtils;
import org.alfresco.maven.plugin.amp.util.PathSet;
import org.alfresco.maven.plugin.amp.util.AmpStructure;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.InterpolationFilterReader;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
/**
* @author Stephane Nicoll
*/
public abstract class AbstractAmpPackagingTask
implements AmpPackagingTask
{
public static final String[] DEFAULT_INCLUDES = {"**/**"};
public static final String META_INF_PATH = "META-INF";
/**
* Copies the files if possible with an optional target prefix.
* <p/>
* Copy uses a first-win strategy: files that have already been copied by previous
* tasks are ignored. This method makes sure to update the list of protected files
* which gives the list of files that have already been copied.
* <p/>
* If the structure of the source directory is not the same as the root of the
* webapp, use the <tt>targetPrefix</tt> parameter to specify in which particular
* directory the files should be copied. Use <tt>null</tt> to copy the files with
* the same structure
*
* @param sourceId the source id
* @param context the context to use
* @param sourceBaseDir the base directory from which the <tt>sourceFilesSet</tt> will be copied
* @param sourceFilesSet the files to be copied
* @param targetPrefix the prefix to add to the target file name
* @throws IOException if an error occured while copying the files
*/
protected void copyFiles( String sourceId, AmpPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet,
String targetPrefix )
throws IOException
{
for ( Iterator iter = sourceFilesSet.iterator(); iter.hasNext(); )
{
final String fileToCopyName = (String) iter.next();
final File sourceFile = new File( sourceBaseDir, fileToCopyName );
String destinationFileName;
if ( targetPrefix == null )
{
destinationFileName = fileToCopyName;
}
else
{
destinationFileName = targetPrefix + fileToCopyName;
}
copyFile( sourceId, context, sourceFile, destinationFileName );
}
}
/**
* Copies the files if possible as is.
* <p/>
* Copy uses a first-win strategy: files that have already been copied by previous
* tasks are ignored. This method makes sure to update the list of protected files
* which gives the list of files that have already been copied.
*
* @param sourceId the source id
* @param context the context to use
* @param sourceBaseDir the base directory from which the <tt>sourceFilesSet</tt> will be copied
* @param sourceFilesSet the files to be copied
* @throws IOException if an error occured while copying the files
*/
protected void copyFiles( String sourceId, AmpPackagingContext context, File sourceBaseDir, PathSet sourceFilesSet )
throws IOException
{
copyFiles( sourceId, context, sourceBaseDir, sourceFilesSet, null );
}
/**
* Copy the specified file if the target location has not yet already been used.
* <p/>
* The <tt>targetFileName</tt> is the relative path according to the root of
* the generated web application.
*
* @param sourceId the source id
* @param context the context to use
* @param file the file to copy
* @param targetFilename the relative path according to the root of the webapp
* @throws IOException if an error occured while copying
*/
protected void copyFile( String sourceId, final AmpPackagingContext context, final File file,
String targetFilename )
throws IOException
{
final File targetFile = new File( context.getAmpDirectory(), targetFilename );
context.getAmpStructure().registerFile( sourceId, targetFilename, new AmpStructure.RegistrationCallback()
{
public void registered( String ownerId, String targetFilename )
throws IOException
{
copyFile( context, file, targetFile, targetFilename, false );
}
public void alreadyRegistered( String ownerId, String targetFilename )
throws IOException
{
copyFile( context, file, targetFile, targetFilename, true );
}
public void refused( String ownerId, String targetFilename, String actualOwnerId )
throws IOException
{
context.getLog().debug( " - " + targetFilename + " wasn't copied because it has " +
"already been packaged for overlay[" + actualOwnerId + "]." );
}
public void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
throws IOException
{
context.getLog().info( "File[" + targetFilename + "] belonged to overlay[" + deprecatedOwnerId +
"] so it will be overwritten." );
copyFile( context, file, targetFile, targetFilename, false );
}
public void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
throws IOException
{
context.getLog().warn( "File[" + targetFilename + "] belonged to overlay[" + unknownOwnerId +
"] which does not exist anymore in the current project. It is recommended to invoke " +
"clean if the dependencies of the project changed." );
copyFile( context, file, targetFile, targetFilename, false );
}
} );
}
/**
* Copy the specified file if the target location has not yet already been
* used and filter its content with the configureed filter properties.
* <p/>
* The <tt>targetFileName</tt> is the relative path according to the root of
* the generated web application.
*
* @param sourceId the source id
* @param context the context to use
* @param file the file to copy
* @param targetFilename the relative path according to the root of the webapp
* @return true if the file has been copied, false otherwise
* @throws IOException if an error occured while copying
* @throws MojoExecutionException if an error occured while retrieving the filter properties
*/
protected boolean copyFilteredFile( String sourceId, AmpPackagingContext context, File file, String targetFilename )
throws IOException, MojoExecutionException
{
if ( context.getAmpStructure().registerFile( sourceId, targetFilename ) )
{
final File targetFile = new File( context.getAmpDirectory(), targetFilename );
// buffer so it isn't reading a byte at a time!
Reader fileReader = null;
Writer fileWriter = null;
try
{
// fix for MWAR-36, ensures that the parent dir are created first
targetFile.getParentFile().mkdirs();
fileReader = new BufferedReader( new FileReader( file ) );
fileWriter = new FileWriter( targetFile );
Reader reader = fileReader;
for ( int i = 0; i < getFilterWrappers().length; i++ )
{
FilterWrapper wrapper = getFilterWrappers()[i];
reader = wrapper.getReader( reader, context.getFilterProperties() );
}
IOUtil.copy( reader, fileWriter );
}
finally
{
IOUtil.close( fileReader );
IOUtil.close( fileWriter );
}
// Add the file to the protected list
context.getLog().debug( " + " + targetFilename + " has been copied." );
return true;
}
else
{
context.getLog().debug( " - " + targetFilename + " wasn't copied because it has already been packaged." );
return false;
}
}
/**
* Unpacks the specified file to the specified directory.
*
* @param context the packaging context
* @param file the file to unpack
* @param unpackDirectory the directory to use for th unpacked file
* @throws MojoExecutionException if an error occured while unpacking the file
*/
protected void doUnpack( AmpPackagingContext context, File file, File unpackDirectory )
throws MojoExecutionException
{
String archiveExt = FileUtils.getExtension( file.getAbsolutePath() ).toLowerCase();
// Uncompressing an AMP into another AMP does not require any
// special treatment so we just use a zip unarchiver
if ("amp".equals(archiveExt))
{
archiveExt = "zip";
}
try
{
UnArchiver unArchiver = context.getArchiverManager().getUnArchiver( archiveExt );
unArchiver.setSourceFile( file );
unArchiver.setDestDirectory( unpackDirectory );
unArchiver.setOverwrite( true );
unArchiver.extract();
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error unpacking file[" + file.getAbsolutePath() + "]" + "to[" +
unpackDirectory.getAbsolutePath() + "]", e );
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "Error unpacking file[" + file.getAbsolutePath() + "]" + "to[" +
unpackDirectory.getAbsolutePath() + "]", e );
}
catch ( NoSuchArchiverException e )
{
context.getLog().warn( "Skip unpacking dependency file[" + file.getAbsolutePath() +
" with unknown extension[" + archiveExt + "]" );
}
}
/**
* Copy file from source to destination. The directories up to <code>destination</code>
* will be created if they don't already exist. if the <code>onlyIfModified</code> flag
* is <tt>false</tt>, <code>destination</code> will be overwritten if it already exists. If the
* flag is <tt>true</tt> destination will be overwritten if it's not up to date.
* <p/>
*
* @param context the packaging context
* @param source an existing non-directory <code>File</code> to copy bytes from
* @param destination a non-directory <code>File</code> to write bytes to (possibly overwriting).
* @param targetFilename the relative path of the file from the webapp root directory
* @param onlyIfModified if true, copy the file only if the source has changed, always copy otherwise
* @return true if the file has been copied/updated, false otherwise
* @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot
* be written to, or an IO error occurs during copying
*/
protected boolean copyFile( AmpPackagingContext context, File source, File destination, String targetFilename,
boolean onlyIfModified )
throws IOException
{
if ( onlyIfModified && destination.lastModified() >= source.lastModified() )
{
context.getLog().debug( " * " + targetFilename + " is up to date." );
return false;
}
else
{
FileUtils.copyFile( source.getCanonicalFile(), destination );
// preserve timestamp
destination.setLastModified( source.lastModified() );
context.getLog().debug( " + " + targetFilename + " has been copied." );
return true;
}
}
/**
* Returns the file to copy. If the includes are <tt>null</tt> or empty, the
* default includes are used.
*
* @param baseDir the base directory to start from
* @param includes the includes
* @param excludes the excludes
* @return the files to copy
*/
protected PathSet getFilesToIncludes( File baseDir, String[] includes, String[] excludes )
{
final DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir( baseDir );
if ( excludes != null )
{
scanner.setExcludes( excludes );
}
scanner.addDefaultExcludes();
if ( includes != null && includes.length > 0 )
{
scanner.setIncludes( includes );
}
else
{
scanner.setIncludes( DEFAULT_INCLUDES );
}
scanner.scan();
return new PathSet( scanner.getIncludedFiles() );
}
/**
* Returns the final name of the specified artifact.
* <p/>
* If the <tt>outputFileNameMapping</tt> is set, it is used, otherwise
* the standard naming scheme is used.
*
* @param context the packaging context
* @param artifact the artifact
* @return the converted filename of the artifact
*/
protected String getArtifactFinalName( AmpPackagingContext context, Artifact artifact )
{
if ( context.getOutputFileNameMapping() != null )
{
return MappingUtils.evaluateFileNameMapping( context.getOutputFileNameMapping(), artifact );
}
String classifier = artifact.getClassifier();
if ( ( classifier != null ) && !( "".equals( classifier.trim() ) ) )
{
return MappingUtils.evaluateFileNameMapping( AbstractAmpMojo.DEFAULT_FILE_NAME_MAPPING_CLASSIFIER,
artifact );
}
else
{
return MappingUtils.evaluateFileNameMapping( AbstractAmpMojo.DEFAULT_FILE_NAME_MAPPING, artifact );
}
}
private FilterWrapper[] getFilterWrappers()
{
return new FilterWrapper[]{
// support ${token}
new FilterWrapper()
{
public Reader getReader( Reader fileReader, Map filterProperties )
{
return new InterpolationFilterReader( fileReader, filterProperties, "${", "}" );
}
},
// support @token@
new FilterWrapper()
{
public Reader getReader( Reader fileReader, Map filterProperties )
{
return new InterpolationFilterReader( fileReader, filterProperties, "@", "@" );
}
}};
}
private interface FilterWrapper
{
Reader getReader( Reader fileReader, Map filterProperties );
}
}

View File

@@ -0,0 +1,189 @@
package org.alfresco.maven.plugin.amp.packaging;
/*
* 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 org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.alfresco.maven.plugin.amp.util.AmpStructure;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import java.io.File;
import java.util.List;
import java.util.Map;
/**
* The packaging context of the AMP
*
* @author Stephane Nicoll - Modified version for AMP
*
*/
public interface AmpPackagingContext
{
/**
* Returns the maven project.
*
* @return the project
*/
MavenProject getProject();
/**
* Returns the webapp directory. Packaging tasks should use this
* directory to generate the webapp.
*
* @return the webapp directory
*/
File getAmpDirectory();
/**
* Returns the AMP classes + resources folder
*
* @return the webapp source directory
*/
File getAmpConfigDirectory();
/**
* Returns the AMP web directory.
*
* @return the webapp source directory
*/
File getAmpWebDirectory();
/**
* Returns the webapp source includes.
*
* @return the webapp source includes
*/
String[] getAmpJarIncludes();
/**
* Returns the webapp source excludes.
*
* @return the webapp source excludes
*/
String[] getAmpJarExcludes();
/**
* Returns the AMP web/ includes.
*
* @return the AMP web/ includes
*/
String[] getAmpWebIncludes();
/**
* Returns the AMP web/ excludes.
*
* @return the AMP web/ excludes
*/
String[] getAmpWebExcludes();
/**
* Returns the directory holding generated classes to be packed in the jar - By default is the same of the AMP configuration
*
* @return the classes directory
*/
File getClassesDirectory();
/**
* Returns the logger to use to output logging event.
*
* @return the logger
*/
Log getLog();
/**
* Returns the directory to unpack dependent WARs into if needed.
*
* @return the overlays work directory
*/
File getOverlaysWorkDirectory();
/**
* Returns the archiver manager to use.
*
* @return the archiver manager
*/
ArchiverManager getArchiverManager();
/**
* The maven archive configuration to use.
*
* @return the maven archive configuration
*/
MavenArchiveConfiguration getArchive();
/**
* Returns the Jar archiver needed for archiving classes directory into
* jar file under WEB-INF/lib.
*
* @return the jar archiver to user
*/
JarArchiver getJarArchiver();
/**
* Returns the output file name mapping to use, if any. Returns <tt>null</tt>
* if no file name mapping is set.
*
* @return the output file name mapping or <tt>null</tt>
*/
String getOutputFileNameMapping();
/**
* Returns the list of filter files to use.
*
* @return a list of filter files
*/
List getFilters();
/**
* Returns the filter properties to use to filter resources.
* <p/>
* TODO: this needs to be refactored to use the resource plugin somehow.
*
* @return a map of filter properties
* @throws MojoExecutionException if an error occured while reading a filter file
*/
Map getFilterProperties()
throws MojoExecutionException;
/**
* Returns the {@link AmpStructure}.
*
* @return the webapp structure
*/
AmpStructure getAmpStructure();
/**
* Returns the list of registered overlays for this session. This list might
* differ from the one returned by the cache; in this case, it means that the
* project's configuration has changed. The plugin will handle thos cases nicely
* but it would be better in general to invoke the clean goal.
*
* @return the list of registered overlays, including the current project
*/
List getOwnerIds();
}

View File

@@ -0,0 +1,47 @@
package org.alfresco.maven.plugin.amp.packaging;
/*
* 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 org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
/**
* The base packaging task.
*
* @author Stephane Nicoll
*/
public interface AmpPackagingTask
{
/**
* Performs the packaging for the specified task.
* <p/>
* The task is responsible to update the packaging context, namely
* with the files that have been copied.
*
* @param context the packaging context
* @throws MojoExecutionException if an error occured
* @throws MojoFailureException if the project configuration is invalid
*/
void performPackaging( AmpPackagingContext context )
throws MojoExecutionException, MojoFailureException;
}

View File

@@ -0,0 +1,27 @@
package org.alfresco.maven.plugin.amp.packaging;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
/**
* Defines tasks that should be performed after the packaging.
*
* @author Stephane Nicoll
*/
public interface AmpPostPackagingTask
{
/**
* Executes the post packaging task.
* <p/>
* The packaging context hold all information regarding the webapp that
* has been packaged.
*
* @param context the packaging context
* @throws MojoExecutionException if an error occured
* @throws MojoFailureException if a falure occured
*/
void performPostPackaging( AmpPackagingContext context )
throws MojoExecutionException, MojoFailureException;
}

View File

@@ -0,0 +1,326 @@
package org.alfresco.maven.plugin.amp.packaging;
import org.alfresco.maven.plugin.amp.Overlay;
import org.alfresco.maven.plugin.amp.util.PathSet;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.StringUtils;
import java.io.File;
import java.io.IOException;
/**
* Handles the project own resources, that is:
* <ul
* <li>The list of web resources, if any</li>
* <li>The content of the webapp directory if it exists</li>
* <li>The custom deployment descriptor(s), if any</li>
* <li>The content of the classes directory if it exists</li>
* <li>The dependencies of the project</li>
* </ul>
*
* @author Stephane Nicoll
*/
public class AmpProjectPackagingTask
extends AbstractAmpPackagingTask
{
private static final String MODULE_PROPERTIES = "module.properties";
private static final String WEB_PATH = "web/";
private static final String CONFIG_PATH = "config/";
private Resource[] webResources = new Resource[0];
private final File moduleProperties;
private final String id;
public AmpProjectPackagingTask( Resource[] webResource, File moduleProperties)
{
if ( webResources != null )
{
this.webResources = webResources;
}
this.moduleProperties = moduleProperties;
this.id = Overlay.currentProjectInstance().getId();
}
public void performPackaging( AmpPackagingContext context )
throws MojoExecutionException, MojoFailureException
{
context.getLog().info( "Processing amp project" );
File metainfDir = new File( context.getAmpDirectory(), META_INF_PATH );
metainfDir.mkdirs();
handleArtifacts( context );
handleWebResources( context );
handleClassesDirectory( context );
handeAmpConfigDirectory( context );
handeWebAppSourceDirectory( context );
// Notice: this will work only in case we are copying only this AMP or this AMP is
// set as last overlay of the maven-amp-plugin
handleDeploymentDescriptors(context);
}
/**
* Handles the web resources.
*
* @param context the packaging context
* @throws MojoExecutionException if a resource could not be copied
*/
protected void handleWebResources( AmpPackagingContext context )
throws MojoExecutionException
{
for ( int i = 0; i < webResources.length; i++ )
{
Resource resource = webResources[i];
if ( !( new File( resource.getDirectory() ) ).isAbsolute() )
{
resource.setDirectory( context.getProject().getBasedir() + File.separator + resource.getDirectory() );
}
// Make sure that the resource directory is not the same as the webappDirectory
if ( !resource.getDirectory().equals( context.getAmpDirectory().getPath() ) )
{
try
{
copyResources( context, resource );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Could not copy resource[" + resource.getDirectory() + "]", e );
}
}
}
}
/**
* Handles the webapp sources.
*
* @param context the packaging context
* @throws MojoExecutionException if the sources could not be copied
*/
protected void handeWebAppSourceDirectory( AmpPackagingContext context )
throws MojoExecutionException
{
if ( !context.getAmpWebDirectory().exists() )
{
context.getLog().debug( "AMP sources directory does not exist - skipping." );
}
else
if ( !context.getAmpWebDirectory().getAbsolutePath().equals( context.getAmpDirectory().getPath() ) )
{
final PathSet sources = getFilesToIncludes( context.getAmpWebDirectory(),
context.getAmpWebIncludes(),
context.getAmpWebExcludes() );
try
{
context.getLog().info("Copying AMP web data into " + WEB_PATH);
copyFiles( id, context, context.getAmpWebDirectory(), sources, WEB_PATH );
}
catch ( IOException e )
{
throw new MojoExecutionException(
"Could not copy AMP sources[" + context.getAmpDirectory().getAbsolutePath() + "]", e );
}
}
}
/**
* Handles the webapp sources.
*
* @param context the packaging context
* @throws MojoExecutionException if the sources could not be copied
*/
protected void handeAmpConfigDirectory( AmpPackagingContext context )
throws MojoExecutionException
{
if ( !context.getAmpConfigDirectory().exists() )
{
context.getLog().debug( "AMP config directory does not exist - skipping." );
}
else
{
if ( !context.getAmpConfigDirectory().getAbsolutePath().equals( context.getAmpDirectory().getPath() ) )
{
final PathSet sources = getFilesToIncludes( context.getAmpConfigDirectory(),
new String[0],
new String[] {"**/*.class"} );
try
{
context.getLog().info("packaging AMP config items into " + CONFIG_PATH);
copyFiles( id, context, context.getAmpConfigDirectory(), sources, CONFIG_PATH );
}
catch ( IOException e )
{
throw new MojoExecutionException(
"Could not copy AMP config [" + context.getAmpDirectory().getAbsolutePath() + "]", e );
}
}
}
}
/**
* Handles the webapp artifacts.
*
* @param context the packaging context
* @throws MojoExecutionException if the artifacts could not be packaged
*/
protected void handleArtifacts( AmpPackagingContext context )
throws MojoExecutionException
{
ArtifactsPackagingTask task = new ArtifactsPackagingTask( context.getProject().getArtifacts() );
task.performPackaging( context );
}
/**
* Handles the webapp classes.
*
* @param context the packaging context
* @throws MojoExecutionException if the classes could not be packaged
*/
protected void handleClassesDirectory( AmpPackagingContext context )
throws MojoExecutionException
{
ClassesPackagingTask task = new ClassesPackagingTask();
task.performPackaging( context );
}
/**
* Handles the deployment descriptors, if specified. Note that the behavior
* here is slightly different since the customized entry always win, even if
* an overlay has already packaged a web.xml previously.
*
* @param context the packaging context
* @param webinfDir the web-inf directory
* @param metainfDir the meta-inf directory
* @throws MojoFailureException if the web.xml is specified but does not exist
* @throws MojoExecutionException if an error occured while copying the descriptors
*/
protected void handleDeploymentDescriptors( AmpPackagingContext context)
throws MojoFailureException, MojoExecutionException
{
try
{
if ( moduleProperties != null && StringUtils.isNotEmpty( moduleProperties.getName() ) )
{
if ( !moduleProperties.exists() )
{
throw new MojoFailureException( "The specified module.properties file '" + moduleProperties + "' does not exist" );
}
if(new File(context.getAmpDirectory().getPath() + "/module.properties").exists())
{
context.getLog().warn("A module.properties was already present, possibly due to previous overlay. Unexpected results may happen, check your target dir");
}
//rename to module.properties
context.getLog().info("copying " + moduleProperties + " into " + MODULE_PROPERTIES);
copyFilteredFile(id, context, moduleProperties, MODULE_PROPERTIES);
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Failed to copy deployment descriptor", e );
}
}
/**
* Copies webapp webResources from the specified directory.
*
* @param context the war packaging context to use
* @param resource the resource to copy
* @throws IOException if an error occured while copying the resources
* @throws MojoExecutionException if an error occured while retrieving the filter properties
*/
public void copyResources( AmpPackagingContext context, Resource resource )
throws IOException, MojoExecutionException
{
if ( !context.getAmpDirectory().exists() )
{
context.getLog().warn( "Not copying AMP ampResources[" + resource.getDirectory() +
"]: amp directory[" + context.getAmpDirectory().getAbsolutePath() + "] does not exist!" );
}
context.getLog().info( "Copy AMP ampResources[" + resource.getDirectory() + "] to[" +
context.getAmpDirectory().getAbsolutePath() + "]" );
String[] fileNames = getFilesToCopy( resource );
for ( int i = 0; i < fileNames.length; i++ )
{
String targetFileName = fileNames[i];
if ( resource.getTargetPath() != null )
{
//TODO make sure this thing is 100% safe
targetFileName = resource.getTargetPath() + File.separator + targetFileName;
}
if ( resource.isFiltering() )
{
copyFilteredFile( id, context, new File( resource.getDirectory(), fileNames[i] ), targetFileName );
}
else
{
copyFile( id, context, new File( resource.getDirectory(), fileNames[i] ), targetFileName );
}
}
}
/**
* Returns a list of filenames that should be copied
* over to the destination directory.
*
* @param resource the resource to be scanned
* @return the array of filenames, relative to the sourceDir
*/
private String[] getFilesToCopy( Resource resource )
{
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir( resource.getDirectory() );
if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() )
{
scanner.setIncludes(
(String[]) resource.getIncludes().toArray( new String[resource.getIncludes().size()] ) );
}
else
{
scanner.setIncludes( DEFAULT_INCLUDES );
}
if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() )
{
scanner.setExcludes(
(String[]) resource.getExcludes().toArray( new String[resource.getExcludes().size()] ) );
}
scanner.addDefaultExcludes();
scanner.scan();
return scanner.getIncludedFiles();
}
}

View File

@@ -0,0 +1,135 @@
package org.alfresco.maven.plugin.amp.packaging;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.plugin.MojoExecutionException;
import org.alfresco.maven.plugin.amp.Overlay;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Handles the artifacts that needs to be packaged in the web application.
*
* @author Stephane Nicoll
*/
public class ArtifactsPackagingTask
extends AbstractAmpPackagingTask
{
public static final String LIB_PATH = "lib/";
private final Set artifacts;
private final String id;
public ArtifactsPackagingTask( Set artifacts )
{
this.artifacts = artifacts;
this.id = Overlay.currentProjectInstance().getId();
}
public void performPackaging( AmpPackagingContext context )
throws MojoExecutionException
{
final ScopeArtifactFilter filter = new ScopeArtifactFilter( Artifact.SCOPE_RUNTIME );
final List duplicates = findDuplicates( context, artifacts );
for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
{
Artifact artifact = (Artifact) iter.next();
String targetFileName = getArtifactFinalName( context, artifact );
context.getLog().debug( "Processing: " + targetFileName );
if ( duplicates.contains( targetFileName ) )
{
context.getLog().debug( "Duplicate found: " + targetFileName );
targetFileName = artifact.getGroupId() + "-" + targetFileName;
context.getLog().debug( "Renamed to: " + targetFileName );
}
if ( !artifact.isOptional() && filter.include( artifact ) )
{
try
{
String type = artifact.getType();
if ( "tld".equals( type ) )
{
// copyFile( id, context, artifact.getFile(), TLD_PATH + targetFileName );
}
else if ( "aar".equals( type ) )
{
//copyFile( id, context, artifact.getFile(), SERVICES_PATH + targetFileName );
}
else if ( "jar".equals( type ) || "ejb".equals( type ) || "ejb-client".equals( type ) ||
"test-jar".equals( type ) )
{
copyFile( id, context, artifact.getFile(), LIB_PATH + targetFileName );
context.getLog().debug("addng " + artifact.getId() + " to AMP in " + LIB_PATH );
}
else if ( "par".equals( type ) )
{
targetFileName = targetFileName.substring( 0, targetFileName.lastIndexOf( '.' ) ) + ".jar";
copyFile( id, context, artifact.getFile(), LIB_PATH + targetFileName );
}
else if ( "war".equals( type ) )
{
// Nothing to do here, it is an overlay and it's already handled
}
else if ("zip".equals( type ))
{
// Nothing to do here, it is an overlay and it's already handled
}
else if ("amp".equals( type ))
{
context.getLog().debug("skipping " + artifact.getId() + " in artifacts packaging phase. Will be processed in overlay");
}
else
{
context.getLog().debug(
"Artifact of type[" + type + "] is not supported, ignoring[" + artifact + "]" );
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Failed to copy file for artifact[" + artifact + "]", e );
}
}
}
}
/**
* Searches a set of artifacts for duplicate filenames and returns a list
* of duplicates.
*
* @param context the packaging context
* @param artifacts set of artifacts
* @return List of duplicated artifacts as bundling file names
*/
private List findDuplicates( AmpPackagingContext context, Set artifacts )
{
List duplicates = new ArrayList();
List identifiers = new ArrayList();
for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
{
Artifact artifact = (Artifact) iter.next();
String candidate = getArtifactFinalName( context, artifact );
if ( identifiers.contains( candidate ) )
{
duplicates.add( candidate );
}
else
{
identifiers.add( candidate );
}
}
return duplicates;
}
}

View File

@@ -0,0 +1,86 @@
package org.alfresco.maven.plugin.amp.packaging;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.alfresco.maven.plugin.amp.Overlay;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.ManifestException;
import sun.util.logging.resources.logging;
import java.io.File;
import java.io.IOException;
/**
* Handles the classes directory that needs to be packaged in the web application.
* <p/>
* Based on the {@link AmpPackagingContext#archiveClasses()} flag the resources
* either copied into to <tt>WEB-INF/classes</tt> directory or archived in a jar
* within the <tt>WEB-INF/lib</tt> directory.
*
* @author Stephane Nicoll
*/
public class ClassesPackagingTask
extends AbstractAmpPackagingTask
{
private static final String LIB_PATH = "lib/";
public void performPackaging( AmpPackagingContext context )
throws MojoExecutionException
{
/* AMP Files do not have a classes folder */
generateJarArchive( context );
}
protected void generateJarArchive( AmpPackagingContext context )
throws MojoExecutionException
{
//TODO use ArtifactFactory and resolve the final name the usual way instead
final String archiveName = context.getProject().getBuild().getFinalName() + ".jar";
final String targetFilename = LIB_PATH + archiveName;
if ( context.getAmpStructure().registerFile( Overlay.currentProjectInstance().getId(), targetFilename ) )
{
final File libDirectory = new File( context.getAmpDirectory(), LIB_PATH );
final File jarFile = new File( libDirectory, archiveName );
try
{
final MavenArchiver archiver = new MavenArchiver();
archiver.setArchiver( context.getJarArchiver() );
archiver.setOutputFile( jarFile );
archiver.getArchiver().addDirectory( context.getClassesDirectory(), context.getAmpJarIncludes(),
context.getAmpJarExcludes());
context.getLog().debug("Archiving AMP classes JAR with default excludes: " + context.getAmpJarExcludes());
archiver.createArchive( context.getProject(), context.getArchive() );
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "Could not create classes archive", e );
}
catch ( ManifestException e )
{
throw new MojoExecutionException( "Could not create classes archive", e );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Could not create classes archive", e );
}
catch ( DependencyResolutionRequiredException e )
{
throw new MojoExecutionException( "Could not create classes archive", e );
}
}
else
{
context.getLog().warn(
"Could not generate archive classes file[" + targetFilename + "] has already been copied." );
}
}
}

View File

@@ -0,0 +1,150 @@
package org.alfresco.maven.plugin.amp.packaging;
/*
* 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 org.apache.maven.plugin.MojoExecutionException;
import org.alfresco.maven.plugin.amp.Overlay;
import org.alfresco.maven.plugin.amp.util.PathSet;
import org.codehaus.plexus.util.FileUtils;
import java.io.File;
import java.io.IOException;
/**
* Handles an overlay.
*
* @author Stephane Nicoll
*/
public class OverlayPackagingTask
extends AbstractAmpPackagingTask
{
private final Overlay overlay;
public OverlayPackagingTask( Overlay overlay )
{
if ( overlay == null )
{
throw new NullPointerException( "overlay could not be null." );
}
if ( overlay.equals( Overlay.currentProjectInstance() ) )
{
throw new IllegalStateException( "Could not handle the current project with this task." );
}
this.overlay = overlay;
}
public void performPackaging( AmpPackagingContext context )
throws MojoExecutionException
{
System.out.print( "OverlayPackagingTask performPackaging overlay.getTargetPath() " + overlay.getTargetPath());
if ( overlay.shouldSkip() )
{
context.getLog().info( "Skipping overlay[" + overlay + "]" );
}
else
{
try
{
context.getLog().info( "Processing overlay[" + overlay + "]" );
// Step1: Extract if necessary
final File tmpDir = unpackOverlay( context, overlay );
// Step2: setup
final PathSet includes = getFilesToIncludes( tmpDir, overlay.getIncludes(), overlay.getExcludes() );
// Copy
if ( null == overlay.getTargetPath() )
{
copyFiles( overlay.getId(), context, tmpDir, includes );
}
else
{
// overlay.getTargetPath() must ended with /
// if not we add it
String targetPath = overlay.getTargetPath();
if (!targetPath.endsWith( "/" ))
{
targetPath = targetPath + "/";
}
copyFiles( overlay.getId(), context, tmpDir, includes, targetPath );
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Failed to copy file for overlay[" + overlay + "]", e );
}
}
}
/**
* Unpacks the specified overlay.
* <p/>
* Makes sure to skip the unpack process if the overlay has
* already been unpacked.
*
* @param context the packaging context
* @param overlay the overlay
* @return the directory containing the unpacked overlay
* @throws MojoExecutionException if an error occured while unpacking the overlay
*/
protected File unpackOverlay( AmpPackagingContext context, Overlay overlay )
throws MojoExecutionException
{
final File tmpDir = getOverlayTempDirectory( context, overlay );
// TODO: not sure it's good, we should reuse the markers of the dependency plugin
if ( FileUtils.sizeOfDirectory( tmpDir ) == 0 ||
overlay.getArtifact().getFile().lastModified() > tmpDir.lastModified() )
{
context.getLog().info( "Unpacking overlay[" + overlay + "]" );
doUnpack( context, overlay.getArtifact().getFile(), tmpDir );
}
else
{
context.getLog().debug( "Overlay[" + overlay + "] was already unpacked" );
}
return tmpDir;
}
/**
* Returns the directory to use to unpack the specified overlay.
*
* @param context the packaging context
* @param overlay the overlay
* @return the temp directory for the overlay
*/
protected File getOverlayTempDirectory( AmpPackagingContext context, Overlay overlay )
{
final File groupIdDir = new File( context.getOverlaysWorkDirectory(), overlay.getGroupId() );
if ( !groupIdDir.exists() )
{
groupIdDir.mkdir();
}
final File result = new File( groupIdDir, overlay.getArtifactId() );
if ( !result.exists() )
{
result.mkdirs();
}
return result;
}
}

View File

@@ -0,0 +1,50 @@
package org.alfresco.maven.plugin.amp.packaging;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.alfresco.maven.plugin.amp.util.AmpStructureSerializer;
import java.io.File;
import java.io.IOException;
/**
* Saves the webapp structure cache.
*
* @author Stephane Nicoll
*/
public class SaveAmpStructurePostPackagingTask
implements AmpPostPackagingTask
{
private final File targetFile;
private final AmpStructureSerializer serialier;
public SaveAmpStructurePostPackagingTask( File targetFile )
{
this.targetFile = targetFile;
this.serialier = new AmpStructureSerializer();
}
public void performPostPackaging( AmpPackagingContext context )
throws MojoExecutionException, MojoFailureException
{
if ( targetFile == null )
{
context.getLog().debug( "Cache usage is disabled, not saving webapp structure." );
}
else
{
try
{
serialier.toXml( context.getAmpStructure(), targetFile );
context.getLog().debug( "Cache saved successfully." );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Could not save webapp structure", e );
}
}
}
}

View File

@@ -0,0 +1,312 @@
package org.alfresco.maven.plugin.amp.util;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Represents the structure of a web application composed of multiple
* overlays. Each overlay is registered within this structure with the
* set of files it holds.
* <p/>
* Note that this structure is persisted to disk at each invocation to
* store wich owner holds which path (file).
*
* @author Stephane Nicoll
*/
public class AmpStructure
{
private Map registeredFiles;
private transient PathSet allFiles = new PathSet();
private transient AmpStructure cache;
/**
* Creates a new empty instance.
*/
public AmpStructure()
{
this.registeredFiles = new HashMap();
this.cache = null;
}
/**
* Creates a new instance with the specified cache.
*
* @param cache the cache
*/
public AmpStructure( AmpStructure cache )
{
this.registeredFiles = new HashMap();
if ( cache == null )
{
this.cache = new AmpStructure();
}
else
{
this.cache = cache;
}
}
/**
* Specify if the specified <tt>path</tt> is registered or not.
*
* @param path the relative path from the webapp root directory
* @return true if the path is registered, false otherwise
*/
public boolean isRegistered( String path )
{
return getFullStructure().contains( path );
}
/**
* Registers the specified path for the specified owner. Returns <tt>true</tt>
* if the path is not already registered, <tt>false</tt> otherwise.
*
* @param id the owner of the path
* @param path the relative path from the webapp root directory
* @return true if the file was registered successfully
*/
public boolean registerFile( String id, String path )
{
if ( !isRegistered( path ) )
{
doRegister( id, path );
return true;
}
else
{
return false;
}
}
/**
* Registers the specified path for the specified owner. Invokes
* the <tt>callback</tt> with the result of the registration.
*
* @param id the owner of the path
* @param path the relative path from the webapp root directory
* @param callback the callback to invoke with the result of the registration
* @throws IOException if the callback invocation throws an IOException
*/
public void registerFile( String id, String path, RegistrationCallback callback )
throws IOException
{
// If the file is already in the current structure, rejects it with the current owner
if ( isRegistered( path ) )
{
callback.refused( id, path, getOwner( path ) );
}
else
{
doRegister( id, path );
// This is a new file
if ( cache.getOwner( path ) == null )
{
callback.registered( id, path );
} // The file already belonged to this owner
else if ( cache.getOwner( path ).equals( id ) )
{
callback.alreadyRegistered( id, path );
} // The file belongs to another owner and it's known currently
else if ( getOwners().contains( cache.getOwner( path ) ) )
{
callback.superseded( id, path, cache.getOwner( path ) );
} // The file belongs to another owner and it's unknown
else
{
callback.supersededUnknownOwner( id, path, cache.getOwner( path ) );
}
}
}
/**
* Returns the owner of the specified <tt>path</tt>. If the file is not
* registered, returns <tt>null</tt>
*
* @param path the relative path from the webapp root directory
* @return the owner or <tt>null</tt>.
*/
public String getOwner( String path )
{
if ( !isRegistered( path ) )
{
return null;
}
else
{
final Iterator it = registeredFiles.keySet().iterator();
while ( it.hasNext() )
{
final String owner = (String) it.next();
final PathSet structure = getStructure( owner );
if ( structure.contains( path ) )
{
return owner;
}
}
throw new IllegalStateException(
"Should not happen, path[" + path + "] is flagged as being registered but was not found." );
}
}
/**
* Returns the owners. Note that this the returned {@link Set} may be
* inconsistent since it represents a persistent cache accross multiple
* invocations.
* <p/>
* For instance, if an overlay was removed in this execution, it will be
* still be there till the cache is cleaned. This happens when the clean
* mojo is invoked.
*
* @return the list of owners
*/
public Set getOwners()
{
return registeredFiles.keySet();
}
/**
* Returns all paths that have been registered so far.
*
* @return all registered path
*/
public PathSet getFullStructure()
{
return allFiles;
}
/**
* Returns the list of registered files for the specified owner.
*
* @param id the owner
* @return the list of files registered for that owner
*/
public PathSet getStructure( String id )
{
PathSet pathSet = (PathSet) registeredFiles.get( id );
if ( pathSet == null )
{
pathSet = new PathSet();
registeredFiles.put( id, pathSet );
}
return pathSet;
}
private void doRegister( String id, String path )
{
getFullStructure().add( path );
getStructure( id ).add( path );
}
private Object readResolve()
{
// the full structure should be resolved so let's rebuild it
this.allFiles = new PathSet();
final Iterator it = registeredFiles.values().iterator();
while ( it.hasNext() )
{
PathSet pathSet = (PathSet) it.next();
this.allFiles.addAll( pathSet );
}
return this;
}
/**
* Callback interfce to handle events related to filepath registration in
* the webapp.
*/
public interface RegistrationCallback
{
/**
* Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
* has been registered successfully.
* <p/>
* This means that the <tt>targetFilename</tt> was unknown and has been
* registered successfully.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @throws IOException if an error occured while handling this event
*/
void registered( String ownerId, String targetFilename )
throws IOException;
/**
* Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
* has already been registered.
* <p/>
* This means that the <tt>targetFilename</tt> was known and belongs to the
* specified owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @throws IOException if an error occured while handling this event
*/
void alreadyRegistered( String ownerId, String targetFilename )
throws IOException;
/**
* Called if the registration of the <tt>targetFilename</tt> for the
* specified <tt>ownerId</tt> has been refused since the path already
* belongs to the <tt>actualOwnerId</tt>.
* <p/>
* This means that the <tt>targetFilename</tt> was known and does not
* belong to the specified owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @param actualOwnerId the actual owner
* @throws IOException if an error occured while handling this event
*/
void refused( String ownerId, String targetFilename, String actualOwnerId )
throws IOException;
/**
* Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
* has been registered successfully by superseding a <tt>deprecatedOwnerId</tt>,
* that is the previous owner of the file.
* <p/>
* This means that the <tt>targetFilename</tt> was known but for another
* owner. This usually happens after a project's configuration change. As a
* result, the file has been registered successfully to the new owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @param deprecatedOwnerId the previous owner that does not exist anymore
* @throws IOException if an error occured while handling this event
*/
void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
throws IOException;
/**
* Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt>
* has been registered successfully by superseding a <tt>unknownOwnerId</tt>,
* that is an owner that does not exist anymore in the current project.
* <p/>
* This means that the <tt>targetFilename</tt> was known but for an owner that
* does not exist anymore. Hence the file has been registered successfully to
* the new owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @param unknownOwnerId the previous owner that does not exist anymore
* @throws IOException if an error occured while handling this event
*/
void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
throws IOException;
}
}

View File

@@ -0,0 +1,93 @@
package org.alfresco.maven.plugin.amp.util;
import com.thoughtworks.xstream.XStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* Serializes {@link AmpStructure} back and forth.
*
* @author Stephane Nicoll
*/
public class AmpStructureSerializer
{
private final XStream xStream;
/**
* Creates a new instance of the serializer.
*/
public AmpStructureSerializer()
{
this.xStream = new XStream();
// Register aliases
xStream.alias( "webapp-structure", AmpStructure.class );
xStream.alias( "path-set", PathSet.class );
}
/**
* Reads the {@link AmpStructure} from the specified file.
*
* @param file the file containing the webapp structure
* @return the webapp structure
* @throws IOException if an error occured while reading the structure
*/
public AmpStructure fromXml( File file )
throws IOException
{
FileReader reader = null;
try
{
reader = new FileReader( file );
return (AmpStructure) xStream.fromXML( reader );
}
finally
{
if ( reader != null )
{
reader.close();
}
}
}
/**
* Saves the {@link AmpStructure} to the specified file.
*
* @param webappStructure the structure to save
* @param targetFile the file to use to save the structure
* @throws IOException if an error occured while saving the webapp structure
*/
public void toXml( AmpStructure webappStructure, File targetFile )
throws IOException
{
FileWriter writer = null;
try
{
if ( !targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs() )
{
throw new IOException(
"Could not create parent[" + targetFile.getParentFile().getAbsolutePath() + "]" );
}
if ( !targetFile.exists() && !targetFile.createNewFile() )
{
throw new IOException( "Could not create file[" + targetFile.getAbsolutePath() + "]" );
}
writer = new FileWriter( targetFile );
xStream.toXML( webappStructure, writer );
}
finally
{
if ( writer != null )
{
writer.close();
}
}
}
}

View File

@@ -0,0 +1,61 @@
package org.alfresco.maven.plugin.amp.util;
/*
* 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.util.AbstractMap;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* @version $Id: CompositeMap.java 565036 2007-08-12 10:26:14Z snicoll $
* @todo merge with resources/assembly plugin
*/
public class CompositeMap
extends AbstractMap
{
private Map recessive;
private Map dominant;
public CompositeMap( Map dominant, Map recessive )
{
this.dominant = Collections.unmodifiableMap( dominant );
this.recessive = Collections.unmodifiableMap( recessive );
}
public synchronized Object get( Object key )
{
Object value = dominant.get( key );
if ( value == null )
{
value = recessive.get( key );
}
return value;
}
public Set entrySet()
{
throw new UnsupportedOperationException( "Cannot enumerate properties in a composite map" );
}
}

View File

@@ -0,0 +1,95 @@
package org.alfresco.maven.plugin.amp.util;
/*
* 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 org.apache.maven.artifact.Artifact;
import org.codehaus.plexus.util.interpolation.ObjectBasedValueSource;
import org.codehaus.plexus.util.interpolation.RegexBasedInterpolator;
import org.codehaus.plexus.util.interpolation.ValueSource;
import java.util.Properties;
/**
* Utilities used to evaluate expression.
* <p/>
* TODO: this comes from the assembly plugin; refactor when it's shared.
* <p/>
* The expression might use any fied of the {@link Artifact} interface. Some
* examples might be:
* <ul>
* <li>${artifactId}-${version}.${extension}</li>
* <li>${artifactId}.${extension}</li>
* </ul>
*
* @author Stephane Nicoll
*/
public class MappingUtils
{
/**
* Evaluates the specified expression for the given artifact.
*
* @param expression the expression to evaluate
* @param artifact the artifact to use as value object for tokens
* @return expression the evaluated expression
*/
public static String evaluateFileNameMapping( String expression, Artifact artifact )
{
String value = expression;
// FIXME: This is BAD! Accessors SHOULD NOT change the behavior of the object.
artifact.isSnapshot();
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
interpolator.addValueSource( new ObjectBasedValueSource( artifact ) );
interpolator.addValueSource( new ObjectBasedValueSource( artifact.getArtifactHandler() ) );
Properties classifierMask = new Properties();
classifierMask.setProperty( "classifier", "" );
interpolator.addValueSource( new PropertiesInterpolationValueSource( classifierMask ) );
value = interpolator.interpolate( value, "__artifact" );
return value;
}
static class PropertiesInterpolationValueSource
implements ValueSource
{
private final Properties properties;
public PropertiesInterpolationValueSource( Properties properties )
{
this.properties = properties;
}
public Object getValue( String key )
{
return properties.getProperty( key );
}
}
}

View File

@@ -0,0 +1,284 @@
package org.alfresco.maven.plugin.amp.util;
/*
* 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 org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.StringUtils;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Set of file's paths.
* <p/>
* The class extends functionality of a "normal" set of strings by a process of
* the paths normalization. All paths are converted to unix form (slashes) and
* they don't start with starting /.
*
* @author Piotr Tabor
*/
public class PathSet
{
/**
* Set of normalized paths
*/
private Set/* <String> */pathsSet = new LinkedHashSet();
/**
* The method normalizes the path.
* <p/>
* <ul>
* <li>changes directory separator to unix's separator(/)</li>
* <li>deletes all trailing slashes</li>
* </ul>
*
* @param path to normalization
* @return normalized path
*/
protected String normalizeFilePath( String path )
{
return normalizeFilePathStatic( path );
}
/*-------------------- Business interface ------------------------------*/
/**
* Creates an empty paths set
*/
public PathSet()
{
/*Empty default constructor*/
}
/**
* Creates paths set and normalizate and adds all 'paths'.
* The source 'paths' will not be changed
*
* @param paths to be added
*/
public PathSet( Collection/*String>*/ paths )
{
addAll( paths );
}
/**
* Creates paths set and normalizate and adds all 'paths'.
* The source 'paths' will not be changed
*
* @param paths to be added
*/
public PathSet( String[] paths )
{
addAll( paths );
}
/**
* Normalizes and adds given path to the set.
*
* @param path to be added
*/
public void add( String path )
{
pathsSet.add( normalizeFilePath( path ) );
}
/**
* Normalizes and adds given paths (collection of strings)
* to the set. The source collection will not be changed
*
* @param paths - collection of strings to be added
* @param prefix added to all given paths
*/
public void addAll( Collection/*<String>*/ paths, String prefix )
{
for ( Iterator iter = paths.iterator(); iter.hasNext(); )
{
add( prefix + iter.next() );
}
}
/**
* Normalizes and adds given paths to the set.
* The source collection will not be changed
*
* @param paths to be added
* @param prefix added to all given paths
*/
public void addAll( String[] paths, String prefix )
{
for ( int i = 0; i < paths.length; i++ )
{
add( prefix + paths[i] );
}
}
/**
* Adds given paths to the set.
* The source collection will not be changed
*
* @param paths to be added
* @param prefix added to all given paths
*/
public void addAll( PathSet paths, String prefix )
{
for ( Iterator iter = paths.iterator(); iter.hasNext(); )
{
add( prefix + iter.next() );
}
}
/**
* Normalizes and adds given paths (collection of strings)
* to the set. The source collection will not be changed
*
* @param paths - collection of strings to be added
*/
public void addAll( Collection/*<String>*/ paths )
{
addAll( paths, "" );
}
/**
* Normalizes and adds given paths to the set.
* The source collection will not be changed
*
* @param paths to be added
*/
public void addAll( String[] paths )
{
addAll( paths, "" );
}
/**
* Adds given paths to the set.
* The source collection will not be changed
*
* @param paths to be added
*/
public void addAll( PathSet paths )
{
addAll( paths, "" );
}
/**
* Checks if the set constains given path. The path is normalized
* before check.
*
* @param path we are looking for in the set.
* @return information if the set constains the path.
*/
public boolean contains( String path )
{
return pathsSet.contains( normalizeFilePath( path ) );
}
/**
* Returns iterator of normalized paths (strings)
*
* @return iterator of normalized paths (strings)
*/
public Iterator iterator()
{
return pathsSet.iterator();
}
/**
* Adds given prefix to all paths in the set.
* <p/>
* The prefix should be ended by '/'. The generated paths are normalized.
*
* @param prefix to be added to all items
*/
public void addPrefix( String prefix )
{
final Set/*<String>*/ newSet = new HashSet();
for ( Iterator iter = pathsSet.iterator(); iter.hasNext(); )
{
String path = (String) iter.next();
newSet.add( normalizeFilePath( prefix + path ) );
}
pathsSet = newSet;
}
/**
* Returns count of the paths in the set
*
* @return count of the paths in the set
*/
public int size()
{
return pathsSet.size();
}
/**
* Adds to the set all files in the given directory
*
* @param directory that will be searched for file's paths to add
* @param prefix to be added to all found files
*/
public void addAllFilesInDirectory( File directory, String prefix )
{
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir( directory );
scanner.scan();
addAll( scanner.getIncludedFiles(), prefix );
}
/*-------------------- Universal static mathods ------------------------*/
/**
* The method normalizes the path.
* <p/>
* <ul>
* <li>changes directory separator to unix's separator(/)</li>
* <li>deletes all trailing slashes</li>
* </ul>
*
* @param path to normalization
* @return normalized path
*/
public static String normalizeFilePathStatic( String path )
{
return trimTrailingSlashes( StringUtils.replace( path, '\\', '/' ) );
}
/**
* The method deletes all trailing slashes from the given string
*
* @param str a string
* @return trimed string
*/
public static String trimTrailingSlashes( String str )
{
int i;
for ( i = 0; i < str.length() && str.charAt( i ) == '/'; i++ )
/* just calculate i */
{
}
return str.substring( i );
}
}

View File

@@ -0,0 +1,155 @@
package org.alfresco.maven.plugin.amp.util;
/*
* 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 org.codehaus.plexus.util.IOUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
/**
* @author <a href="mailto:kenney@neonics.com">Kenney Westerhof</a>
* @version $Id: PropertyUtils.java 565036 2007-08-12 10:26:14Z snicoll $
* @todo this is duplicated from the resources plugin - migrate to plexus-util
*/
public final class PropertyUtils
{
private PropertyUtils()
{
// prevent instantiation
}
/**
* Reads a property file, resolving all internal variables.
*
* @param propfile The property file to load
* @param fail wheter to throw an exception when the file cannot be loaded or to return null
* @param useSystemProps wheter to incorporate System.getProperties settings into the returned Properties object.
* @return the loaded and fully resolved Properties object
* @throws IOException if an error failed while loading the properties
*/
public static Properties loadPropertyFile( File propfile, boolean fail, boolean useSystemProps )
throws IOException
{
Properties props = new Properties();
if ( useSystemProps )
{
props = new Properties( System.getProperties() );
}
if ( propfile.exists() )
{
FileInputStream inStream = new FileInputStream( propfile );
try
{
props.load( inStream );
}
finally
{
IOUtil.close( inStream );
}
}
else if ( fail )
{
throw new FileNotFoundException( propfile.toString() );
}
for ( Enumeration n = props.propertyNames(); n.hasMoreElements(); )
{
String k = (String) n.nextElement();
props.setProperty( k, PropertyUtils.getPropertyValue( k, props ) );
}
return props;
}
/**
* Retrieves a property value, replacing values like ${token}
* using the Properties to look them up.
* <p/>
* It will leave unresolved properties alone, trying for System
* properties, and implements reparsing (in the case that
* the value of a property contains a key), and will
* not loop endlessly on a pair like
* test = ${test}.
*
* @param k the token
* @param p the properties containing the filter values
* @return the value
*/
private static String getPropertyValue( String k, Properties p )
{
// This can also be done using InterpolationFilterReader,
// but it requires reparsing the file over and over until
// it doesn't change.
String v = p.getProperty( k );
String ret = "";
int idx, idx2;
while ( ( idx = v.indexOf( "${" ) ) >= 0 )
{
// append prefix to result
ret += v.substring( 0, idx );
// strip prefix from original
v = v.substring( idx + 2 );
// if no matching } then bail
if ( ( idx2 = v.indexOf( '}' ) ) < 0 )
{
break;
}
// strip out the key and resolve it
// resolve the key/value for the ${statement}
String nk = v.substring( 0, idx2 );
v = v.substring( idx2 + 1 );
String nv = p.getProperty( nk );
// try global environment..
if ( nv == null )
{
nv = System.getProperty( nk );
}
// if the key cannot be resolved,
// leave it alone ( and don't parse again )
// else prefix the original string with the
// resolved property ( so it can be parsed further )
// taking recursion into account.
if ( nv == null || nv.equals( k ) )
{
ret += "${" + nk + "}";
}
else
{
v = nv + v;
}
}
return ret + v;
}
}

View File

@@ -0,0 +1,60 @@
package org.alfresco.maven.plugin.amp.util;
/*
* 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 org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.introspection.ReflectionValueExtractor;
import java.util.AbstractMap;
import java.util.Set;
/**
* @version $Id: ReflectionProperties.java 565036 2007-08-12 10:26:14Z snicoll $
* @todo merge with resources/assembly plugin
*/
public class ReflectionProperties
extends AbstractMap
{
private MavenProject project;
public ReflectionProperties( MavenProject project )
{
this.project = project;
}
public synchronized Object get( Object key )
{
Object value = null;
try
{
value = ReflectionValueExtractor.evaluate( String.valueOf( key ), project );
}
catch ( Exception e )
{
//TODO: remove the try-catch block when ReflectionValueExtractor.evaluate() throws no more exceptions
}
return value;
}
public Set entrySet()
{
throw new UnsupportedOperationException( "Cannot enumerate properties in a project" );
}
}

View File

@@ -0,0 +1,114 @@
package org.alfresco.plexus.archiver;
import java.io.File;
import java.io.IOException;
import org.codehaus.plexus.archiver.*;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.zip.ZipOutputStream;
public class AmpArchiver extends JarArchiver
{
public AmpArchiver()
{
super.archiveType = "amp";
}
public void setModuleProperties(File descr)
throws ArchiverException
{
// deploymentDescriptor = descr;
// if(!deploymentDescriptor.exists())
// {
// throw new ArchiverException("Deployment descriptor: " + deploymentDescriptor + " does not exist.");
// } else
// {
// addFile(descr, "config/AMP-INF/module.properties");
// return;
// }
}
public void addLib(File fileName)
throws ArchiverException
{
addDirectory(fileName.getParentFile(), "lib/", new String[] {
fileName.getName()
}, null);
}
public void addLibs(File directoryName, String includes[], String excludes[])
throws ArchiverException
{
addDirectory(directoryName, "lib/", includes, excludes);
}
public void addClass(File fileName)
throws ArchiverException
{
addDirectory(fileName.getParentFile(), "classes/", new String[] {
fileName.getName()
}, null);
}
public void addClasses(File directoryName, String includes[], String excludes[])
throws ArchiverException
{
addDirectory(directoryName, "classes/", includes, excludes);
}
protected void initZipOutputStream(ZipOutputStream zOut)
throws IOException, ArchiverException
{
// if(deploymentDescriptor == null && !isInUpdateMode())
// {
// throw new ArchiverException("module properies attribute is required");
// }
// else
// {
super.initZipOutputStream(zOut);
return;
// }
}
protected void zipFile(ArchiveEntry entry, ZipOutputStream zOut, String vPath, int mode)
throws IOException, ArchiverException
{
if(vPath.equalsIgnoreCase("config/AMP-INF/module.properties"))
{
if(deploymentDescriptor == null || !deploymentDescriptor.getCanonicalPath().equals(entry.getFile().getCanonicalPath()) || descriptorAdded)
{
getLogger().warn("Warning: selected " + super.archiveType + " files include a config/AMP-INF/module.properites which will be ignored " + "(please use webxml attribute to " + super.archiveType + " task)");
}
else
{
super.zipFile(entry, zOut, vPath);
descriptorAdded = true;
}
}
else
{
super.zipFile(entry, zOut, vPath);
}
}
protected void cleanUp()
{
descriptorAdded = false;
super.cleanUp();
}
private File deploymentDescriptor;
private boolean descriptorAdded;
/**
* @see org.codehaus.plexus.archiver.AbstractArchiver#addDirectory(java.io.File, java.lang.String, java.lang.String[], java.lang.String[])
*/
public void addDirectory(File pArg0, String pArg1, String[] pArg2, String[] pArg3)
throws ArchiverException
{
/* */
getLogger().info("adding directory [ '"+pArg0+"' '"+pArg1+"']");
super.addDirectory(pArg0, pArg1, pArg2, pArg3);
}
}

View File

@@ -0,0 +1,270 @@
package org.alfresco.plexus.archiver;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipException;
import org.codehaus.plexus.archiver.ArchiveFilterException;
import org.codehaus.plexus.archiver.ArchiveFinalizer;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.util.FilterSupport;
import org.codehaus.plexus.archiver.zip.AbstractZipUnArchiver;
import org.codehaus.plexus.archiver.zip.ZipEntry;
import org.codehaus.plexus.archiver.zip.ZipFile;
import org.codehaus.plexus.util.FileUtils;
public class AmpUnArchiver extends AbstractZipUnArchiver {
private static final String NATIVE_ENCODING = "native-encoding";
private String encoding = "UTF8";
private FilterSupport filterSupport;
private List finalizers;
private static String fileSeparator = System.getProperty("file.separator");
public void setArchiveFilters( List filters )
{
filterSupport = new FilterSupport( filters, getLogger() );
}
/**
* Sets the encoding to assume for file names and comments.
* <p/>
* <p>Set to <code>native-encoding</code> if you want your
* platform's native encoding, defaults to UTF8.</p>
*/
public void setEncoding( String encoding )
{
if ( NATIVE_ENCODING.equals( encoding ) )
{
encoding = null;
}
this.encoding = encoding;
}
private static Map<String,String> ampMapping = new HashMap<String,String>();
@Override
protected void execute()
throws ArchiverException, IOException
{
getLogger().info( "Expanding: " + getSourceFile() + " into " + getDestDirectory() );
ZipFile zf = null;
try
{
zf = new ZipFile( getSourceFile(), encoding );
Enumeration e = zf.getEntries();
String moduleId = getModuleId(zf);
// Based on the current AMP name creates the appropriate mapping to alfresco/module/<ampname>
createAmpMapping(moduleId);
while ( e.hasMoreElements() )
{
ZipEntry ze = (ZipEntry) e.nextElement();
if (!ze.getName().startsWith("META-INF"))
{
String fileInAmp = getAmpMapping(ze.getName());
extractFileIfIncluded( getSourceFile(), getDestDirectory(), zf.getInputStream( ze ), fileInAmp, new Date( ze.getTime() ), ze.isDirectory() );
}
}
runArchiveFinalizers();
getLogger().debug( "expand complete" );
}
catch ( IOException ioe )
{
throw new ArchiverException( "Error while expanding " + getSourceFile().getAbsolutePath(), ioe );
}
finally
{
if ( zf != null )
{
try
{
zf.close();
}
catch ( IOException e )
{
//ignore
}
}
}
}
private String getModuleId(ZipFile zf) throws IOException, ZipException, ArchiverException {
ZipEntry modulePropertiesEntry = zf.getEntry("module.properties");
Properties moduleProperties = new Properties();
moduleProperties.load(zf.getInputStream(modulePropertiesEntry));
String moduleId = moduleProperties.getProperty("module.id");
if(moduleId == null || "".equals(moduleId))
throw new ArchiverException("module.id property not found in module.properties");
return moduleId;
}
private String getAmpMapping(String name) {
if(name.startsWith("web"+ fileSeparator) && !name.startsWith("web"+ fileSeparator +"licenses"))
{
return name.substring(4);
}
for (Map.Entry<String, String> mapElelement : ampMapping.entrySet()) {
if(name.startsWith(mapElelement.getKey()))
{
String relativePath = "";
if((name.startsWith("config"+fileSeparator)))
{
relativePath = name.substring(7);
}
else
relativePath = FileUtils.removePath(name);
return mapElelement.getValue() + relativePath;
}
}
return "";
}
private void createAmpMapping(String moduleId) {
File zipFile = getSourceFile();
String ampName = zipFile.getName();
ampName = FileUtils.removeExtension(FileUtils.removePath(ampName));
ampName = ampName.substring(0, ampName.lastIndexOf('-'));
ampMapping.put("module.properties", "WEB-INF"+ fileSeparator +"classes" + fileSeparator +"alfresco" + fileSeparator +"module" + fileSeparator + moduleId + fileSeparator);
ampMapping.put("config", "WEB-INF"+fileSeparator+"classes"+ fileSeparator);
ampMapping.put("lib", "WEB-INF" + fileSeparator + "lib" +fileSeparator);
ampMapping.put("web"+ fileSeparator +"licenses", "WEB-INF" +fileSeparator);
}
private void extractFileIfIncluded( File sourceFile, File destDirectory, InputStream inputStream, String name,
Date time, boolean isDirectory )
throws IOException, ArchiverException
{
try
{
if ( filterSupport == null || filterSupport.include( inputStream, name ) )
{
extractFile( sourceFile, destDirectory, inputStream, name, time, isDirectory );
}
}
catch ( ArchiveFilterException e )
{
throw new ArchiverException( "Error verifying \'" + name + "\' for inclusion: " + e.getMessage(), e );
}
}
protected void extractFile( File srcF, File dir, InputStream compressedInputStream, String entryName,
Date entryDate, boolean isDirectory )
throws IOException
{
File f = FileUtils.resolveFile( dir, entryName );
try
{
if ( !isOverwrite() && f.exists() && f.lastModified() >= entryDate.getTime() )
{
getLogger().debug( "Skipping " + f + " as it is up-to-date" );
return;
}
getLogger().debug( "expanding " + entryName + " to " + f );
// create intermediary directories - sometimes zip don't add them
File dirF = f.getParentFile();
if ( dirF != null )
{
dirF.mkdirs();
}
if ( isDirectory )
{
f.mkdirs();
}
else
{
byte[] buffer = new byte[1024];
int length;
FileOutputStream fos = null;
try
{
fos = new FileOutputStream( f );
while ( ( length = compressedInputStream.read( buffer ) ) >= 0 )
{
fos.write( buffer, 0, length );
}
fos.close();
fos = null;
}
finally
{
if ( fos != null )
{
try
{
fos.close();
}
catch ( IOException e )
{
// ignore
}
}
}
}
f.setLastModified( entryDate.getTime() );
}
catch ( FileNotFoundException ex )
{
getLogger().warn( "Unable to expand to file " + f.getPath() );
}
}
public void setArchiveFinalizers( List archiveFinalizers )
{
this.finalizers = archiveFinalizers;
}
protected void runArchiveFinalizers()
throws ArchiverException
{
if ( finalizers != null )
{
for ( Iterator it = finalizers.iterator(); it.hasNext(); )
{
ArchiveFinalizer finalizer = (ArchiveFinalizer) it.next();
finalizer.finalizeArchiveExtraction( this );
}
}
}
}

View File

@@ -0,0 +1,17 @@
<lifecycles>
<lifecycle>
<id>amp</id>
<phases>
<phase>
<id>package</id>
<executions>
<execution>
<goals>
<goal>amp</goal>
</goals>
</execution>
</executions>
</phase>
</phases>
</lifecycle>
</lifecycles>

View File

@@ -0,0 +1,53 @@
<component-set>
<components>
<component>
<role>org.apache.maven.artifact.handler.ArtifactHandler</role>
<role-hint>amp</role-hint>
<implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
<configuration>
<extension>amp</extension>
<type>zip</type>
<packaging>zip</packaging>
<language>java</language>
<addedToClasspath>true</addedToClasspath>
<includesDependencies>true</includesDependencies>
</configuration>
</component>
<component>
<role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
<role-hint>amp</role-hint>
<implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
<configuration>
<phases>
<process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
<compile>org.apache.maven.plugins:maven-compiler-plugin:compile</compile>
<process-test-resources>org.apache.maven.plugins:maven-resources-plugin:testResources</process-test-resources>
<test-compile>org.apache.maven.plugins:maven-compiler-plugin:testCompile</test-compile>
<test>org.apache.maven.plugins:maven-surefire-plugin:test</test>
<package>org.alfresco.maven.plugin:maven-amp-plugin:amp</package>
<install>org.apache.maven.plugins:maven-install-plugin:install</install>
<deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
</phases>
</configuration>
</component>
<component>
<role>org.codehaus.plexus.archiver.Archiver</role>
<role-hint>amp</role-hint>
<implementation>org.alfresco.plexus.archiver.AmpArchiver</implementation>
<instantiation-strategy>per-lookup</instantiation-strategy>
</component>
<!-- A amp requires an MMT like behaviour when unpacked in a war. The default assumption archiver role-hint = file-extension is used in
the maven-war-plugin, so we use role-hint=amp here to make unpacking seamless.
Not used byt the maven-amp-plugin which uses a plain zip unpacker
-->
<component>
<role>org.codehaus.plexus.archiver.UnArchiver</role>
<role-hint>amp</role-hint>
<implementation>org.alfresco.plexus.archiver.AmpUnArchiver</implementation>
</component>
</components>
</component-set>

View File

@@ -0,0 +1,37 @@
-----
Maven AMP Plugin Plexus Components
-----
AMP Lifecycle Mapping
This plugin provides support for <packaging>amp</packaging> type of projects. \
Lifecycle of an Alfresco modules is mapped in the file:
{{ ${site_tags_url}/${site_pom_artifactId}-${site_pom_version}/src/main/resources/META-INF/plexus/components.xml }}
This build produces an Alfresco compatible AMP as main build product. It supports (being derived from maven-war-plugin)
overlay of modules and transitive AMP dependency packing.
An AMP depending on one ore more AMP will package those AMP in the final product of the build:
overlays can be configured same as in {{ http://maven.apache.org/plugins/maven-war-plugin/overlays.html }}.
A plain zip UnArchiver is used for this overlay.
AMP -> WAR Unarchiver
The default UnArchiver (role-hint="amp") used by the default maven infrastructure for .amp files is a custom UnArchiver
which behaves as the MMT, unarchiving AMPs in the proper places as dictated by {{{http://wiki.alfresco.com/wiki/AMP_Files} Alfresco AMP convention}} .
This little component allows any plugin to manage .amp dependencies in case the maven-amp-plugin is declared with <extensions>true</extensions> in
the current POM, basically supporting AMPs in Maven with no need for custom external tools like MMT.
See {{ ${site_tags_url}/${site_pom_artifactId}-${site_pom_version}/src/main/resources/META-INF/plexus/components.xml }}
AMP Artifact Handler
Instructs maven which type of Archive is the AMP, providing info about its inclusion in the classpath or the fact that already contains
its dependencies.

View File

@@ -0,0 +1,55 @@
-----
Maven AMP Plugin
-----
Welcome to the Maven Alfresco AMP plugin home
You've reached the ({{ ${site_pom_url} }}) Maven Alfresco Extension archetype home page (version: ${site_pom_version})
Description
${site_pom_description}
References
Deployed at: {{ http://repository.sourcesense.com/maven2/org/alfresco/maven/plugin }}
Maven usage:
------------------------------------------
<dependency>
<groupId>${site_pom_groupId}</groupId>
<artifactId>${site_pom_artifactId}</artifactId>
<version>${site_pom_version}</version>
<!-- Used to have AMP lifecycle available -->
<extensions>true</extensions>
</dependency>
-------------------------------------------
using repository (either in POM or settings.xml) :
------------------------------------------
<repository>
<id>ss-public</id>
<url>http://repository.sourcesense.com/maven2</url>
</repository>
-------------------------------------------
Quick usage
Create a project using the maven-alfresco-amp-archetype (see {{ http://repository.sourcesense.com/maven2-sites/maven-alfresco-amp-archetype }} ).

View File

@@ -0,0 +1,38 @@
<project>
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-stylus-skin</artifactId>
<version>1.0</version>
</skin>
<poweredBy>
<logo name="Maven" href="http://maven.apache.org" img="http://maven.apache.org/images/logos/maven-feather.png"/>
<logo name="Alfresco - Open source ECM" img="http://alfresco.com/assets/images/icons/powered_by_alfresco.gif" href="http://www.sourcesense.com" />
</poweredBy>
<publishDate position="navigation-bottom" format="MM-dd-yy"/>
<bannerLeft>
<name>Maven AMP Plugin - v. ${project.version}</name>
<href>${site_site_url}</href>
</bannerLeft>
<body>
<links>
<item name="Maven" href="http://maven.apache.org/"/>
<item name="Apache" href="http://www.apache.org/"/>
</links>
<menu name="Plugin info">
<item name="Goals" href="plugin-info.html"/>
<item name="Components" href="components.html"/>
</menu>
<menu name="Used by">
<item name="Maven Alfresco AMP Archetype" href="http://repository.sourcesense.com/maven2-sites/maven-alfresco-amp-archetype"/>
</menu>
<menu name="See also">
<item name="Maven Alfresco Extension Archetype" href="http://repository.sourcesense.com/maven2-sites/maven-alfresco-extension-archetype"/>
</menu>
<menu ref="reports"/>
</body>
</project>