Commit 813c9c7a authored by Brian Long's avatar Brian Long
Browse files

initial checkin

parents
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+9 −0
Original line number Diff line number Diff line
# Maven
target
pom.xml.versionsBackup

# Eclipse
.settings
.project
.classpath

pom.xml

0 → 100644
+117 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.inteligr8.alfresco</groupId>
	<artifactId>maven-amp-plugin</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>maven-plugin</packaging>

	<name>A Maven plugin to generate AMP files</name>
	<description>Generate Alfresco Module Packages (AMP) files in a way simialr to WAR files</description>

	<developers>
		<developer>
			<id>brian.long</id>
			<name>Brian Long</name>
			<email>brian@inteligr8.com</email>
		</developer>
	</developers>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.7</maven.compiler.source>
		<maven.compiler.target>1.7</maven.compiler.target>
		<maven.version>3.6.3</maven.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.4</version>
		</dependency>
		<dependency>
			<groupId>org.eclipse.aether</groupId>
			<artifactId>aether-util</artifactId>
			<version>1.1.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-plugin-api</artifactId>
			<version>${maven.version}</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.maven.plugin-tools</groupId>
			<artifactId>maven-plugin-annotations</artifactId>
			<version>3.6.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-core</artifactId>
			<version>${maven.version}</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.maven.plugin-testing</groupId>
			<artifactId>maven-plugin-testing-harness</artifactId>
			<version>3.3.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-compat</artifactId>
			<version>${maven.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-plugin-plugin</artifactId>
				<version>3.6.0</version>
				<configuration>
					<goalPrefix>amp</goalPrefix>
				</configuration>
				<executions>
					<execution>
						<id>mojo-descriptor</id>
						<goals>
							<goal>descriptor</goal>
						</goals>
					</execution>
					<execution>
						<id>help-goal</id>
						<goals>
							<goal>helpmojo</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

	<distributionManagement>
		<repository>
			<id>inteligr8-release</id>
			<name>Inteligr8 Releases</name>
			<url>http://repos.yateslong.us/nexus/repository/inteligr8-public</url>
		</repository>
	</distributionManagement>

</project>
+323 −0
Original line number Diff line number Diff line
package com.inteligr8.alfresco.amp;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.FileSet;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.DefaultDependencyResolutionRequest;
import org.apache.maven.project.DependencyResolutionException;
import org.apache.maven.project.DependencyResolutionRequest;
import org.apache.maven.project.DependencyResolutionResult;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectDependenciesResolver;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.util.filter.DependencyFilterUtils;
import org.eclipse.aether.util.filter.ExclusionsDependencyFilter;
import org.eclipse.aether.util.filter.ScopeDependencyFilter;

@Mojo( name = "amp", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true,
		requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class AmpMojo extends AbstractMojo {
	
	private final Pattern modulePropertiesPattern = Pattern.compile("^alfresco/module/[^/]+/module\\.properties$");
	private final int streamBufferSize = 16384;
	
	@Parameter( defaultValue = "${project}", readonly = true )
	protected MavenProject project;
	
	@Parameter( defaultValue = "${session}", readonly = true )
	protected MavenSession session;
	
	@Requirement
	private ProjectDependenciesResolver resolver;
	
	@Parameter( property = "outputFile", required = true, defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}.amp" )
	protected String outputAmpFile;

	@Parameter( property = "moduleFile", required = true, defaultValue = "${project.build.directory}/${project.build.finalName}.${project.artifact.type}" )
	protected String moduleJarFile;
	
	@Parameter( property = "modulePropertyFile", required = false )
	protected String modulePropFile;
	
	@Parameter( property = "resources", required = false )
	protected List<Resource> resources;
	
	@Parameter( property = "dependencyIncludeScopes", required = true, defaultValue = "compile,runtime" )
	protected String includeScopes;
	
	@Parameter( property = "dependencyExclusions", required = false )
	protected List<Exclusion> excludeDependencies;
	
	@Parameter( property = "lib", required = false )
	protected List<FileSet> libDirectories;
	
	@Parameter( property = "charset", required = true, defaultValue = "utf-8" )
	protected String charsetName;
	
	@Parameter( property = "skip", required = true, defaultValue = "false" )
	protected boolean skip;

    public void execute() throws MojoExecutionException {
    	if (this.skip) {
        	this.getLog().debug("Skipped AMP packaging");
    		return;
    	}
    	this.getLog().debug("Executing AMP packaging");
    	
    	this.normalize();
    	
    	File ampFile = this.getOutputFile();
    	this.getLog().debug("Writing AMP file: " + ampFile.getAbsolutePath());
    	
    	try {
	    	FileOutputStream fostream = new FileOutputStream(ampFile, false);
			BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.streamBufferSize);
			ZipOutputStream zstream = new ZipOutputStream(bostream, Charset.forName(this.charsetName));
			try {
				this.zip(zstream);
		    	
				zstream.finish();
			} finally {
				zstream.close();
			}
    	} catch (DependencyResolutionException dre) {
    		throw new MojoExecutionException("The dependencies could not be properly resolved", dre);
    	} catch (IOException ie) {
    		throw new MojoExecutionException("An I/O issue occurred", ie);
    	}
    	
    	this.getLog().debug("Wrote AMP file: " + ampFile.length());
    }
    
    private void normalize() {
    	this.outputAmpFile = StringUtils.trimToNull(this.outputAmpFile);
    	this.moduleJarFile = StringUtils.trimToNull(this.moduleJarFile);
    	this.modulePropFile = StringUtils.trimToNull(this.modulePropFile);
    	if (this.resources == null)
    		this.resources = Collections.emptyList();
    	this.includeScopes = this.includeScopes.trim();
    	if (this.libDirectories == null)
    		this.libDirectories = Collections.emptyList();
    	
    	this.getLog().debug("Normalized parameters");
    }
    
    private File getOutputFile() {
    	File ampFile = new File(this.outputAmpFile);
    	if (ampFile.exists())
    		ampFile.delete();
    	return ampFile;
    }
    
    private File getModuleFile() throws MojoExecutionException {
    	File jarFile = new File(this.moduleJarFile);
    	if (!jarFile.exists())
    		throw new MojoExecutionException("The module JAR file does not exist: " + this.moduleJarFile);
    	return jarFile;
    }
    
    private File getModulePropertyFile() throws MojoExecutionException {
    	File propFile = null;
    	if (this.modulePropFile != null) {
	    	propFile = new File(this.modulePropFile);
    	} else {
    		String outputDirectory = this.project.getBuild().getOutputDirectory();
    		File modulesDirectory = new File(outputDirectory, "alfresco/module");
    		if (!modulesDirectory.exists() || !modulesDirectory.isDirectory())
    			throw new MojoExecutionException("The module.properties file could not be found: 'alfresco/module' path does not exist");

    		File moduleDirectory = new File(modulesDirectory, this.project.getGroupId() + "." + this.project.getArtifactId());
    		if (!moduleDirectory.exists())
    			moduleDirectory = new File(modulesDirectory, this.project.getArtifactId());
    		if (!moduleDirectory.exists()) {
    			File[] files = modulesDirectory.listFiles();
    			if (files.length > 0) {
    				moduleDirectory = files[0];
	        		if (!moduleDirectory.exists())
	        			throw new MojoExecutionException("The module.properties file could not be found: 'alfresco/module' path does not exist");
    			}
    		}
    		
    		propFile = new File(moduleDirectory, "module.properties");
    	}
    	
    	if (!propFile.exists())
    		throw new MojoExecutionException("The module.properties file does not exist: " + propFile.getAbsolutePath());
    	return propFile;
    }
    
    private void zip(ZipOutputStream zstream) throws IOException, MojoExecutionException, DependencyResolutionException {
		for (Resource resource : this.resources)
			this.zipResource(zstream, resource);

    	File jarFile = this.getModuleFile();
		this.zipFile(zstream, jarFile, "lib");
		
		File propFile = this.getModulePropertyFile();
		this.zipFile(zstream, propFile, "");
		
		Collection<Dependency> deps = this.getDependencies();
		if (deps.isEmpty()) {
    		this.getLog().info("Zipped 0 dependencies");
		} else {
			for (Dependency dependency : deps) {
				File file = dependency.getArtifact().getFile();
				if (this.isAlfrescoModule(file)) {
					this.getLog().info("Not packaging JAR; detected as Alfresco JAR Module: " + dependency.getArtifact().getArtifactId());
				} else {
					this.zipFile(zstream, file, "lib");
				}
			}
	    	if (this.getLog().isInfoEnabled())
	    		this.getLog().info("Zipped " + deps.size() + " dependencies");
		}
    	
    	for (FileSet fileset : this.libDirectories)
    		this.zipFileset(zstream, fileset, "lib");
    }
    
    private List<String> getExclusions() {
    	List<String> exclusions = new ArrayList<>(this.excludeDependencies.size());
    	for (Exclusion exclusion : this.excludeDependencies)
    		exclusions.add(exclusion.getGroupId() + ":" + exclusion.getArtifactId());
    	return exclusions;
    }
    
    private Collection<Dependency> getDependencies() throws DependencyResolutionException {
    	String[] includeScopes = StringUtils.split(this.includeScopes, ",");
    	
		ScopeDependencyFilter scopeFilter = new ScopeDependencyFilter(Arrays.asList(includeScopes), new ArrayList<String>(0));
		ExclusionsDependencyFilter exclusionFilter = new ExclusionsDependencyFilter(this.getExclusions());

		DependencyResolutionRequest request = new DefaultDependencyResolutionRequest(this.project, this.session.getRepositorySession());
		request.setResolutionFilter(DependencyFilterUtils.andFilter(scopeFilter, exclusionFilter));

		DependencyResolutionResult result = this.resolver.resolve(request);
		return result.getResolvedDependencies();
    }
    
    private void zipResource(ZipOutputStream zstream, Resource resource) throws MojoExecutionException, IOException {
    	String baseZipPath = resource.getTargetPath();
    	if (baseZipPath == null)
    		baseZipPath = "";
    	else if (!baseZipPath.endsWith("/"))
    		baseZipPath += "/";
    	this.zipFileset(zstream, resource, baseZipPath);
    }

    private void zipFileset(ZipOutputStream zstream, FileSet fileset, String targetPath) throws MojoExecutionException, IOException {
    	targetPath = StringUtils.trimToEmpty(targetPath);
    	if (targetPath.length() > 0 && !targetPath.endsWith("/"))
    		targetPath += "/";

    	File directory = new File(this.project.getBasedir(), fileset.getDirectory());
    	if (!directory.exists())
    		return;
    	if (!directory.isDirectory())
    		throw new MojoExecutionException("The fileset 'directory' must be a directory: " + fileset.getDirectory());

    	String includes = StringUtils.join(fileset.getIncludes(), ",");
    	String excludes = StringUtils.join(fileset.getExcludes(), ",");
    	List<String> filenames = FileUtils.getFileNames(directory, includes, excludes, false, true);
    	
    	if (this.getLog().isDebugEnabled())
    		this.getLog().debug("Zipping files in: " + fileset.getDirectory());
    	for (String filename : filenames) {
        	if (this.getLog().isDebugEnabled())
        		this.getLog().debug("Zipping file: " + filename);
        	
    		zstream.putNextEntry(new ZipEntry(filename));
    		
    		File file = new File(directory, filename);
    		FileReader freader = new FileReader(file);
    		try {
    			IOUtil.copy(freader, zstream);
    		} finally {
    			freader.close();
    		}
    		
    		zstream.closeEntry();
    	}

    	if (this.getLog().isInfoEnabled())
    		this.getLog().info("Zipped " + filenames.size() + " files: " + fileset.getDirectory());
    }
    
    private void zipFile(ZipOutputStream zstream, File file, String targetPath) throws IOException {
    	targetPath = StringUtils.trimToEmpty(targetPath);
    	if (targetPath.length() > 0 && !targetPath.endsWith("/"))
    		targetPath += "/";
    	
    	if (this.getLog().isDebugEnabled())
    		this.getLog().debug("Zipping file: " + file.toString());
        
    	zstream.putNextEntry(new ZipEntry(targetPath + file.getName()));
    	
		FileReader freader = new FileReader(file);
		try {
			IOUtil.copy(freader, zstream);
		} finally {
			freader.close();
		}
    		
    	zstream.closeEntry();
    }
    
    private boolean isAlfrescoModule(File file) throws IOException {
    	FileInputStream fistream = new FileInputStream(file);
    	BufferedInputStream bistream = new BufferedInputStream(fistream, this.streamBufferSize);
    	ZipInputStream zstream = new ZipInputStream(bistream, Charset.forName(this.charsetName));
    	try {
    		ZipEntry zentry = zstream.getNextEntry();
    		while (zentry != null) {
    			this.getLog().debug("dep entry: " + zentry.getName());
    			try {
	    			Matcher modPropsMatcher = this.modulePropertiesPattern.matcher(zentry.getName());
	    			if (modPropsMatcher.find())
	    				return true;
    			} finally {
    				zstream.closeEntry();
    			}
    			
    			zentry = zstream.getNextEntry();
    		}
    	} finally {
    		zstream.close();
    	}
    	
    	return false;
    }
    
}