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 -> 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 }