| ------ |
| Using Container Descriptor Handlers |
| ------ |
| Guillaume Boué |
| ------ |
| 2017-07-15 |
| ------ |
| |
| ~~ 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. |
| |
| ~~ NOTE: For help with the syntax of this file, see: |
| ~~ http://maven.apache.org/doxia/references/apt-format.html |
| |
| Using Container Descriptor Handlers |
| |
| * Introduction |
| |
| 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 {{{../../assembly.html}descriptor format}}. |
| |
| * Built-in container descriptor handlers |
| |
| The plugin comes with several handlers already defined. |
| |
| [<<<file-aggregator>>>] This handler matches the files according to the given regular |
| expression <<<filePattern>>>, aggregates their content, and stores the output |
| in the assembly at the given <<<outputPath>>>. A sample descriptor which matches |
| all <<<file.txt>>> files configured in the assembly and aggregates them, by |
| appending their content, into a single <<<file.txt>>> located under the base directory |
| of the assembly, is: |
| |
| +----- |
| <assembly xmlns="http://maven.apache.org/ASSEMBLY/${mdoVersion}" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/${mdoVersion} http://maven.apache.org/xsd/assembly-${mdoVersion}.xsd"> |
| .... |
| <containerDescriptorHandlers> |
| <containerDescriptorHandler> |
| <handlerName>file-aggregator</handlerName> |
| <configuration> |
| <filePattern>.*/file.txt</filePattern> |
| <outputPath>file.txt</outputPath> |
| </configuration> |
| </containerDescriptorHandler> |
| </containerDescriptorHandlers> |
| </assembly> |
| +----- |
| |
| [<<<metaInf-services>>>] This handler matches every <<<META-INF/services>>> file and |
| aggregates them into a single <<<META-INF/services>>>. The content of the files |
| are appended together. |
| |
| +----- |
| <assembly xmlns="http://maven.apache.org/ASSEMBLY/${mdoVersion}" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/${mdoVersion} http://maven.apache.org/xsd/assembly-${mdoVersion}.xsd"> |
| .... |
| <containerDescriptorHandlers> |
| <containerDescriptorHandler> |
| <handlerName>metaInf-services</handlerName> |
| </containerDescriptorHandler> |
| </containerDescriptorHandlers> |
| </assembly> |
| +----- |
| |
| [<<<metaInf-spring>>>] This handler is similar to <<<metaInf-services>>>. It matches |
| every file with a name starting with <<<META-INF/spring.>>>. |
| |
| +----- |
| <assembly xmlns="http://maven.apache.org/ASSEMBLY/${mdoVersion}" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/${mdoVersion} http://maven.apache.org/xsd/assembly-${mdoVersion}.xsd"> |
| .... |
| <containerDescriptorHandlers> |
| <containerDescriptorHandler> |
| <handlerName>metaInf-spring</handlerName> |
| </containerDescriptorHandler> |
| </containerDescriptorHandlers> |
| </assembly> |
| +----- |
| |
| [<<<plexus>>>] This handler matches every <<<META-INF/plexus/components.xml>>> file and |
| aggregates them into a single valid <<<META-INF/plexus/components.xml>>>. |
| |
| +----- |
| <assembly xmlns="http://maven.apache.org/ASSEMBLY/${mdoVersion}" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/${mdoVersion} http://maven.apache.org/xsd/assembly-${mdoVersion}.xsd"> |
| .... |
| <containerDescriptorHandlers> |
| <containerDescriptorHandler> |
| <handlerName>plexus</handlerName> |
| </containerDescriptorHandler> |
| </containerDescriptorHandlers> |
| </assembly> |
| +----- |
| |
| [] |
| |
| * Custom container descriptor handlers |
| |
| 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>${project.version}</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: |
| |
| [<<<isSelected>>>] Tells whether a given file or directory, configured in the |
| assembly descriptor, should be added to the final assembly. A typical |
| set-up would be to prevent the addition of certain files, and let the |
| handler do its work on them. |
| |
| [<<<getVirtualFiles>>>] Returns the list of file paths, from the root of the assembly, |
| of each file this handler will add. |
| |
| [<<<finalizeArchiveCreation>>>] Callback that is invoked when an assembly is going |
| to be created. This method can be used to add files that resulted from the |
| handler's work on each selected file. <<Cave-at:>> Because of the way archive |
| finalization is performed, we need to loop through each resource in the archive |
| before doing anything. |
| |
| [<<<finalizeArchiveExtraction>>>] Callback that is invoked when an assembly has been |
| extracted into a directory. This method can be used to process files that resulted |
| from the handler's work on each selected file. |
| |
| [] |
| |
| 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>${project.version}</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>>>. |