package org.org.maven.plugin.dws; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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. */ import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.manager.WagonManager; import org.apache.maven.artifact.metadata.ArtifactMetadataSource; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.repository.metadata.RepositoryMetadataManager; import org.apache.maven.artifact.resolver.ArtifactCollector; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.Restriction; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectBuilder; import org.apache.maven.settings.Settings; 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.filter.AncestorOrSelfDependencyNodeFilter; import org.apache.maven.shared.dependency.tree.filter.AndDependencyNodeFilter; import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter; import org.apache.maven.shared.dependency.tree.filter.StateDependencyNodeFilter; import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor; import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor; import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor; import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor; import org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor; import org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.TreeTokens; import org.codehaus.plexus.PlexusConstants; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.context.Context; import org.codehaus.plexus.context.ContextException; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable; import org.org.maven.plugin.dws.utils.OutputUtils; import org.org.maven.plugin.dws.utils.RepositoryUtils; /** * Retrieves the dependency tree for this project in a flat DWS description. * * Note that this code is clearly "inspired" by maven-dependency-plugin version 2.x ;), and also maven-project-report-plugin for the dependencies part. * * @author <a href="mailto:pierre.antoine.gregoire@gmail.com">Pierre-Antoine Grégoire</a> * @version $Id:$ * @since 1.0.0 * @goal descriptor * @requiresDependencyResolution test */ public class DWSDescriptorMojo extends AbstractMojo implements Contextualizable { // fields ----------------------------------------------------------------- public class CollectingDWSDependencyItemsVisitor implements DependencyNodeVisitor { private Set dwsDependencyItems = new TreeSet(new Comparator() { public int compare(Object obj1, Object obj2) { return obj1.toString().compareTo(obj2.toString()); } }); private List repositories = new ArrayList(); private final boolean compileScope; private final boolean runtimeScope; private final boolean testScope; private final boolean providedScope; private final boolean systemScope; private final RepositoryUtils repositoryUtils; public CollectingDWSDependencyItemsVisitor(List repositories, String filteredScope, RepositoryUtils repositoryUtils) { getLog().debug("Filtered by " + filteredScope); this.repositories = repositories; this.repositoryUtils = repositoryUtils; if (Artifact.SCOPE_COMPILE.equals(filteredScope)) { systemScope = false; providedScope = false; compileScope = false; runtimeScope = true; testScope = true; } else if (Artifact.SCOPE_RUNTIME.equals(filteredScope)) { systemScope = true; providedScope = true; compileScope = false; runtimeScope = false; testScope = true; } else if (Artifact.SCOPE_TEST.equals(filteredScope)) { systemScope = false; providedScope = false; compileScope = false; runtimeScope = false; testScope = false; } else { systemScope = true; providedScope = true; compileScope = true; runtimeScope = true; testScope = true; } } public boolean endVisit(DependencyNode dependencyNode) { return true; } public boolean visit(DependencyNode dependencyNode) { Artifact artifact = dependencyNode.getArtifact(); getLog().info(artifact.toString()); boolean filteredScope = isScopeFiltered(artifact.getScope()); if (!filteredScope) { getLog().debug(dependencyNode.getArtifact() + " is NOT filtered."); DWSDependencyItem dwsDependencyItem = new DWSDependencyItem(artifact); for (Iterator it2 = repositories.iterator(); it2.hasNext();) { ArtifactRepository artifactRepository = (ArtifactRepository) it2.next(); // String repositoryUrl = artifactRepository.getUrl(); // String possibleUrl = repositoryUrl + needsSeparator(repositoryUrl) + artifactRepository.pathOf(artifact); String possibleUrl = repositoryUtils.getDependencyUrlFromRepository(artifact, artifactRepository); if (possibleUrl != null) { boolean artifactExists = false; // check snapshots in snapshots repository only and releases in release repositories... if ((artifact.isSnapshot() && artifactRepository.getSnapshots().isEnabled()) || (!artifact.isSnapshot() && artifactRepository.getReleases().isEnabled())) { artifactExists = repositoryUtils.dependencyExistsInRepo(artifactRepository, artifact); } if (artifactExists) { dwsDependencyItem.addArtifactUrl(possibleUrl); } } } dwsDependencyItems.add(dwsDependencyItem); } else { getLog().debug(dependencyNode.getArtifact() + " IS filtered."); } return (!filteredScope); } private boolean isScopeFiltered(String artifactScope) { boolean filteredScope = false; if (artifactScope == null) { artifactScope = Artifact.SCOPE_COMPILE; } if (Artifact.SCOPE_COMPILE.equals(artifactScope)) { filteredScope = compileScope; } else if (Artifact.SCOPE_RUNTIME.equals(artifactScope)) { filteredScope = runtimeScope; } else if (Artifact.SCOPE_TEST.equals(artifactScope)) { filteredScope = testScope; } else if (Artifact.SCOPE_PROVIDED.equals(artifactScope)) { filteredScope = providedScope; } else if (Artifact.SCOPE_SYSTEM.equals(artifactScope)) { filteredScope = systemScope; } else { filteredScope = true; } return filteredScope; } public Set getResult() { return dwsDependencyItems; } } /** * The Maven project. * * @parameter expression="${project}" * @required * @readonly */ private MavenProject project; /** * List of Remote Repositories used by the resolver * * @parameter expression="${project.remoteArtifactRepositories}" * @readonly * @required */ private java.util.List remoteRepositories = Collections.emptyList(); /** * The artifact repository to use. * * @parameter expression="${localRepository}" * @required * @readonly */ private ArtifactRepository localRepository; // // /** // * The artifact factory to use. // * // * @component // * @required // * @readonly // */ // private ArtifactFactory artifactFactory; /** * The artifact metadata source to use. * * @component * @required * @readonly */ private ArtifactMetadataSource artifactMetadataSource; // // /** // * The artifact collector to use. // * // * @component // * @required // * @readonly // */ // private ArtifactCollector artifactCollector; /** * The dependency tree builder to use. * * @component * @required * @readonly */ private DependencyTreeBuilder dependencyTreeBuilder; /** * Wagon manager component. * * @component * @required * @readonly */ private WagonManager wagonManager; /** * The current user system settings for use in Maven. * * @parameter expression="${settings}" * @required * @readonly */ private Settings settings; /** * Maven Project Builder component. * * @component * @required * @readonly */ private MavenProjectBuilder mavenProjectBuilder; /** * Artifact Factory component. * * @component * @required * @readonly */ protected ArtifactFactory factory; /** * Repository metadata component. * * @component * @required * @readonly */ private RepositoryMetadataManager repositoryMetadataManager; /** * Artifact Resolver component. * * @component * @required * @readonly */ protected ArtifactResolver resolver; /** * Artifact collector component. * * @component * @required * @readonly */ private ArtifactCollector collector; // // /** // * Jar classes analyzer component. // * // * @component // * @required // * @readonly // */ // private JarClassesAnalysis classesAnalyzer; /** * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of writing to the console. * * @since 1.0.0 * @parameter expression="${outputFile}" default-value="${basedir}" * */ private String outputPath; // /** // * A regular expression allowing to choose which repositories should be linked and which shouldn't if multiple are available. Use with caution after a first launch, or if you want libraries from some limited repositories. Defaults to all available repositories. // * // * @since 1.0.0 // * // * @parameter expression="${repos}" default-value=".*" // */ // private String repos; /** * The scope to filter by when resolving the dependency tree, or <code>null</code> to include dependencies from all scopes. Note that this feature does not currently work due to MNG-3236. * * @since 1.0.0 * @see <a href="http://jira.codehaus.org/browse/MNG-3236">MNG-3236</a> * * @parameter expression="${scope}" default-value="compile" */ private String scope; // // /** // * Runtime Information used to check the Maven version // * // * @since 1.0.0 // * @component role="org.apache.maven.execution.RuntimeInformation" // */ // private RuntimeInformation rti; /** * The computed dependency tree root node of the Maven project. */ private DependencyNode rootNode; private PlexusContainer container; // Mojo methods ----------------------------------------------------------- /* * @see org.apache.maven.plugin.Mojo#execute() */ public void execute() throws MojoExecutionException, MojoFailureException { RepositoryUtils repositoryUtils = new RepositoryUtils(getLog(), container.getLoggerManager(), wagonManager, settings, mavenProjectBuilder, factory, resolver, project.getRemoteArtifactRepositories(), project.getPluginArtifactRepositories(), localRepository, repositoryMetadataManager); DependencyNode dependencyTreeNode = resolveProject(); Set dwsDependencyItems = getDWSDependencyItems(dependencyTreeNode, repositoryUtils); try { String dependencyTreeString = serialiseDependencyTree(dependencyTreeNode); String descriptorContent = createDescriptorContent(dependencyTreeString, dwsDependencyItems); if (outputPath != null) { File file = new File(outputPath + needsSeparator(outputPath) + "dws.xml"); OutputUtils.write(descriptorContent, file, getLog()); getLog().info("Wrote dws descriptor to: " + file); } else { OutputUtils.log(descriptorContent, getLog()); } } catch (IOException exception) { throw new MojoExecutionException("Cannot serialise project dependency tree", exception); } } /** * @return resolve the dependency tree */ private DependencyNode resolveProject() { try { ArtifactFilter artifactFilter = new ScopeArtifactFilter(Artifact.SCOPE_TEST); return dependencyTreeBuilder.buildDependencyTree(project, localRepository, factory, artifactMetadataSource, artifactFilter, collector); } catch (DependencyTreeBuilderException e) { getLog().error("Unable to build dependency tree.", e); return null; } } private String createDescriptorContent(String dependencyTreeString, Set dwsDependencyItems) { StringBuffer buffer = new StringBuffer(); final String CRLF = "\n"; buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + CRLF); buffer.append("<!--" + CRLF); buffer.append("\tFULL DEPENDENCY TREE" + CRLF); buffer.append(dependencyTreeString + CRLF); buffer.append("DEPENDENCIES BELOW ARE FILTERED BY SCOPE: " + scope + CRLF); buffer.append("-->" + CRLF); buffer.append("<dws>" + CRLF); if (dwsDependencyItems.size() > 0) { buffer.append("\t<dependencies>" + CRLF); for (Iterator it = dwsDependencyItems.iterator(); it.hasNext();) { DWSDependencyItem dependencyItem = (DWSDependencyItem) it.next(); buffer.append("\t\t<dependency>" + CRLF); buffer.append("\t\t\t<maven-artifact-description>" + CRLF); buffer.append("\t\t\t\t<groupId>" + dependencyItem.getArtifact().getGroupId() + "</groupId>" + CRLF); buffer.append("\t\t\t\t<artifactId>" + dependencyItem.getArtifact().getArtifactId() + "</artifactId>" + CRLF); buffer.append("\t\t\t\t<version>" + dependencyItem.getArtifact().getVersion() + "</version>" + CRLF); buffer.append("\t\t\t\t<classifier>" + dependencyItem.getArtifact().getClassifier() + "</classifier>" + CRLF); buffer.append("\t\t\t\t<scope>" + dependencyItem.getArtifact().getScope() + "</scope>" + CRLF); buffer.append("\t\t\t\t<type>" + dependencyItem.getArtifact().getType() + "</type>" + CRLF); buffer.append("\t\t\t</maven-artifact-description>" + CRLF); if (dependencyItem.getArtifactUrls().size() > 0) { buffer.append("\t\t\t<download-urls>" + CRLF); for (Iterator it2 = dependencyItem.getArtifactUrls().iterator(); it2.hasNext();) { String artifactUrl = (String) it2.next(); buffer.append("\t\t\t\t<download-url>" + artifactUrl + "</download-url>" + CRLF); } buffer.append("\t\t\t</download-urls>" + CRLF); } else { buffer.append("\t\t\t<download-urls />" + CRLF); } buffer.append("\t\t</dependency>" + CRLF); } buffer.append("\t</dependencies>" + CRLF); } else { buffer.append("\t<dependencies />" + CRLF); } buffer.append("</dws>"); return buffer.toString(); } private Set getDWSDependencyItems(DependencyNode rootNode, RepositoryUtils repositoryUtils) { CollectingDWSDependencyItemsVisitor visitor = new CollectingDWSDependencyItemsVisitor(remoteRepositories, scope, repositoryUtils); rootNode.accept(visitor); return visitor.getResult(); } // public methods --------------------------------------------------------- private String needsSeparator(String outputPath) { return (outputPath.endsWith("\\") || outputPath.endsWith("/") ? "" : "/"); } /** * Gets the Maven project used by this mojo. * * @return the Maven project */ public MavenProject getProject() { return project; } /** * Gets the computed dependency tree root node for the Maven project. * * @return the dependency tree root node */ public DependencyNode getDependencyTree() { return rootNode; } // private methods -------------------------------------------------------- // /** // * Gets the artifact filter to use when resolving the dependency tree. // * // * @return the artifact filter // */ // private ArtifactFilter createResolvingArtifactFilter() { // ArtifactFilter filter; // // filter filteredScope // if (scope != null) { // getLog().debug("+ Resolving dependency tree for filteredScope '" + scope + "'"); // filter = new ScopeArtifactFilter(scope); // } else { // filter = null; // } // return filter; // } /** * Serialises the specified dependency tree to a string. * * @param rootNode * the dependency tree root node to serialise * @return the serialised dependency tree */ private String serialiseDependencyTree(DependencyNode rootNode) { StringWriter writer = new StringWriter(); // standard extended or whitespace org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.TreeTokens treeTokens = toTreeTokens("standard"); DependencyNodeVisitor visitor = new SerializingDependencyNodeVisitor(writer, treeTokens); // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only visitor = new BuildingDependencyNodeVisitor(visitor); DependencyNodeFilter filter = createDependencyNodeFilter(); if (filter != null) { CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor(); DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter); rootNode.accept(firstPassVisitor); DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes()); visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter); } rootNode.accept(visitor); return writer.toString(); } /** * Gets the tree tokens instance for the specified name. * * @param tokens * the tree tokens name * @return the <code>TreeTokens</code> instance */ private org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.TreeTokens toTreeTokens(String tokens) { TreeTokens treeTokens; if ("whitespace".equals(tokens)) { getLog().debug("+ Using whitespace tree tokens"); treeTokens = SerializingDependencyNodeVisitor.WHITESPACE_TOKENS; } else if ("extended".equals(tokens)) { getLog().debug("+ Using extended tree tokens"); treeTokens = SerializingDependencyNodeVisitor.EXTENDED_TOKENS; } else { treeTokens = SerializingDependencyNodeVisitor.STANDARD_TOKENS; } return treeTokens; } /** * Gets the dependency node filter to use when serializing the dependency tree. * * @return the dependency node filter, or <code>null</code> if none required */ private DependencyNodeFilter createDependencyNodeFilter() { List filters = new ArrayList(); getLog().debug("+ Filtering omitted nodes from dependency tree"); filters.add(StateDependencyNodeFilter.INCLUDED); return filters.isEmpty() ? null : new AndDependencyNodeFilter(filters); } /** * Copied from Artifact.VersionRange. This is tweaked to handle singular ranges properly. Currently the default containsVersion method assumes a singular version means allow everything. This method assumes that "2.0.4" == "[2.0.4,)" * * @param allowedRange * range of allowed versions. * @param theVersion * the version to be checked. * @return true if the version is contained by the range. */ public static boolean containsVersion(VersionRange allowedRange, ArtifactVersion theVersion) { boolean matched = false; ArtifactVersion recommendedVersion = allowedRange.getRecommendedVersion(); if (recommendedVersion == null) { for (Iterator i = allowedRange.getRestrictions().iterator(); i.hasNext() && !matched;) { Restriction restriction = (Restriction) i.next(); if (restriction.containsVersion(theVersion)) { matched = true; } } } else { // only singular versions ever have a recommendedVersion int compareTo = recommendedVersion.compareTo(theVersion); matched = (compareTo <= 0); } return matched; } public void contextualize(Context context) throws ContextException { this.container = (PlexusContainer) context.get(PlexusConstants.PLEXUS_KEY); } }