001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015 ForgeRock AS.
015 */
016package org.forgerock.opendj.maven;
017import static java.lang.String.*;
018
019import java.io.File;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023import java.util.Set;
024
025import org.apache.maven.artifact.Artifact;
026import org.apache.maven.artifact.DependencyResolutionRequiredException;
027import org.apache.maven.plugin.AbstractMojo;
028import org.apache.maven.plugin.MojoExecutionException;
029import org.apache.maven.plugin.MojoFailureException;
030import org.apache.maven.plugins.annotations.LifecyclePhase;
031import org.apache.maven.plugins.annotations.Mojo;
032import org.apache.maven.plugins.annotations.Parameter;
033import org.apache.maven.plugins.annotations.ResolutionScope;
034import org.apache.maven.project.MavenProject;
035
036/**
037 * Generate a class path suitable for the Class-Path header of a Manifest file,
038 * allowing to filter on included jars, using excludes/includes properties.
039 * <p>
040 * There is a single goal that generates a property given by 'classPathProperty'
041 * parameter, with the generated classpath as the value.
042 */
043@Mojo(name = "generate-manifest", defaultPhase = LifecyclePhase.VALIDATE,
044    requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
045public final class GenerateManifestClassPathMojo extends AbstractMojo {
046
047    private static final int MAX_LINE_LENGTH = 72;
048    private static final String HEADER_CLASSPATH = "Class-Path:";
049
050    /**
051     * The Maven Project.
052     */
053    @Parameter(property = "project", required = true, readonly = true)
054    private MavenProject project;
055
056    /**
057     * A property to set to the content of the generated classpath string.
058     */
059    @Parameter(required = true)
060    private String classPathProperty;
061
062    /**
063     * List of artifacts to exclude from the classpath. Each item must be of format "groupId:artifactId".
064     */
065    @Parameter
066    private List<String> excludes;
067
068    /**
069     * List of artifacts to include in the classpath. Each item must be of format "groupId:artifactId".
070     */
071    @Parameter
072    private List<String> includes;
073
074    /**
075     * List of additional JARs to include in the classpath. Each item must be of format "file.jar".
076     */
077    @Parameter
078    private List<String> additionalJars;
079
080    /**
081     * Name of product jar, e.g. "OpenDJ".
082     */
083    @Parameter
084    private String productJarName;
085
086    /**
087     * List of supported locales, separated by a ",".
088     * <p>
089     * Example: "fr,es,de"
090     */
091    @Parameter
092    private String supportedLocales;
093
094    /** {@inheritDoc} */
095    @Override
096    public void execute() throws MojoExecutionException, MojoFailureException {
097        try {
098            String classPath = getClasspath();
099            getLog().info(
100                    format("Setting the classpath property: [%s] (debug to see actual value)", classPathProperty));
101            getLog().debug(String.format("Setting the classpath property %s to:\n%s", classPathProperty, classPath));
102            project.getProperties().put(classPathProperty, classPath);
103        } catch (DependencyResolutionRequiredException e) {
104            getLog().error(
105                    String.format("Unable to set the classpath property %s, an error occured", classPathProperty));
106            throw new MojoFailureException(e.getMessage(), e);
107        }
108    }
109
110    /**
111     * Get the classpath.
112     * <p>
113     * The returned value is conform to Manifest Header syntax, where line length must be at most 72 bytes.
114     *
115     * @return the classpath string
116     * @throws DependencyResolutionRequiredException
117     */
118    private String getClasspath() throws DependencyResolutionRequiredException {
119        final List<String> classpathItems = getClasspathItems();
120        final StringBuilder classpath = new StringBuilder(HEADER_CLASSPATH);
121        for (String item : classpathItems) {
122            classpath.append(" ").append(item);
123        }
124        int index = MAX_LINE_LENGTH - 2;
125        while (index <= classpath.length()) {
126            classpath.insert(index, "\n ");
127            index += MAX_LINE_LENGTH - 1;
128        }
129        return classpath.toString();
130    }
131
132    private List<String> getClasspathItems() throws DependencyResolutionRequiredException {
133        final List<String> classpathItems = new ArrayList<>();
134
135        // add project dependencies
136        for (String artifactFile : project.getRuntimeClasspathElements()) {
137            final File file = new File(artifactFile);
138            if (file.getAbsoluteFile().isFile()) {
139                final Artifact artifact = findArtifactWithFile(project.getArtifacts(), file);
140                if (isAccepted(artifact)) {
141                    final String artifactString = artifact.getArtifactId() + "." + artifact.getType();
142                    classpathItems.add(artifactString);
143                }
144            }
145        }
146        // add product jars, with localized versions
147        Collections.sort(classpathItems);
148        if (productJarName != null) {
149            if (supportedLocales != null) {
150                String[] locales = supportedLocales.split(",");
151                for (int i = locales.length - 1; i >= 0; i--) {
152                    classpathItems.add(0, productJarName + "_" + locales[i] + ".jar");
153                }
154            }
155            classpathItems.add(0, productJarName + ".jar");
156        }
157        // add additional JARs
158        if (additionalJars != null) {
159            classpathItems.addAll(additionalJars);
160        }
161
162        return classpathItems;
163    }
164
165    private boolean isAccepted(Artifact artifact) {
166        String artifactString = artifact.getGroupId() + ":" + artifact.getArtifactId();
167        if (includes != null) {
168            if (containsIgnoreCase(includes, artifactString)) {
169                return true;
170            }
171            if (!includes.isEmpty()) {
172                return false;
173            }
174        }
175        return !containsIgnoreCase(excludes, artifactString);
176    }
177
178    private boolean containsIgnoreCase(List<String> strings, String toFind) {
179        if (strings == null) {
180            return false;
181        }
182        for (String s : strings) {
183            if (toFind.equalsIgnoreCase(s)) {
184                return true;
185            }
186        }
187        return false;
188    }
189
190    private Artifact findArtifactWithFile(Set<Artifact> artifacts, File file) {
191        for (Artifact artifact : artifacts) {
192            if (artifact.getFile() != null
193                    && artifact.getFile().equals(file)) {
194                return artifact;
195            }
196        }
197        return null;
198    }
199}