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