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}