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}