diff --git a/plugins/maven-amp-plugin/pom.xml b/plugins/maven-amp-plugin/pom.xml
new file mode 100644
index 00000000..bca9d462
--- /dev/null
+++ b/plugins/maven-amp-plugin/pom.xml
@@ -0,0 +1,180 @@
+
+
+ maven-plugins
+ org.apache.maven.plugins
+ 8
+
+ 4.0.0
+ org.alfresco.maven.plugin
+ maven-amp-plugin
+ maven-plugin
+ Alfresco AMP Plugin
+ 2.0.2-SNAPSHOT
+
+ This plugin defines a lifecycle for Alfresco ECM Modules packaging (.amp), handles AMP transitive dependencies from AMP and WAR projects,
+ providing a more enterprise oriented support alternative to the Alfresco MMT (module management tool) usage. It is used by
+ maven-alfresco-amp-archetype in order to package and test run an AMP and by the maven-alfresco-extension-archetype to depend transitively upon
+ AMPs and WARs. It is a modification of the org.apache.maven.plugins:maven-war-plugin of which it uses the whole core infrastructure. See
+ ${site.url} for more info
+
+ ${site.url}
+
+ 2.0.1
+
+
+ GForge
+ https://forge.alfresco.com/tracker/?group_id=121
+
+
+ https://forge.alfresco.com/svn/maven4alfresco/amp-plugin/branches/2.0.0
+ https://forge.alfresco.com/svn/maven4alfresco/amp-plugin/tags
+ https://repository.sourcesense.com/maven2-sites/${pom.artifactId}
+ ${pom.description}
+ ${pom.url}
+ ${pom.groupId}
+ ${pom.artifactId}
+ ${pom.version}
+ ${svn.tags.url}
+
+
+
+ mindthegab
+ Gabriele Columbro
+ g.columbro@sourcesense.com
+
+ Sourcesense
+ http://www.sourcesense.com
+
+
+ rdanner
+ Russ Danner
+
+ Rivet Logic
+
+
+
+
+ scm:svn:https://forge.alfresco.com/svn/maven4alfresco/amp-plugin/tags/maven-amp-plugin-2.0.0
+ https://forge.alfresco.com/svn/maven4alfresco/amp-plugin/tags/maven-amp-plugin-2.0.0
+ scm:svn:https://forge.alfresco.com/svn/maven4alfresco/amp-plugin/tags/maven-amp-plugin-2.0.0
+
+
+
+
+
+ maven-compiler-plugin
+
+ 1.5
+ 1.5
+
+
+
+ org.apache.maven.plugins
+ maven-changes-plugin
+
+
+
+ announcement-generate
+
+ announcement-generate
+
+
+
+ announcement-mail
+
+ announcement-mail
+
+
+
+
+ mail.sourcesense.com
+ 25
+
+ sysadmin@sourcesense.com
+ alfresco@sourcesense.com
+ alfresco-dev@lists.sourcesense.com
+ users@maven.apache.org
+ announce@maven.apache.org
+
+ ${smtp.username}
+ ${smtp.password}
+ mindthegab
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ 2.0-beta-5
+
+
+
+ clean package
+ deploy site:site site:deploy changes:announcement-mail
+ ${svn.tags.url}
+ -Dgpg.skip=true
+
+
+
+ maven-gpg-plugin
+ org.apache.maven.plugins
+
+ true
+
+
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ 2.0.6
+
+
+ org.apache.maven
+ maven-artifact
+ 2.0.6
+
+
+ org.apache.maven
+ maven-archiver
+ 2.2
+
+
+ org.codehaus.plexus
+ plexus-utils
+ 1.4.7
+
+
+ com.thoughtworks.xstream
+ xstream
+ 1.2.2
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ org.apache.maven.shared
+ maven-plugin-testing-harness
+ 1.1
+ test
+
+
+
+
+ ss-public
+ scp://repository.sourcesense.com/var/www/demo.sourcesense.com/maven2
+
+
+ ss-public
+ SS public repo
+ scp://repository.sourcesense.com/var/www/demo.sourcesense.com/maven2-snapshots
+
+
+ ss-site-public
+ scp://repository.sourcesense.com/var/www/demo.sourcesense.com/maven2-sites/${pom.artifactId}
+
+
+
diff --git a/plugins/maven-amp-plugin/src/changes/changes.xml b/plugins/maven-amp-plugin/src/changes/changes.xml
new file mode 100644
index 00000000..05b6b07e
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/changes/changes.xml
@@ -0,0 +1,21 @@
+
+
+ Maven AMP plugin
+
+
+
+
+ Now consistent maven plugin properties usage and support for AMP overlays and full AMP lifecycle
+
+
+ Added UnArchiver to mimic MMT behavior. Not needed anymore as AMPs gets properly unpacked into a WAR artifact.
+
+
+ Added plugin site documentation
+
+
+ Deployed on maven repository: http://repository.sourcesense.com/maven2/org/alfresco/maven/plugins
+
+
+
+
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AbstractAmpMojo.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AbstractAmpMojo.java
new file mode 100644
index 00000000..8f983c5e
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AbstractAmpMojo.java
@@ -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
+ *
+ * Classes, libraries and tld files are copied to
+ * the webappDirectory 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 List 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 List 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;
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AmpExplodedMojo.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AmpExplodedMojo.java
new file mode 100644
index 00000000..9ac9880a
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AmpExplodedMojo.java
@@ -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() );
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AmpMojo.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AmpMojo.java
new file mode 100644
index 00000000..ca81897f
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/AmpMojo.java
@@ -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.
+ *
+ * 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 false 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 ampArchiver property.
+ *
+ * The ampArchiver property
+ * @return Returns the internal value for the ampArchiver property.
+ */
+ protected AmpArchiver getAmpArchiver()
+ {
+ return this.mAmpArchiver;
+ }
+
+ /**
+ * set the internal value for the ampArchiver property
+ * @param pAmpArchiver The ampArchiver to set.
+ */
+ protected void setAmpArchiver(AmpArchiver pAmpArchiver)
+ {
+ this.mAmpArchiver = pAmpArchiver;
+ }
+
+ /**
+ * get the the internal value for the ampName property.
+ *
+ * The ampName property
+ * @return Returns the internal value for the ampName property.
+ */
+ protected String getAmpName()
+ {
+ return this.mAmpName;
+ }
+
+ /**
+ * set the internal value for the ampName property
+ * @param pAmpName The ampName to set.
+ */
+ protected void setAmpName(String pAmpName)
+ {
+ this.mAmpName = pAmpName;
+ }
+
+ /**
+ * get the the internal value for the outputDirectory property.
+ *
+ * The outputDirectory property
+ * @return Returns the internal value for the outputDirectory property.
+ */
+ protected String getOutputDirectory()
+ {
+ return this.mOutputDirectory;
+ }
+
+ /**
+ * set the internal value for the outputDirectory property
+ * @param pOutputDirectory The outputDirectory to set.
+ */
+ protected void setOutputDirectory(String pOutputDirectory)
+ {
+ this.mOutputDirectory = pOutputDirectory;
+ }
+
+ /**
+ * get the the internal value for the primaryArtifact property.
+ *
+ * The primaryArtifact property
+ * @return Returns the internal value for the primaryArtifact property.
+ */
+ protected boolean isPrimaryArtifact()
+ {
+ return this.mPrimaryArtifact;
+ }
+
+ /**
+ * set the internal value for the primaryArtifact property
+ * @param pPrimaryArtifact The primaryArtifact to set.
+ */
+ protected void setPrimaryArtifact(boolean pPrimaryArtifact)
+ {
+ this.mPrimaryArtifact = pPrimaryArtifact;
+ }
+
+ /**
+ * get the the internal value for the projectHelper property.
+ *
+ * The projectHelper property
+ * @return Returns the internal value for the projectHelper property.
+ */
+ protected MavenProjectHelper getProjectHelper()
+ {
+ return this.mProjectHelper;
+ }
+
+ /**
+ * set the internal value for the projectHelper property
+ * @param pProjectHelper The projectHelper 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 classifier property
+ * @param pClassifier The classifier 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 mode 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;
+}
+
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/Overlay.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/Overlay.java
new file mode 100644
index 00000000..84d4d708
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/Overlay.java
@@ -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.
+ *
+ * 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.
+ *
+ * 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()] );
+ }
+ }
+
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/DefaultOverlay.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/DefaultOverlay.java
new file mode 100644
index 00000000..fa372807
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/DefaultOverlay.java
@@ -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 );
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/InvalidOverlayConfigurationException.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/InvalidOverlayConfigurationException.java
new file mode 100644
index 00000000..6f03e751
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/InvalidOverlayConfigurationException.java
@@ -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 );
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/OverlayManager.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/OverlayManager.java
new file mode 100644
index 00000000..4a00119d
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/overlay/OverlayManager.java
@@ -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.
+ *
+ * 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.
+ *
+ * If the overlay defines the current project, null 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;
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AbstractAmpPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AbstractAmpPackagingTask.java
new file mode 100644
index 00000000..4299c00b
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AbstractAmpPackagingTask.java
@@ -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.
+ *
+ * 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.
+ *
+ * If the structure of the source directory is not the same as the root of the
+ * webapp, use the targetPrefix parameter to specify in which particular
+ * directory the files should be copied. Use null 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 sourceFilesSet 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.
+ *
+ * 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 sourceFilesSet 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.
+ *
+ * The targetFileName 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.
+ *
+ * The targetFileName 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 destination
+ * will be created if they don't already exist. if the onlyIfModified flag
+ * is false, destination will be overwritten if it already exists. If the
+ * flag is true destination will be overwritten if it's not up to date.
+ *
+ *
+ * @param context the packaging context
+ * @param source an existing non-directory File to copy bytes from
+ * @param destination a non-directory File 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 source does not exist, destination 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 null 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.
+ *
+ * If the outputFileNameMapping 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 );
+ }
+
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPackagingContext.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPackagingContext.java
new file mode 100644
index 00000000..cee7bca2
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPackagingContext.java
@@ -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 null
+ * if no file name mapping is set.
+ *
+ * @return the output file name mapping or null
+ */
+ 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.
+ *
+ * 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();
+
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPackagingTask.java
new file mode 100644
index 00000000..bc9b81ac
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPackagingTask.java
@@ -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.
+ *
+ * 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;
+
+
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPostPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPostPackagingTask.java
new file mode 100644
index 00000000..a37edcf3
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpPostPackagingTask.java
@@ -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.
+ *
+ * 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;
+
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpProjectPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpProjectPackagingTask.java
new file mode 100644
index 00000000..377a9f32
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/AmpProjectPackagingTask.java
@@ -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:
+ *
The list of web resources, if any
+ *
The content of the webapp directory if it exists
+ *
The custom deployment descriptor(s), if any
+ *
The content of the classes directory if it exists
+ *
The dependencies of the project
+ *
+ *
+ * @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();
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/ArtifactsPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/ArtifactsPackagingTask.java
new file mode 100644
index 00000000..ee9bbd86
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/ArtifactsPackagingTask.java
@@ -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;
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/ClassesPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/ClassesPackagingTask.java
new file mode 100644
index 00000000..b4315572
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/ClassesPackagingTask.java
@@ -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.
+ *
+ * Based on the {@link AmpPackagingContext#archiveClasses()} flag the resources
+ * either copied into to WEB-INF/classes directory or archived in a jar
+ * within the WEB-INF/lib 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." );
+ }
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/OverlayPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/OverlayPackagingTask.java
new file mode 100644
index 00000000..03616bff
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/OverlayPackagingTask.java
@@ -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.
+ *
+ * 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;
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/SaveAmpStructurePostPackagingTask.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/SaveAmpStructurePostPackagingTask.java
new file mode 100644
index 00000000..35a05877
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/packaging/SaveAmpStructurePostPackagingTask.java
@@ -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 );
+ }
+ }
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/AmpStructure.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/AmpStructure.java
new file mode 100644
index 00000000..7805b5e4
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/AmpStructure.java
@@ -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.
+ *
+ * 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 path 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 true
+ * if the path is not already registered, false 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 callback 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 path. If the file is not
+ * registered, returns null
+ *
+ * @param path the relative path from the webapp root directory
+ * @return the owner or null.
+ */
+ 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.
+ *
+ * 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 targetFilename for the specified ownerId
+ * has been registered successfully.
+ *
+ * This means that the targetFilename 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 targetFilename for the specified ownerId
+ * has already been registered.
+ *
+ * This means that the targetFilename 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 targetFilename for the
+ * specified ownerId has been refused since the path already
+ * belongs to the actualOwnerId.
+ *
+ * This means that the targetFilename 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 targetFilename for the specified ownerId
+ * has been registered successfully by superseding a deprecatedOwnerId,
+ * that is the previous owner of the file.
+ *
+ * This means that the targetFilename 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 targetFilename for the specified ownerId
+ * has been registered successfully by superseding a unknownOwnerId,
+ * that is an owner that does not exist anymore in the current project.
+ *
+ * This means that the targetFilename 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;
+
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/AmpStructureSerializer.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/AmpStructureSerializer.java
new file mode 100644
index 00000000..2b8c0a05
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/AmpStructureSerializer.java
@@ -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();
+ }
+ }
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/CompositeMap.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/CompositeMap.java
new file mode 100644
index 00000000..651ebc51
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/CompositeMap.java
@@ -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" );
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/MappingUtils.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/MappingUtils.java
new file mode 100644
index 00000000..5f1c24f2
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/MappingUtils.java
@@ -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.
+ *
+ * TODO: this comes from the assembly plugin; refactor when it's shared.
+ *
+ * The expression might use any fied of the {@link Artifact} interface. Some
+ * examples might be:
+ *
+ *
${artifactId}-${version}.${extension}
+ *
${artifactId}.${extension}
+ *
+ *
+ * @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 );
+ }
+
+ }
+
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/PathSet.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/PathSet.java
new file mode 100644
index 00000000..0453db36
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/PathSet.java
@@ -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.
+ *
+ * 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/* */pathsSet = new LinkedHashSet();
+
+ /**
+ * The method normalizes the path.
+ *
+ *
+ *
changes directory separator to unix's separator(/)
+ *
deletes all trailing slashes
+ *
+ *
+ * @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/**/ 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/**/ 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.
+ *
+ * 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/**/ 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.
+ *
+ *
+ *
changes directory separator to unix's separator(/)
+ *
deletes all trailing slashes
+ *
+ *
+ * @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 );
+ }
+
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/PropertyUtils.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/PropertyUtils.java
new file mode 100644
index 00000000..b4f79ff6
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/PropertyUtils.java
@@ -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 Kenney Westerhof
+ * @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.
+ *
+ * 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;
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/ReflectionProperties.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/ReflectionProperties.java
new file mode 100644
index 00000000..197df8d5
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/maven/plugin/amp/util/ReflectionProperties.java
@@ -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" );
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/plexus/archiver/AmpArchiver.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/plexus/archiver/AmpArchiver.java
new file mode 100644
index 00000000..7ea605ea
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/plexus/archiver/AmpArchiver.java
@@ -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);
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/java/org/alfresco/plexus/archiver/AmpUnArchiver.java b/plugins/maven-amp-plugin/src/main/java/org/alfresco/plexus/archiver/AmpUnArchiver.java
new file mode 100644
index 00000000..3c01c2ac
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/java/org/alfresco/plexus/archiver/AmpUnArchiver.java
@@ -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.
+ *
+ *
Set to native-encoding if you want your
+ * platform's native encoding, defaults to UTF8.
+ */
+ public void setEncoding( String encoding )
+ {
+ if ( NATIVE_ENCODING.equals( encoding ) )
+ {
+ encoding = null;
+ }
+ this.encoding = encoding;
+ }
+
+
+ private static Map ampMapping = new HashMap();
+
+ @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/
+ 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 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 );
+ }
+ }
+ }
+}
diff --git a/plugins/maven-amp-plugin/src/main/resources/META-INF/maven/lifecycle.xml b/plugins/maven-amp-plugin/src/main/resources/META-INF/maven/lifecycle.xml
new file mode 100644
index 00000000..0bf27002
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/resources/META-INF/maven/lifecycle.xml
@@ -0,0 +1,17 @@
+
+
+ amp
+
+
+ package
+
+
+
+ amp
+
+
+
+
+
+
+
diff --git a/plugins/maven-amp-plugin/src/main/resources/META-INF/plexus/components.xml b/plugins/maven-amp-plugin/src/main/resources/META-INF/plexus/components.xml
new file mode 100644
index 00000000..cd7b9b6d
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/main/resources/META-INF/plexus/components.xml
@@ -0,0 +1,53 @@
+
+
+
+ org.apache.maven.artifact.handler.ArtifactHandler
+ amp
+ org.apache.maven.artifact.handler.DefaultArtifactHandler
+
+ amp
+ zip
+ zip
+ java
+ true
+ true
+
+
+
+
+ org.apache.maven.lifecycle.mapping.LifecycleMapping
+ amp
+ org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
+
+
+ org.apache.maven.plugins:maven-resources-plugin:resources
+ org.apache.maven.plugins:maven-compiler-plugin:compile
+ org.apache.maven.plugins:maven-resources-plugin:testResources
+ org.apache.maven.plugins:maven-compiler-plugin:testCompile
+ org.apache.maven.plugins:maven-surefire-plugin:test
+ org.alfresco.maven.plugin:maven-amp-plugin:amp
+ org.apache.maven.plugins:maven-install-plugin:install
+ org.apache.maven.plugins:maven-deploy-plugin:deploy
+
+
+
+
+
+ org.codehaus.plexus.archiver.Archiver
+ amp
+ org.alfresco.plexus.archiver.AmpArchiver
+ per-lookup
+
+
+
+
+ org.codehaus.plexus.archiver.UnArchiver
+ amp
+ org.alfresco.plexus.archiver.AmpUnArchiver
+
+
+
+
\ No newline at end of file
diff --git a/plugins/maven-amp-plugin/src/site/apt/components.apt.vm b/plugins/maven-amp-plugin/src/site/apt/components.apt.vm
new file mode 100644
index 00000000..0db45ded
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/site/apt/components.apt.vm
@@ -0,0 +1,37 @@
+ -----
+ Maven AMP Plugin Plexus Components
+ -----
+
+
+
+AMP Lifecycle Mapping
+
+ This plugin provides support for amp 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 true 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.
+
\ No newline at end of file
diff --git a/plugins/maven-amp-plugin/src/site/apt/index.apt.vm b/plugins/maven-amp-plugin/src/site/apt/index.apt.vm
new file mode 100644
index 00000000..aa0f430f
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/site/apt/index.apt.vm
@@ -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:
+
+
+------------------------------------------
+
+
+ ${site_pom_groupId}
+ ${site_pom_artifactId}
+ ${site_pom_version}
+
+ true
+
+
+-------------------------------------------
+
+
+ using repository (either in POM or settings.xml) :
+
+
+------------------------------------------
+
+
+ ss-public
+ http://repository.sourcesense.com/maven2
+
+
+-------------------------------------------
+
+
+Quick usage
+
+ Create a project using the maven-alfresco-amp-archetype (see {{ http://repository.sourcesense.com/maven2-sites/maven-alfresco-amp-archetype }} ).
+
\ No newline at end of file
diff --git a/plugins/maven-amp-plugin/src/site/site.xml b/plugins/maven-amp-plugin/src/site/site.xml
new file mode 100644
index 00000000..2ae769fd
--- /dev/null
+++ b/plugins/maven-amp-plugin/src/site/site.xml
@@ -0,0 +1,38 @@
+
+
+ org.apache.maven.skins
+ maven-stylus-skin
+ 1.0
+
+
+
+
+
+
+
+ Maven AMP Plugin - v. ${project.version}
+ ${site_site_url}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+