Java tutorial
/** * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jasig.maven.notice; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.MessageFormat; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.LineIterator; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.metadata.ArtifactMetadataSource; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactCollector; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectBuilder; import org.apache.maven.shared.dependency.tree.DependencyNode; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException; import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor; import org.jasig.maven.notice.lookup.ArtifactLicense; import org.jasig.maven.notice.lookup.LicenseLookup; import org.jasig.maven.notice.lookup.MappedVersion; import org.jasig.maven.notice.util.ResourceFinder; import org.jasig.maven.notice.util.ResourceFinderImpl; /** * Common base mojo for notice related plugins * * @author Eric Dalquist */ public abstract class AbstractNoticeMojo extends AbstractMojo { /* DI configuration of Maven components needed for the plugin */ /** * The Maven Project. * * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project; /** * The dependency tree builder to use. * * @component * @required * @readonly */ protected DependencyTreeBuilder dependencyTreeBuilder; /** * The artifact repository to use. * * @parameter expression="${localRepository}" * @required * @readonly */ protected ArtifactRepository localRepository; /** * The artifact factory to use. * * @component * @required * @readonly */ protected ArtifactFactory artifactFactory; /** * The artifact metadata source to use. * * @component * @required * @readonly */ protected ArtifactMetadataSource artifactMetadataSource; /** * The artifact collector to use. * * @component * @required * @readonly */ protected ArtifactCollector artifactCollector; /** * Maven Project Builder component. * * @component * @required * @readonly */ protected MavenProjectBuilder mavenProjectBuilder; /* Mojo Configuration Properties */ /** * Use licenseMapping * * @parameter * @deprecated use licenseMapping */ @Deprecated protected String[] licenseLookup = new String[0]; /** * Parameter to skip running checks entirely. * * @parameter expression="${skip.checks}" */ private boolean skipChecks = false; /** * License Mapping XML files / URLs. Lookups are done in-order with * files being checked top to bottom for matches * * @parameter */ protected String[] licenseMapping = new String[0]; /** * Template for NOTICE file generation * * @parameter default-value="NOTICE.template" */ protected String noticeTemplate = "NOTICE.template"; /** * Placeholder string in the NOTICE template file * * @parameter default-value="#GENERATED_NOTICES#" */ protected String noticeTemplatePlaceholder = "#GENERATED_NOTICES#"; /** * Output location for the generated NOTICE file * * @parameter default-value="${basedir}" */ protected String outputDir = ""; /** * Output file name * * @parameter default-value="NOTICE" */ protected String fileName = "NOTICE"; /** * @parameter default-value="${project.build.sourceEncoding}" */ protected String encoding = "UTF-8"; /** * Set if the NOTICE file should include all dependencies from all child modules. * * @parameter default-value="true" */ protected boolean includeChildDependencies = true; /** * Set if a NOTICE file should be generated for each child module * * @parameter default-value="true" */ protected boolean generateChildNotices = true; /** * The {@link MessageFormat} syntax string used to generate each license line in the NOTICE file<br/> * {0} - artifact name<br/> * {1} - license name<br/> * * @parameter default-value=" {0} under {1}" */ protected String noticeMessage = " {0} under {1}"; private MessageFormat parsedNoticeMessage; /** * ArtifactIds of child modules to exclude * * @parameter */ protected Set<String> excludedModules = new LinkedHashSet<String>(); /* (non-Javadoc) * @see org.apache.maven.plugin.Mojo#execute() */ public final void execute() throws MojoExecutionException, MojoFailureException { final Log logger = this.getLog(); if (this.skipChecks) { logger.info("NOTICE file checks are skipped."); return; } if (licenseLookup != null && licenseLookup.length > 0) { logger.warn("'licenseLookup' configuration property is deprecated use 'licenseMapping' instead"); if (licenseMapping != null && licenseMapping.length > 0) { throw new MojoFailureException( "Both 'licenseMapping' and 'licenseLookup' configuration properties configured. Only one may be used."); } licenseMapping = licenseLookup; } //Check if NOTICE for child modules should be generated if (!this.generateChildNotices && !this.project.isExecutionRoot()) { return; } final ResourceFinder finder = this.getResourceFinder(); final LicenseLookupHelper licenseLookupHelper = new LicenseLookupHelper(logger, finder, licenseMapping); final List<?> remoteArtifactRepositories = project.getRemoteArtifactRepositories(); final LicenseResolvingNodeVisitor visitor = new LicenseResolvingNodeVisitor(logger, licenseLookupHelper, remoteArtifactRepositories, this.mavenProjectBuilder, this.localRepository); this.parseProject(this.project, visitor); //Check for any unresolved artifacts final Set<Artifact> unresolvedArtifacts = visitor.getUnresolvedArtifacts(); this.checkUnresolved(unresolvedArtifacts); //Convert the resovled notice data into a String final Map<String, String> resolvedLicenses = visitor.getResolvedLicenses(); final String noticeLines = this.generateNoticeLines(resolvedLicenses); final String noticeTemplateContents = this.readNoticeTemplate(finder); //Replace the template placeholder with the generated notice data final String noticeContents = noticeTemplateContents .replaceAll(Pattern.quote(this.noticeTemplatePlaceholder), noticeLines); //Let the subclass deal with the generated NOTICE file this.handleNotice(finder, noticeContents); } /** * Called with the expected NOTICE file contents for this project. */ protected abstract void handleNotice(ResourceFinder finder, String noticeContents) throws MojoFailureException; /** * Loads the dependency tree for the project via {@link #loadDependencyTree(MavenProject)} and then uses * the {@link DependencyNodeVisitor} to load the license data. If {@link #aggregating} is enabled the method * recurses on each child module. */ @SuppressWarnings("unchecked") protected void parseProject(MavenProject project, DependencyNodeVisitor visitor) throws MojoExecutionException, MojoFailureException { final Log logger = this.getLog(); logger.info("Parsing Dependencies for: " + project.getName()); //Load and parse immediate dependencies final DependencyNode tree = this.loadDependencyTree(project); tree.accept(visitor); //If not including child deps don't recurse on modules if (!this.includeChildDependencies) { return; } //No child modules, return final List<MavenProject> collectedProjects = project.getCollectedProjects(); if (collectedProjects == null) { return; } //Find all sub-modules for the project for (final MavenProject moduleProject : collectedProjects) { if (this.isExcluded(moduleProject, project.getArtifactId())) { continue; } this.parseProject(moduleProject, visitor); } } /** * Check if a project is excluded based on its artifactId or a parent's artifactId */ protected boolean isExcluded(MavenProject mavenProject, String rootArtifactId) { final Log logger = this.getLog(); final String artifactId = mavenProject.getArtifactId(); if (this.excludedModules.contains(artifactId)) { logger.info("Skipping aggregation of child module " + mavenProject.getName() + " with excluded artifactId: " + artifactId); return true; } MavenProject parentProject = mavenProject.getParent(); while (parentProject != null && !rootArtifactId.equals(parentProject.getArtifactId())) { final String parentArtifactId = parentProject.getArtifactId(); if (this.excludedModules.contains(parentArtifactId)) { logger.info("Skipping aggregation of child module " + mavenProject.getName() + " with excluded parent artifactId: " + parentArtifactId); return true; } parentProject = parentProject.getParent(); } return false; } /** * Check if there are any unresolved artifacts in the Set. If there are print a helpful error * message and then throw a {@link MojoFailureException} */ protected void checkUnresolved(Set<Artifact> unresolvedArtifacts) throws MojoFailureException { final Log logger = this.getLog(); if (unresolvedArtifacts.isEmpty()) { return; } final LicenseLookup licenseLookup = new LicenseLookup(); final List<ArtifactLicense> artifacts = licenseLookup.getArtifact(); logger.error("Failed to find Licenses for the following dependencies: "); for (final Artifact unresolvedArtifact : unresolvedArtifacts) { logger.error("\t" + unresolvedArtifact); //Build LicenseLookup data model for artifacts that failed resolution final ArtifactLicense artifactLicense = new ArtifactLicense(); artifactLicense.setGroupId(unresolvedArtifact.getGroupId()); artifactLicense.setArtifactId(unresolvedArtifact.getArtifactId()); final List<MappedVersion> mappedVersions = artifactLicense.getVersion(); final MappedVersion mappedVersion = new MappedVersion(); mappedVersion.setValue(unresolvedArtifact.getVersion()); mappedVersions.add(mappedVersion); artifacts.add(artifactLicense); } logger.error("Try adding them to a 'licenseMapping' file."); final File buildDir = new File(project.getBuild().getDirectory()); final File mappingsfile = new File(buildDir, "license-mappings.xml"); //Make sure the target directory exists try { FileUtils.forceMkdir(buildDir); } catch (IOException e) { logger.warn("Failed to write stub license-mappings.xml file to: " + mappingsfile, e); } //Write the example mappings file final Marshaller marshaller = LicenseLookupContext.getMarshaller(); try { marshaller.marshal(licenseLookup, mappingsfile); logger.error( "A stub license-mapping.xml file containing the unresolved dependencies has been written to: " + mappingsfile); } catch (JAXBException e) { logger.warn("Failed to write stub license-mappings.xml file to: " + mappingsfile, e); } throw new MojoFailureException("Failed to find Licenses for " + unresolvedArtifacts.size() + " artifacts"); } /** * Create the generated part of the NOTICE file based on the resolved license data */ protected String generateNoticeLines(Map<String, String> resolvedLicenses) { final StringBuilder builder = new StringBuilder(); final MessageFormat messageFormat = getNoticeMessageFormat(); for (final Map.Entry<String, String> resolvedEntry : resolvedLicenses.entrySet()) { final String line = messageFormat .format(new Object[] { resolvedEntry.getKey(), resolvedEntry.getValue() }); builder.append(line).append(IOUtils.LINE_SEPARATOR); } return builder.toString(); } /** * Get the {@link MessageFormat} of the configured {@link #noticeMessage} */ protected final MessageFormat getNoticeMessageFormat() { final MessageFormat messageFormat; synchronized (this) { if (this.parsedNoticeMessage == null || !this.noticeMessage.equals(this.parsedNoticeMessage.toPattern())) { this.parsedNoticeMessage = new MessageFormat(this.noticeMessage); } messageFormat = this.parsedNoticeMessage; } return messageFormat; } /** * Read the template notice file into a string, converting the line ending to the current OS line endings */ protected String readNoticeTemplate(ResourceFinder finder) throws MojoFailureException { final URL inputFile = finder.findResource(this.noticeTemplate); final StringBuilder noticeTemplateContents = new StringBuilder(); InputStream inputStream = null; try { inputStream = inputFile.openStream(); for (final LineIterator lineIterator = IOUtils.lineIterator(new BufferedInputStream(inputStream), this.encoding); lineIterator.hasNext();) { final String line = lineIterator.next(); noticeTemplateContents.append(line).append(IOUtils.LINE_SEPARATOR); } } catch (IOException e) { throw new MojoFailureException( "Failed to open NOTICE Template File '" + this.noticeTemplate + "' from: " + inputFile, e); } finally { IOUtils.closeQuietly(inputStream); } return noticeTemplateContents.toString(); } /** * Resolve the {@link File} to write the generated NOTICE file to */ protected File getNoticeOutputFile() { if (this.outputDir == null) { this.outputDir = ""; } File outputPath = new File(this.outputDir); if (!outputPath.isAbsolute()) { outputPath = new File(project.getBasedir(), this.outputDir); } return new File(outputPath, this.fileName); } /** * Create the {@link ResourceFinderImpl} for the project */ @SuppressWarnings("unchecked") protected ResourceFinder getResourceFinder() throws MojoExecutionException { final ResourceFinder finder = new ResourceFinderImpl(this.project); try { final List<String> classpathElements = this.project.getCompileClasspathElements(); finder.setCompileClassPath(classpathElements); } catch (DependencyResolutionRequiredException e) { throw new MojoExecutionException(e.getMessage(), e); } finder.setPluginClassPath(getClass().getClassLoader()); return finder; } /** * Load the dependency tree for the specified project */ protected DependencyNode loadDependencyTree(MavenProject project) throws MojoExecutionException { try { return this.dependencyTreeBuilder.buildDependencyTree(project, this.localRepository, this.artifactFactory, this.artifactMetadataSource, null, this.artifactCollector); } catch (DependencyTreeBuilderException e) { throw new MojoExecutionException("Cannot build project dependency tree for project: " + project, e); } } }