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: + *

+ * + * @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: + *

+ * + * @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. + *

+ *

+ * + * @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. + *

+ *

+ * + * @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} + + + + + + + + + + + + + + + + + + + + + + + +