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.enforcer.rules.dependency;
20  
21  import java.util.Collections;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.Set;
26  
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
30  import org.apache.maven.enforcer.rules.AbstractStandardEnforcerRule;
31  import org.apache.maven.enforcer.rules.utils.ArtifactMatcher.MatchingArtifact;
32  import org.apache.maven.enforcer.rules.utils.ArtifactUtils;
33  import org.apache.maven.execution.MavenSession;
34  import org.eclipse.aether.graph.DependencyNode;
35  
36  /**
37   * Abstract base class for rules which validate the transitive
38   * dependency tree by traversing all children and validating every
39   * dependency artifact.
40   */
41  abstract class BannedDependenciesBase extends AbstractStandardEnforcerRule {
42  
43      /**
44       * Specify the banned dependencies. This can be a list of artifacts in the format
45       * <code>groupId[:artifactId][:version]</code>. Any of the sections can be a wildcard
46       * by using '*' (ie group:*:1.0) <br>
47       * The rule will fail if any dependency matches any exclude, unless it also matches
48       * an include rule.
49       *
50       * @see #setExcludes(List)
51       * @see #getExcludes()
52       */
53      private List<String> excludes = null;
54  
55      /**
56       * Specify the allowed dependencies. This can be a list of artifacts in the format
57       * <code>groupId[:artifactId][:version]</code>. Any of the sections can be a wildcard
58       * by using '*' (ie group:*:1.0) <br>
59       * Includes override the exclude rules. It is meant to allow wide exclusion rules
60       * with wildcards and still allow a
61       * smaller set of includes. <br>
62       * For example, to ban all xerces except xerces-api -&gt; exclude "xerces", include "xerces:xerces-api"
63       *
64       * @see #setIncludes(List)
65       * @see #getIncludes()
66       */
67      private List<String> includes = null;
68  
69      /** Specify if transitive dependencies should be searched (default) or only look at direct dependencies. */
70      private boolean searchTransitive = true;
71  
72      private final MavenSession session;
73  
74      private final ResolverUtil resolverUtil;
75  
76      BannedDependenciesBase(MavenSession session, ResolverUtil resolverUtil) {
77          this.session = Objects.requireNonNull(session);
78          this.resolverUtil = Objects.requireNonNull(resolverUtil);
79      }
80  
81      protected MavenSession getSession() {
82          return session;
83      }
84  
85      @Override
86      public void execute() throws EnforcerRuleException {
87  
88          if (!searchTransitive) {
89              String result = session.getCurrentProject().getDependencyArtifacts().stream()
90                      .filter(a -> !validate(a))
91                      .collect(
92                              StringBuilder::new,
93                              (messageBuilder, node) -> messageBuilder
94                                      .append(System.lineSeparator())
95                                      .append(node.getId())
96                                      .append(" <--- ")
97                                      .append(getErrorMessage()),
98                              (m1, m2) -> m1.append(m2.toString()))
99                      .toString();
100             if (!result.isEmpty()) {
101                 String message = "";
102                 if (getMessage() != null) {
103                     message = getMessage() + System.lineSeparator();
104                 }
105                 throw new EnforcerRuleException(message + result);
106             }
107         } else {
108             StringBuilder messageBuilder = new StringBuilder();
109             DependencyNode rootNode = resolverUtil.resolveTransitiveDependenciesVerbose(Collections.emptyList());
110             Set<MatchingArtifact> visitedArtifacts = new HashSet<>();
111             if (!validate(rootNode, 0, messageBuilder, visitedArtifacts)) {
112                 String message = "";
113                 if (getMessage() != null) {
114                     message = getMessage() + System.lineSeparator();
115                 }
116                 throw new EnforcerRuleException(message + messageBuilder);
117             }
118         }
119     }
120 
121     protected boolean validate(
122             DependencyNode node, int level, StringBuilder messageBuilder, Set<MatchingArtifact> visitedArtifacts) {
123         Artifact artifact = ArtifactUtils.toArtifact(node);
124         boolean rootFailed = false;
125         if (level > 0 && visitedArtifacts.add(new MatchingArtifact(artifact))) {
126             rootFailed = !validate(artifact);
127         }
128         StringBuilder childMessageBuilder = new StringBuilder();
129         if (rootFailed
130                 || !node.getChildren().stream()
131                         .map(childNode -> validate(childNode, level + 1, childMessageBuilder, visitedArtifacts))
132                         .reduce(true, Boolean::logicalAnd)) {
133             messageBuilder
134                     .append(StringUtils.repeat("   ", level))
135                     .append(ArtifactUtils.toArtifact(node).getId());
136             if (rootFailed) {
137                 messageBuilder.append(" <--- ").append(getErrorMessage());
138             }
139             messageBuilder.append(System.lineSeparator()).append(childMessageBuilder);
140             return false;
141         }
142         return true;
143     }
144 
145     protected abstract String getErrorMessage();
146 
147     /**
148      * Validates a dependency artifact if it fulfills the enforcer rule
149      *
150      * @param dependency dependency to be checked against the list of excludes
151      * @return {@code true} if the dependency <b>passes</b> the rule, {@code false} if the dependency
152      * triggers a validation error
153      */
154     protected abstract boolean validate(Artifact dependency);
155 
156     /**
157      * Sets the search transitive.
158      *
159      * @param searchTransitive the searchTransitive to set
160      */
161     public void setSearchTransitive(boolean searchTransitive) {
162         this.searchTransitive = searchTransitive;
163     }
164 
165     /**
166      * Gets the excludes.
167      *
168      * @return the excludes
169      */
170     public List<String> getExcludes() {
171         return this.excludes;
172     }
173 
174     /**
175      * Specify the banned dependencies. This can be a list of artifacts in the format
176      * <code>groupId[:artifactId][:version]</code>. Any of the sections can be a wildcard
177      * by using '*' (ie group:*:1.0) <br>
178      * The rule will fail if any dependency matches any exclude, unless it also matches an
179      * include rule.
180      *
181      * @see #getExcludes()
182      * @param excludes the excludes to set
183      */
184     public void setExcludes(List<String> excludes) {
185         this.excludes = excludes;
186     }
187 
188     /**
189      * Gets the includes.
190      *
191      * @return the includes
192      */
193     public List<String> getIncludes() {
194         return this.includes;
195     }
196 
197     /**
198      * Specify the allowed dependencies. This can be a list of artifacts in the format
199      * <code>groupId[:artifactId][:version]</code>. Any of the sections can be a wildcard
200      * by using '*' (ie group:*:1.0) <br>
201      * Includes override the exclude rules. It is meant to allow wide exclusion rules with
202      * wildcards and still allow a
203      * smaller set of includes. <br>
204      * For example, to ban all xerces except xerces-api → exclude "xerces",
205      * include "xerces:xerces-api"
206      *
207      * @see #setIncludes(List)
208      * @param includes the includes to set
209      */
210     public void setIncludes(List<String> includes) {
211         this.includes = includes;
212     }
213 
214     public boolean isSearchTransitive() {
215         return searchTransitive;
216     }
217 }