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