Addjars Plugin을 개발하기 위해서 가장 먼저 Maven Project를 빈것을 하나 생성하고 그 다음에는 다음과 같이 pom.xml  파일을 생성합니다.

pom.xml 파일
<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/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>io.datadynamics.maven.plugins</groupId>
    <artifactId>addjars-maven-plugin</artifactId>
    <version>1.0.0</version>
    <packaging>maven-plugin</packaging>
    <name>AddJars Maven Plugin</name>
    <description>Adds arbitrary jars to project's classpath</description>

    <prerequisites>
        <maven>3.0.3</maven>
    </prerequisites>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-project</artifactId>
            <version>2.2.1</version>
        </dependency>
    </dependencies>

    <licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
            <distribution>repo</distribution>
        </license>
    </licenses>

</project>

이 플러그인을 사용하기 위해서는 우선 다음과 같이 Maven POM에 Plugin을 정의하고 리소스 설정을 통해 JAR 파일이 있는 위치를 지정할 수 있도록 합니다.

플러그인 사용 방법
<plugin>
    <groupId>io.datadynamics.maven.plugins</groupId>
    <artifactId>addjars-maven-plugin</artifactId>
    <version>1.0.0</version>
    <executions>
        <execution>
            <goals>
                <goal>add-jars</goal>
            </goals>
            <configuration>
                <resources>
                    <resource>
                        <directory>${basedir}/lib</directory>
                        <includes>
                            <include>**/*.jar</include>
                        </includes>
                        <excludes>
                            <exclude>${basedir}/lib/runtime/**/*.jar</exclude>
                        </excludes>
                    </resource>
                    <resource>
                        <directory>${basedir}/lib/runtime</directory>
                        <scope>runtime</scope>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>

플러그인 설정에서 resource 설정 정보를 담고 있는 model 클래스인 JarResource.java 를 생성합니다.

JarResource.java
package io.datadynamics.maven.plugins.addjars;

import java.io.File;
import java.io.Serializable;
import java.util.List;

public class JarResource implements Serializable {

    private File directory;

    private String scope = "compile";

    private List<String> includes;

    private List<String> excludes;

    public File getDirectory() {
        return directory;
    }

    public void setDirectory(File directory) {
        this.directory = directory;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public List<String> getIncludes() {
        return includes;
    }

    public void setIncludes(List<String> includes) {
        this.includes = includes;
    }

    public List<String> getExcludes() {
        return excludes;
    }

    public void setExcludes(List<String> excludes) {
        this.excludes = excludes;
    }
}

이제 가장 중요한 Maven Plugin의 Mojo라 불리우는 플러그의 단위 기능을 작성할 차례입니다.

AddJarsMojo.java
package io.datadynamics.maven.plugins.addjars;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.installer.ArtifactInstaller;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.ProjectArtifactMetadata;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.WriterFactory;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.*;

/**
 * Adds arbitrary jars to project's classpath.
 *
 * @goal add-jars
 * @phase generate-sources
 */
public class AddJarsMojo extends AbstractMojo {

    /**
     * @parameter
     * @required
     * @readonly
     */
    private List<JarResource> resources;

    /**
     * @parameter property="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * @component
     */
    private ArtifactFactory artifactFactory;

    /**
     * @component
     */
    private ArtifactInstaller artifactInstaller;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            executeInt();
        } catch (MojoFailureException e) {
            throw e;
        } catch (MojoExecutionException e) {
            throw e;
        } catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    private void executeInt() throws Exception {
        // Maven 빌드 디렉토리에 임의의 경로를 생성한다.
        File workdir = new File(project.getBuild().getDirectory(), getClass().getName());
        workdir.mkdirs();

        // JAR 파일을 실제로 존재하지 않지만 실제로 존재하는 것처럼 꾸며서 생성한 Dependency 목록
        Set<Artifact> dependenciesArtifacts = new HashSet<Artifact>();

        // Plugin의 Resource 설정을 통해 모든 JAR 파일을 가져온다.
        for (JarResource resource : resources) {
            for (File jar : getJars(resource)) {
                // JAR 파일명과 Plugin을 사용한 Project Group ID를 바탕으로 JAR 파일의 Artifact를 생성한다.
                Artifact a = artifactFactory.createArtifact(project.getGroupId(), project.getArtifactId() + "-" + jar.getName(), project.getVersion(), resource.getScope(), "jar");

                // 각 Artifact의 POM을 생성한다.
                File stamp = new File(workdir, a.getArtifactId());
                if (jar.lastModified() > stamp.lastModified()) {
                    a.addMetadata(new ProjectArtifactMetadata(a, createArtifactPom(a)));
                    artifactInstaller.install(jar, a, null);
                    stamp.createNewFile();
                    stamp.setLastModified(jar.lastModified());
                }

                dependenciesArtifacts.add(a);
            }
        }

        // 프로젝트의 모든 Dependency를 가지고 와서 임의로 추가한 Artifact 목록에 함께 추가한다.
        Set newDependenciesArtifacts = new HashSet(project.getDependencyArtifacts());
        newDependenciesArtifacts.addAll(dependenciesArtifacts);

        project.setDependencyArtifacts(newDependenciesArtifacts);

        for (Artifact dependency : dependenciesArtifacts) {
            project.getOriginalModel().addDependency(createDependency(dependency));
        }

        // 새로운 POM을 생성한다.
        File pomFile = new File(workdir, "pom.xml");
        writePom(pomFile, project.getOriginalModel());
        project.setFile(pomFile);
    }

    /**
     * Plugin의 Resource 설정 정보를 기반으로 JAR 파일을 찾아서 반환한다.
     *
     * @param resource Maven POM에 정의되어 있는 Plugin의 Resource 설정
     * @return JAR 파일목록
     * @throws IOException
     */
    private List<File> getJars(JarResource resource) throws IOException {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(resource.getDirectory());
        if (resource.getIncludes() != null) {
            scanner.setIncludes(resource.getIncludes().toArray(new String[]{}));
        }
        if (resource.getExcludes() != null) {
            scanner.setExcludes(resource.getExcludes().toArray(new String[]{}));
        }

        try {
            scanner.scan();
        } catch (IllegalStateException e) {
            getLog().warn("디렉토리가 아닙니다 : " + resource.getDirectory());
            return Collections.emptyList();
        }

        List<File> files = new ArrayList<File>();
        for (String file : scanner.getIncludedFiles()) {
            File f = new File(resource.getDirectory(), file).getCanonicalFile();
            if (f.getName().endsWith(".jar")) {
                files.add(f);
            } else {
                getLog().warn("JAR 파일이 아닙니다 : " + f);
            }
        }

        Collections.sort(files);
        return files;
    }

    private Dependency createDependency(Artifact a) {
        Dependency d = new Dependency();
        d.setGroupId(a.getGroupId());
        d.setArtifactId(a.getArtifactId());
        d.setVersion(a.getVersion());
        d.setScope(a.getScope());
        d.setType(a.getType());
        return d;
    }

    private File createArtifactPom(Artifact a) throws IOException {
        File pomFile = File.createTempFile(a.getArtifactId(), ".pom");
        writePom(pomFile, createModel(a));
        return pomFile;
    }

    private Model createModel(Artifact a) {
        Model model = new Model();
        model.setModelVersion("4.0.0");
        model.setGroupId(a.getGroupId());
        model.setArtifactId(a.getArtifactId());
        model.setVersion(a.getVersion());
        model.setPackaging(a.getType());
        return model;
    }

    private void writePom(File pom, Model model) throws IOException {
        Writer writer = WriterFactory.newXmlWriter(pom);
        new MavenXpp3Writer().write(writer, model);
        writer.close();
    }
}

모든 작업이 완료되면 이제 Maven Plugin을 사용할 수 있도록 빌드합니다. 로컬에서 테스트할 때에는 mvn install 을 통해서 수행하고 Maven Repository 서버에 배포를 하는 경우는 mvn deploy 커맨드를 하도록 합니다.

# mvn -Dmaven.test.skip=true install