View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.dependency.fromDependencies;
20  
21  import javax.inject.Inject;
22  
23  import java.io.File;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.HashSet;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.stream.Collectors;
31  
32  import org.apache.maven.RepositoryUtils;
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
35  import org.apache.maven.execution.MavenSession;
36  import org.apache.maven.model.Dependency;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.dependency.AbstractDependencyMojo;
40  import org.apache.maven.plugins.dependency.utils.DependencyStatusSets;
41  import org.apache.maven.plugins.dependency.utils.DependencyUtil;
42  import org.apache.maven.plugins.dependency.utils.ResolverUtil;
43  import org.apache.maven.plugins.dependency.utils.translators.ArtifactTranslator;
44  import org.apache.maven.plugins.dependency.utils.translators.ClassifierTypeTranslator;
45  import org.apache.maven.project.DefaultProjectBuildingRequest;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.project.ProjectBuilder;
48  import org.apache.maven.project.ProjectBuildingException;
49  import org.apache.maven.project.ProjectBuildingRequest;
50  import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
51  import org.apache.maven.shared.artifact.filter.collection.ArtifactIdFilter;
52  import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
53  import org.apache.maven.shared.artifact.filter.collection.ClassifierFilter;
54  import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
55  import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter;
56  import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
57  import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
58  import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
59  import org.eclipse.aether.repository.RemoteRepository;
60  import org.eclipse.aether.resolution.ArtifactResolutionException;
61  import org.eclipse.aether.resolution.DependencyResolutionException;
62  import org.sonatype.plexus.build.incremental.BuildContext;
63  
64  /**
65   * Class that encapsulates the plugin parameters, and contains methods that handle dependency filtering.
66   *
67   * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
68   * @see org.apache.maven.plugins.dependency.AbstractDependencyMojo
69   */
70  public abstract class AbstractDependencyFilterMojo extends AbstractDependencyMojo {
71  
72      /**
73       * Overwrite release artifacts.
74       *
75       * @since 1.0
76       */
77      @Parameter(property = "overWriteReleases", defaultValue = "false")
78      protected boolean overWriteReleases;
79  
80      /**
81       * Overwrite snapshot artifacts.
82       *
83       * @since 1.0
84       */
85      @Parameter(property = "overWriteSnapshots", defaultValue = "false")
86      protected boolean overWriteSnapshots;
87  
88      /**
89       * Overwrite artifacts that don't exist or are older than the source.
90       *
91       * @since 2.0
92       */
93      @Parameter(property = "overWriteIfNewer", defaultValue = "true")
94      protected boolean overWriteIfNewer;
95  
96      /**
97       * If we should exclude transitive dependencies.
98       *
99       * @since 2.0
100      */
101     @Parameter(property = "excludeTransitive", defaultValue = "false")
102     protected boolean excludeTransitive;
103 
104     /**
105      * Comma-separated list of Types to include. Empty String indicates include everything (default).
106      *
107      * @since 2.0
108      */
109     @Parameter(property = "includeTypes", defaultValue = "")
110     protected String includeTypes;
111 
112     /**
113      * Comma-separated list of Types to exclude. Empty String indicates don't exclude anything (default).
114      *
115      * @since 2.0
116      */
117     @Parameter(property = "excludeTypes", defaultValue = "")
118     protected String excludeTypes;
119 
120     /**
121      * Scope threshold to include. An empty string indicates include all dependencies (default).<br>
122      * The scope threshold value being interpreted is the scope as
123      * Maven filters for creating a classpath, not as specified in the pom. In summary:
124      * <ul>
125      * <li><code>runtime</code> include scope gives runtime and compile dependencies,</li>
126      * <li><code>compile</code> include scope gives compile, provided, and system dependencies,</li>
127      * <li><code>test</code> include scope gives all dependencies (equivalent to default),</li>
128      * <li><code>provided</code> include scope just gives provided dependencies,</li>
129      * <li><code>system</code> include scope just gives system dependencies.</li>
130      * </ul>
131      *
132      * @since 2.0
133      */
134     @Parameter(property = "includeScope", defaultValue = "")
135     protected String includeScope;
136 
137     /**
138      * Scope threshold to exclude, if no value is defined for include.
139      * An empty string indicates no dependencies (default).  Unlike the other
140      * exclusion parameters, this property does not support a comma-delimited
141      * list of scope exclusions. Just one scope may be excluded at a time.<br>
142      * The scope threshold value being interpreted is the scope as
143      * Maven filters for creating a classpath, not as specified in the pom. In summary:
144      * <ul>
145      * <li><code>runtime</code> exclude scope excludes runtime and compile dependencies,</li>
146      * <li><code>compile</code> exclude scope excludes compile, provided, and system dependencies,</li>
147      * <li><code>test</code> exclude scope excludes all dependencies, then not really a legitimate option: it will
148      * fail, you probably meant to configure includeScope = compile</li>
149      * <li><code>provided</code> exclude scope just excludes provided dependencies,</li>
150      * <li><code>system</code> exclude scope just excludes system dependencies.</li>
151      * </ul>
152      *
153      * @since 2.0
154      */
155     @Parameter(property = "excludeScope", defaultValue = "")
156     protected String excludeScope;
157 
158     /**
159      * Comma-separated list of Classifiers to include. Empty string indicates include everything (default).
160      *
161      * @since 2.0
162      */
163     @Parameter(property = "includeClassifiers", defaultValue = "")
164     protected String includeClassifiers;
165 
166     /**
167      * Comma-separated list of Classifiers to exclude. Empty String indicates don't exclude anything (default).
168      *
169      * @since 2.0
170      */
171     @Parameter(property = "excludeClassifiers", defaultValue = "")
172     protected String excludeClassifiers;
173 
174     /**
175      * Specify classifier to look for. Example: sources.
176      *
177      * @since 2.0
178      */
179     @Parameter(property = "classifier", defaultValue = "")
180     protected String classifier;
181 
182     /**
183      * Specify type to look for when constructing artifact based on classifier. Example: java-source,jar,war.
184      *
185      * @since 2.0
186      */
187     @Parameter(property = "type", defaultValue = "")
188     protected String type;
189 
190     /**
191      * Comma-separated list of artifact IDs to exclude.
192      *
193      * @since 2.0
194      */
195     @Parameter(property = "excludeArtifactIds", defaultValue = "")
196     protected String excludeArtifactIds;
197 
198     /**
199      * Comma-separated list of artifact IDs to include. Empty String indicates include everything (default).
200      *
201      * @since 2.0
202      */
203     @Parameter(property = "includeArtifactIds", defaultValue = "")
204     protected String includeArtifactIds;
205 
206     /**
207      * Comma-separated list of group IDs to exclude.
208      *
209      * @since 2.0
210      */
211     @Parameter(property = "excludeGroupIds", defaultValue = "")
212     protected String excludeGroupIds;
213 
214     /**
215      * Comma-separated list of group IDs to include. Empty string indicates include everything (default).
216      *
217      * @since 2.0
218      */
219     @Parameter(property = "includeGroupIds", defaultValue = "")
220     protected String includeGroupIds;
221 
222     /**
223      * Directory to store flag files.
224      *
225      * @since 2.0
226      */
227     @Parameter(
228             property = "markersDirectory",
229             defaultValue = "${project.build.directory}/dependency-maven-plugin-markers")
230     protected File markersDirectory;
231 
232     /**
233      * Prepend the group ID during copy.
234      *
235      * @since 2.2
236      */
237     @Parameter(property = "mdep.prependGroupId", defaultValue = "false")
238     protected boolean prependGroupId = false;
239 
240     /**
241      * By default, this goal uses the project itself as the root of the dependency tree.
242      * With graphRoots, you can select a subtree of dependencies based on groupId and artifactId.
243      * After that, the general include/exclude filters can be applied.
244      *
245      * @since 3.10.0
246      */
247     @Parameter
248     private List<GraphRoot> graphRoots;
249 
250     private final ResolverUtil resolverUtil;
251 
252     private final ProjectBuilder projectBuilder;
253 
254     private final ArtifactHandlerManager artifactHandlerManager;
255 
256     @Inject
257     protected AbstractDependencyFilterMojo(
258             MavenSession session,
259             BuildContext buildContext,
260             MavenProject project,
261             ResolverUtil resolverUtil,
262             ProjectBuilder projectBuilder,
263             ArtifactHandlerManager artifactHandlerManager) {
264         super(session, buildContext, project);
265         this.resolverUtil = resolverUtil;
266         this.projectBuilder = projectBuilder;
267         this.artifactHandlerManager = artifactHandlerManager;
268     }
269 
270     /**
271      * Return an {@link ArtifactsFilter} indicating which artifacts must be filtered out.
272      *
273      * @return an {@link ArtifactsFilter} indicating which artifacts must be filtered out
274      */
275     protected abstract ArtifactsFilter getMarkedArtifactFilter();
276 
277     /**
278      * Retrieves dependencies, either direct only or all including transitive.
279      *
280      * @param stopOnFailure true to fail if resolution does not work or false not to fail
281      * @return a set of artifacts
282      * @throws MojoExecutionException in case of errors
283      */
284     protected Set<Artifact> getResolvedDependencies(boolean stopOnFailure) throws MojoExecutionException {
285 
286         DependencyStatusSets status = getDependencySets(stopOnFailure);
287 
288         return status.getResolvedDependencies();
289     }
290 
291     /**
292      * @param stopOnFailure true/false
293      * @return {@link DependencyStatusSets}
294      * @throws MojoExecutionException in case of an error
295      */
296     protected DependencyStatusSets getDependencySets(boolean stopOnFailure) throws MojoExecutionException {
297         return getDependencySets(stopOnFailure, false);
298     }
299 
300     /**
301      * Method creates filters and filters the projects dependencies. This method also transforms the dependencies if
302      * classifier is set. The dependencies are filtered in least specific to most specific order.
303      *
304      * @param stopOnFailure true to fail if artifacts can't be resolved false otherwise
305      * @param includeParents <code>true</code> if parents should be included or not <code>false</code>
306      * @return DependencyStatusSets - Bean of TreeSets that contains information on the projects dependencies
307      * @throws MojoExecutionException in case of errors
308      */
309     protected DependencyStatusSets getDependencySets(boolean stopOnFailure, boolean includeParents)
310             throws MojoExecutionException {
311 
312         // add filters in well known order, least specific to most specific
313         FilterArtifacts filter = new FilterArtifacts();
314 
315         filter.addFilter(new ProjectTransitivityFilter(getProject().getDependencyArtifacts(), this.excludeTransitive));
316 
317         if ("test".equals(this.excludeScope)) {
318             throw new MojoExecutionException("Excluding every artifact inside 'test' resolution scope means "
319                     + "excluding everything: you probably want includeScope='compile', "
320                     + "read parameters documentation for detailed explanations");
321         }
322         filter.addFilter(new ScopeFilter(
323                 DependencyUtil.cleanToBeTokenizedString(this.includeScope),
324                 DependencyUtil.cleanToBeTokenizedString(this.excludeScope)));
325 
326         filter.addFilter(new TypeFilter(
327                 DependencyUtil.cleanToBeTokenizedString(this.includeTypes),
328                 DependencyUtil.cleanToBeTokenizedString(this.excludeTypes)));
329 
330         filter.addFilter(new ClassifierFilter(
331                 DependencyUtil.cleanToBeTokenizedString(this.includeClassifiers),
332                 DependencyUtil.cleanToBeTokenizedString(this.excludeClassifiers)));
333 
334         filter.addFilter(new GroupIdFilter(
335                 DependencyUtil.cleanToBeTokenizedString(this.includeGroupIds),
336                 DependencyUtil.cleanToBeTokenizedString(this.excludeGroupIds)));
337 
338         filter.addFilter(new ArtifactIdFilter(
339                 DependencyUtil.cleanToBeTokenizedString(this.includeArtifactIds),
340                 DependencyUtil.cleanToBeTokenizedString(this.excludeArtifactIds)));
341 
342         // start with all artifacts.
343         Set<Artifact> artifacts;
344 
345         try {
346             artifacts = collectArtifacts(getProject());
347         } catch (DependencyResolutionException e) {
348             throw new MojoExecutionException("Failed to collect artifacts", e);
349         }
350 
351         if (includeParents) {
352             // add dependencies parents
353             for (Artifact dep : new ArrayList<>(artifacts)) {
354                 addParentArtifacts(buildProjectFromArtifact(dep), artifacts);
355             }
356 
357             // add current project parent
358             addParentArtifacts(getProject(), artifacts);
359         }
360 
361         // perform filtering
362         try {
363             artifacts = filter.filter(artifacts);
364         } catch (ArtifactFilterException e) {
365             throw new MojoExecutionException(e.getMessage(), e);
366         }
367 
368         // transform artifacts if classifier is set
369         DependencyStatusSets status;
370         if (classifier != null && !classifier.isEmpty()) {
371             status = getClassifierTranslatedDependencies(artifacts, stopOnFailure);
372         } else {
373             status = filterMarkedDependencies(artifacts);
374         }
375 
376         return status;
377     }
378 
379     private MavenProject buildProjectFromArtifact(Artifact artifact) throws MojoExecutionException {
380         try {
381             ProjectBuildingRequest buildingRequest =
382                     new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
383             buildingRequest.setProcessPlugins(false);
384             return projectBuilder.build(artifact, buildingRequest).getProject();
385         } catch (ProjectBuildingException e) {
386             throw new MojoExecutionException("Coud not build project for " + artifact.getId(), e);
387         }
388     }
389 
390     private void addParentArtifacts(MavenProject project, Set<Artifact> artifacts) throws MojoExecutionException {
391         while (project.hasParent()) {
392             project = project.getParent();
393 
394             if (artifacts.contains(project.getArtifact())) {
395                 // artifact already in the set
396                 break;
397             }
398             try {
399                 org.eclipse.aether.artifact.Artifact resolvedArtifact = resolverUtil.resolveArtifact(
400                         RepositoryUtils.toArtifact(project.getArtifact()), project.getRemoteProjectRepositories());
401 
402                 artifacts.add(RepositoryUtils.toArtifact(resolvedArtifact));
403             } catch (ArtifactResolutionException e) {
404                 throw new MojoExecutionException(e.getMessage(), e);
405             }
406         }
407     }
408 
409     /**
410      * Transform artifacts.
411      *
412      * @param artifacts set of artifacts {@link Artifact}
413      * @param stopOnFailure true/false
414      * @return DependencyStatusSets - Bean of TreeSets that contains information on the projects dependencies
415      * @throws MojoExecutionException in case of an error
416      */
417     private DependencyStatusSets getClassifierTranslatedDependencies(Set<Artifact> artifacts, boolean stopOnFailure)
418             throws MojoExecutionException {
419         Set<Artifact> unResolvedArtifacts = new LinkedHashSet<>();
420         Set<Artifact> resolvedArtifacts = artifacts;
421         DependencyStatusSets status = new DependencyStatusSets();
422 
423         // possibly translate artifacts into a new set of artifacts based on the
424         // classifier and type
425         // if this did something, we need to resolve the new artifacts
426         if (classifier != null && !classifier.isEmpty()) {
427             ArtifactTranslator translator =
428                     new ClassifierTypeTranslator(artifactHandlerManager, this.classifier, this.type);
429             Collection<org.eclipse.aether.artifact.Artifact> coordinates = translator.translate(artifacts, getLog());
430 
431             status = filterMarkedDependencies(artifacts);
432 
433             // the unskipped artifacts are in the resolved set.
434             artifacts = status.getResolvedDependencies();
435 
436             // resolve the rest of the artifacts
437             resolvedArtifacts = resolve(new LinkedHashSet<>(coordinates), stopOnFailure);
438 
439             // calculate the artifacts not resolved.
440             unResolvedArtifacts.addAll(artifacts);
441             unResolvedArtifacts.removeAll(resolvedArtifacts);
442         }
443 
444         // return a bean of all 3 sets.
445         status.setResolvedDependencies(resolvedArtifacts);
446         status.setUnResolvedDependencies(unResolvedArtifacts);
447 
448         return status;
449     }
450 
451     /**
452      * Filter the marked dependencies.
453      *
454      * @param artifacts the artifacts set {@link Artifact}
455      * @return status set {@link DependencyStatusSets}
456      * @throws MojoExecutionException in case of an error
457      */
458     protected DependencyStatusSets filterMarkedDependencies(Set<Artifact> artifacts) throws MojoExecutionException {
459         // remove files that have markers already
460         FilterArtifacts filter = new FilterArtifacts();
461         filter.clearFilters();
462         filter.addFilter(getMarkedArtifactFilter());
463 
464         Set<Artifact> unMarkedArtifacts;
465         try {
466             unMarkedArtifacts = filter.filter(artifacts);
467         } catch (ArtifactFilterException e) {
468             throw new MojoExecutionException(e.getMessage(), e);
469         }
470 
471         // calculate the skipped artifacts
472         Set<Artifact> skippedArtifacts = new LinkedHashSet<>(artifacts);
473         skippedArtifacts.removeAll(unMarkedArtifacts);
474 
475         return new DependencyStatusSets(unMarkedArtifacts, null, skippedArtifacts);
476     }
477 
478     /**
479      * @param artifacts the set of artifacts
480      * @param stopOnFailure <code>true</code> if we should fail with exception if an artifact couldn't be resolved
481      *            <code>false</code> otherwise
482      * @return the resolved artifacts. {@link Artifact}.
483      * @throws MojoExecutionException in case of error
484      */
485     private Set<Artifact> resolve(Set<org.eclipse.aether.artifact.Artifact> artifacts, boolean stopOnFailure)
486             throws MojoExecutionException {
487 
488         Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
489         for (org.eclipse.aether.artifact.Artifact artifact : artifacts) {
490             try {
491                 org.eclipse.aether.artifact.Artifact resolveArtifact =
492                         resolverUtil.resolveArtifact(artifact, getProject().getRemoteProjectRepositories());
493                 resolvedArtifacts.add(RepositoryUtils.toArtifact(resolveArtifact));
494             } catch (ArtifactResolutionException ex) {
495                 // an error occurred during resolution, log it and continue
496                 getLog().debug("error resolving: " + artifact, ex);
497                 if (stopOnFailure) {
498                     throw new MojoExecutionException("error resolving: " + artifact, ex);
499                 }
500             }
501         }
502         return resolvedArtifacts;
503     }
504 
505     private Set<Artifact> collectArtifacts(MavenProject project) throws DependencyResolutionException {
506         if (graphRoots == null || graphRoots.isEmpty()) {
507             // artifact have already been resolved here due to
508             // @Mojo(requiresDependencyResolution = ResolutionScope.TEST) on final Mojo
509             return project.getArtifacts();
510         } else {
511             // MavenProject doesn't provide access to the graph of dependencies(only the direct dependencies)
512             // Hence we need to re-resolve artifacts, but only for the matching graphnodes
513             List<DependencyMatcher> filterMatchers =
514                     graphRoots.stream().map(GraphRootMatcher::new).collect(Collectors.toList());
515 
516             DependencyMatcher subTreeMatcher = new OrDependencyMatcher(filterMatchers);
517 
518             Set<Artifact> artifacts = new HashSet<>();
519             for (Dependency dep : project.getDependencies()) {
520                 if (subTreeMatcher.matches(dep)) {
521                     artifacts.addAll(resolveDependencyArtifacts(dep));
522                 }
523             }
524             return artifacts;
525         }
526     }
527 
528     private Set<Artifact> resolveDependencyArtifacts(Dependency root) throws DependencyResolutionException {
529         org.eclipse.aether.graph.Dependency dependency = RepositoryUtils.toDependency(
530                 root, session.getRepositorySession().getArtifactTypeRegistry());
531 
532         List<RemoteRepository> remoteRepositories = getProject().getRemoteProjectRepositories();
533 
534         Collection<org.eclipse.aether.artifact.Artifact> depArtifacts =
535                 resolverUtil.resolveDependencies(dependency.getArtifact(), remoteRepositories);
536 
537         return depArtifacts.stream().map(RepositoryUtils::toArtifact).collect(Collectors.toSet());
538     }
539 
540     /**
541      * @return returns the markersDirectory
542      */
543     public File getMarkersDirectory() {
544         return this.markersDirectory;
545     }
546 
547     /**
548      * @param theMarkersDirectory the markersDirectory to set
549      */
550     public void setMarkersDirectory(File theMarkersDirectory) {
551         this.markersDirectory = theMarkersDirectory;
552     }
553 
554     // TODO: Set marker files.
555 
556     /**
557      * @return true, if the groupId should be prepended to the filename
558      */
559     public boolean isPrependGroupId() {
560         return prependGroupId;
561     }
562 
563     /**
564      * @param prependGroupId true if the groupId must be prepended during the copy
565      */
566     public void setPrependGroupId(boolean prependGroupId) {
567         this.prependGroupId = prependGroupId;
568     }
569 
570     /**
571      * @return {@link #resolverUtil}
572      */
573     protected final ResolverUtil getResolverUtil() {
574         return resolverUtil;
575     }
576 }