使用容器描述符处理程序

介绍

容器描述符处理程序可用于动态过滤描述符中配置的文件的内容,例如通过将多个文件聚合到单个文件中,或自定义特定文件的内容。

此示例演示了在程序集描述符格式中<containerDescriptorHandlers>的使用。

内置容器描述符处理程序

该插件附带了几个已经定义的处理程序。

文件聚合器
此处理程序根据给定的正则表达式filePattern匹配文件,聚合它们的内容,并将输出存储在给定outputPath的程序集中。与程序集中配置的所有file.txt文件匹配并通过将其内容附加到位于程序集基本目录下的 单个file.txt将它们聚合的示例描述符是:
<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>
元信息服务
此处理程序匹配每个META-INF/services文件并将它们聚合到单个META-INF/services中。文件的内容附加在一起。
<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>
元信息
此处理程序类似于metaInf-services。它匹配每个名称以META-INF/spring 开头的文件。.
<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>
此处理程序匹配每个META-INF/plexus/components.xml文件并将它们聚合到一个有效的 META-INF/plexus/components.xml中。
<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>

自定义容器描述符处理程序

您可以通过创建实现ContainerDescriptorHandler的类来创建自己的容器描述符处理程序。例如,让我们创建一个处理程序,它将配置的注释添加到程序集描述符中配置的每个属性文件中。

我们首先使用以下 POM创建一个名为custom-container-descriptor-handler的新 Maven 项目:

<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>

这个 POM 声明了对 Assembly Plugin 的依赖,以便我们可以创建我们的处理程序,并生成一个 Plexus 配置文件,以便在组装过程中通过依赖注入找到它。

实现ContainerDescriptorHandler需要定义几个方法:

被选中
告知是否应将在程序集描述符中配置的给定文件或目录添加到最终程序集中。一个典型的设置是阻止添加某些文件,并让处理程序对其进行处理。
获取虚拟文件
从程序集的根目录返回此处理程序将添加的每个文件的文件路径列表。
完成存档创建
在要创建程序集时调用的回调。此方法可用于添加处理程序对每个选定文件的工作产生的文件。警告:由于归档完成的执行方式,我们需要在做任何事情之前遍历归档中的每个资源。
finalizeArchiveExtraction
将程序集提取到目录中时调用的回调。此方法可用于处理处理程序对每个选定文件的工作所产生的文件。

我们为每个属性文件添加注释的处理程序可能如下所示(此处使用 Java 8 特性):

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;
    }

}

它是一个 Plexus 组件,具有ContainerDescriptorHandler角色,通过它的custom提示与其他处理程序区分开来。

它选择每个属性文件并将其内容存储到目录映射中,其中键是文件的名称,值是其行的列表。那些匹配的文件不会添加到程序集中,因为处理程序需要先处理它们。在程序集创建期间,它会创建临时文件,其内容是先前读取的行,前面有自定义注释。然后,它们会以其以前的名称重新添加到存档中。请注意,这个简单的处理程序不会聚合具有相同名称的文件 - 可以对其进行增强。将临时文件添加到存档时,会自动调用isSelected方法,因此我们需要将布尔excludeOverride设置为true以确保目录处理部分未完成。

最后一个要素是在某个 Maven 项目的程序集描述符中使用我们的自定义处理程序。假设该项目中有一个src/samples目录,其中包含一个名为test.xml的 XML 文件和一个名为test.properties的属性文件。具有以下描述符格式

<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>

以及以下 Assembly 插件配置

<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>

生成的程序集将包含基本目录下的test.xmltest.properties,只有后者以#A注释开头。