Container descriptor handlers can be used to filter dynamically the content of files configured in a descriptor, for example by aggregating multiple files into a single file, or customizing the content of specific files.
This example demonstrate the use of <containerDescriptorHandlers> in the assembly descriptor format.
The plugin comes with several handlers already defined.
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> .... <containerDescriptorHandlers> <containerDescriptorHandler> <handlerName>file-aggregator</handlerName> <configuration> <filePattern>.*/file.txt</filePattern> <outputPath>file.txt</outputPath> </configuration> </containerDescriptorHandler> </containerDescriptorHandlers> </assembly>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> .... <containerDescriptorHandlers> <containerDescriptorHandler> <handlerName>metaInf-services</handlerName> </containerDescriptorHandler> </containerDescriptorHandlers> </assembly>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> .... <containerDescriptorHandlers> <containerDescriptorHandler> <handlerName>metaInf-spring</handlerName> </containerDescriptorHandler> </containerDescriptorHandlers> </assembly>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> .... <containerDescriptorHandlers> <containerDescriptorHandler> <handlerName>plexus</handlerName> </containerDescriptorHandler> </containerDescriptorHandlers> </assembly>
You can create your own container descriptor handler by creating a class implementing ContainerDescriptorHandler. As an example, let's create a handler that will prepend a configured comment to every properties file configured in an assembly descriptor.
We start by creating a new Maven project named custom-container-descriptor-handler with the following POM:
<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.test</groupId> <artifactId>custom-container-descriptor-handler</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-component-metadata</artifactId> <version>1.7.1</version> <executions> <execution> <goals> <goal>generate-metadata</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
This POM declares a dependency on the Assembly Plugin so that we can create our handler, and generates a Plexus configuration file so that it can be found through dependency injection during assembling.
Implementing ContainerDescriptorHandler requires defining a couple of methods:
Our handler that prepends a comment to each properties file could look like the following (using here Java 8 features):
package com.test; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.maven.plugins.assembly.filter.ContainerDescriptorHandler; import org.apache.maven.plugins.assembly.utils.AssemblyFileUtils; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.ArchiverException; import org.codehaus.plexus.archiver.UnArchiver; import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.components.io.fileselectors.FileInfo; @Component(role = ContainerDescriptorHandler.class, hint = "custom") public class MyCustomDescriptorHandler implements ContainerDescriptorHandler { private String comment; private Map<String, List<String>> catalog = new HashMap<>(); private boolean excludeOverride = false; @Override public void finalizeArchiveCreation(Archiver archiver) throws ArchiverException { archiver.getResources().forEachRemaining(a -> {}); // necessary to prompt the isSelected() call for (Map.Entry<String, List<String>> entry : catalog.entrySet()) { String name = entry.getKey(); String fname = new File(name).getName(); Path p; try { p = Files.createTempFile("assembly-" + fname, ".tmp"); } catch (IOException e) { throw new ArchiverException("Cannot create temporary file to finalize archive creation", e); } try (BufferedWriter writer = Files.newBufferedWriter(p, StandardCharsets.ISO_8859_1)) { writer.write("# " + comment); for (String line : entry.getValue()) { writer.newLine(); writer.write(line); } } catch (IOException e) { throw new ArchiverException("Error adding content of " + fname + " to finalize archive creation", e); } File file = p.toFile(); file.deleteOnExit(); excludeOverride = true; archiver.addFile(file, name); excludeOverride = false; } } @Override public void finalizeArchiveExtraction(UnArchiver unarchiver) throws ArchiverException { } @Override public List<String> getVirtualFiles() { return new ArrayList<>(catalog.keySet()); } @Override public boolean isSelected(FileInfo fileInfo) throws IOException { if (excludeOverride) { return true; } String name = AssemblyFileUtils.normalizeFileInfo(fileInfo); if (fileInfo.isFile() && AssemblyFileUtils.isPropertyFile(name)) { catalog.put(name, readLines(fileInfo)); return false; } return true; } private List<String> readLines(FileInfo fileInfo) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(fileInfo.getContents(), StandardCharsets.ISO_8859_1))) { return reader.lines().collect(Collectors.toList()); } } public void setComment(String comment) { this.comment = comment; } }
It is a Plexus component, having the ContainerDescriptorHandler role, that is distinguished from the other handlers with its hint of custom.
It selects each properties file and stores their content into a catalog map, where the key is the name of the file and the value is a list of its lines. Those matched files are not added to the assembly, because the handler needs to process them first. During assembly creation, it creates temporary files whose content are the previously read lines, prepended by a custom comment. They are then added back into the archive with their previous name. Note that this simple handler does not aggregate files with the same name - it could be enhanced to do it. When the temporary files are added to the archive, the isSelected method is automatically called, hence we need to set a boolean excludeOverride to true to make sure the catalog processing part is not done.
The last ingredient is using our custom handler in an assembly descriptor of some Maven project. Suppose there is a src/samples directory in this project, containing an XML file named test.xml and a properties file named test.properties. With the following descriptor format
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> <id>dist</id> <formats> <format>zip</format> </formats> <containerDescriptorHandlers> <containerDescriptorHandler> <handlerName>custom</handlerName> <configuration> <comment>A comment</comment> </configuration> </containerDescriptorHandler> </containerDescriptorHandlers> <fileSets> <fileSet> <directory>src/samples</directory> <outputDirectory></outputDirectory> </fileSet> </fileSets> </assembly>
and the following Assembly plugin configuration
<project> [...] <build> [...] <plugins> [...] <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <descriptors> <descriptor>src/assemble/assembly.xml</descriptor> </descriptors> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>com.test</groupId> <artifactId>custom-container-descriptor-handler</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> </plugin> [...] </project>
the resulting assembly would contain both test.xml and test.properties under the base directory, with only the latter starting with # A comment.