001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.enforcer.rules.dependency; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023 024import java.util.List; 025import java.util.Objects; 026import java.util.Set; 027import java.util.stream.Collectors; 028 029import org.apache.commons.lang3.StringUtils; 030import org.apache.maven.RepositoryUtils; 031import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 032import org.apache.maven.enforcer.rules.AbstractStandardEnforcerRule; 033import org.apache.maven.enforcer.rules.utils.ArtifactMatcher; 034import org.apache.maven.enforcer.rules.utils.ArtifactUtils; 035import org.apache.maven.execution.MavenSession; 036import org.eclipse.aether.artifact.ArtifactTypeRegistry; 037import org.eclipse.aether.graph.Dependency; 038import org.eclipse.aether.graph.DependencyNode; 039 040import static java.util.Optional.ofNullable; 041 042/** 043 * This rule bans all transitive dependencies. There is a configuration option to exclude certain artifacts from being 044 * checked. 045 * 046 * @author Jakub Senko 047 */ 048@Named("banTransitiveDependencies") 049public final class BanTransitiveDependencies extends AbstractStandardEnforcerRule { 050 051 /** 052 * Specify the dependencies that will be ignored. This can be a list of artifacts in the format 053 * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific 054 * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br> 055 * You can override this patterns by using includes. Version is a string representing standard maven version range. 056 * Empty patterns will be ignored. 057 */ 058 private List<String> excludes; 059 060 /** 061 * Specify the dependencies that will be checked. These are exceptions to excludes intended for more convenient and 062 * finer settings. This can be a list of artifacts in the format 063 * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific 064 * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br> 065 * Version is a string representing standard maven version range. Empty patterns will be ignored. 066 */ 067 private List<String> includes; 068 069 private final MavenSession session; 070 071 private final ResolverUtil resolverUtil; 072 073 @Inject 074 public BanTransitiveDependencies(MavenSession session, ResolverUtil resolverUtil) { 075 this.session = Objects.requireNonNull(session); 076 this.resolverUtil = Objects.requireNonNull(resolverUtil); 077 } 078 079 /** 080 * Searches dependency tree recursively for transitive dependencies that are not excluded, while generating nice 081 * info message along the way. 082 */ 083 private static boolean searchTree( 084 DependencyNode node, 085 int level, 086 ArtifactMatcher excludes, 087 Set<Dependency> directDependencies, 088 StringBuilder message) { 089 090 List<DependencyNode> children = node.getChildren(); 091 092 /* 093 * if the node is deeper than direct dependency and is empty, it is transitive. 094 */ 095 boolean hasTransitiveDependencies = level > 1; 096 097 boolean excluded = false; 098 099 /* 100 * holds recursive message from children, will be appended to current message if this node has any transitive 101 * descendants if message is null, don't generate recursive message. 102 */ 103 StringBuilder messageFromChildren = message == null ? null : new StringBuilder(); 104 105 if (excludes.match(ArtifactUtils.toArtifact(node))) { 106 // is excluded, we don't care about descendants 107 excluded = true; 108 hasTransitiveDependencies = false; 109 } else if (directDependencies.contains(node.getDependency())) { 110 hasTransitiveDependencies = false; 111 } else { 112 for (DependencyNode childNode : children) { 113 /* 114 * if any of the children has transitive d. so does the parent 115 */ 116 hasTransitiveDependencies = hasTransitiveDependencies 117 || searchTree(childNode, level + 1, excludes, directDependencies, messageFromChildren); 118 } 119 } 120 121 if ((excluded || hasTransitiveDependencies) && message != null) // then generate message 122 { 123 message.append(StringUtils.repeat(" ", level)).append(node.getArtifact()); 124 125 if (excluded) { 126 message.append(" [excluded]").append(System.lineSeparator()); 127 } 128 129 if (hasTransitiveDependencies) { 130 if (level > 0) { 131 message.append(" has transitive dependencies:"); 132 } 133 134 message.append(System.lineSeparator()).append(messageFromChildren); 135 } 136 } 137 138 return hasTransitiveDependencies; 139 } 140 141 @Override 142 public void execute() throws EnforcerRuleException { 143 ArtifactTypeRegistry artifactTypeRegistry = 144 session.getRepositorySession().getArtifactTypeRegistry(); 145 ArtifactMatcher exclusions = new ArtifactMatcher(excludes, includes); 146 Set<Dependency> directDependencies = session.getCurrentProject().getDependencies().stream() 147 .map(d -> RepositoryUtils.toDependency(d, artifactTypeRegistry)) 148 .collect(Collectors.toSet()); 149 150 DependencyNode rootNode = resolverUtil.resolveTransitiveDependencies(); 151 StringBuilder generatedMessage = new StringBuilder(); 152 if (searchTree(rootNode, 0, exclusions, directDependencies, generatedMessage)) { 153 throw new EnforcerRuleException(ofNullable(getMessage()).orElse(generatedMessage.toString())); 154 } 155 } 156 157 @Override 158 public String toString() { 159 return String.format("BanTransitiveDependencies[message=%s, excludes=%s]", getMessage(), excludes); 160 } 161}