001package org.apache.maven.plugins.enforcer; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.util.Collections; 023import java.util.List; 024 025import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; 026import org.apache.maven.enforcer.rule.api.EnforcerRule; 027import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 028import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; 029import org.apache.maven.plugins.enforcer.utils.ArtifactMatcher; 030import org.apache.maven.project.MavenProject; 031import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder; 032import org.apache.maven.shared.dependency.graph.DependencyNode; 033import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyGraphBuilder; 034import org.codehaus.plexus.component.repository.exception.ComponentLookupException; 035import org.codehaus.plexus.logging.console.ConsoleLogger; 036 037/** 038 * This rule bans all transitive dependencies. There is a configuration option to exclude certain artifacts from being 039 * checked. 040 * 041 * @author Jakub Senko 042 */ 043public class BanTransitiveDependencies 044 extends AbstractNonCacheableEnforcerRule 045 implements EnforcerRule 046{ 047 048 private EnforcerRuleHelper helper; 049 050 /** 051 * Specify the dependencies that will be ignored. This can be a list of artifacts in the format 052 * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific 053 * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br> 054 * You can override this patterns by using includes. Version is a string representing standard maven version range. 055 * Empty patterns will be ignored. 056 */ 057 private List<String> excludes; 058 059 /** 060 * Specify the dependencies that will be checked. These are exceptions to excludes intended for more convenient and 061 * finer settings. This can be a list of artifacts in the format 062 * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific 063 * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br> 064 * Version is a string representing standard maven version range. Empty patterns will be ignored. 065 */ 066 private List<String> includes; 067 068 /** 069 * Searches dependency tree recursively for transitive dependencies that are not excluded, while generating nice 070 * info message along the way. 071 * 072 * @throws InvalidVersionSpecificationException 073 */ 074 private static boolean searchTree( DependencyNode node, int level, ArtifactMatcher excludes, StringBuilder message ) 075 throws InvalidVersionSpecificationException 076 { 077 078 List<DependencyNode> children = node.getChildren(); 079 080 /* 081 * if the node is deeper than direct dependency and is empty, it is transitive. 082 */ 083 boolean hasTransitiveDependencies = level > 1; 084 085 boolean excluded = false; 086 087 /* 088 * holds recursive message from children, will be appended to current message if this node has any transitive 089 * descendants if message is null, don't generate recursive message. 090 */ 091 StringBuilder messageFromChildren = message == null ? null : new StringBuilder(); 092 093 if ( excludes.match( node.getArtifact() ) ) 094 { 095 // is excluded, we don't care about descendants 096 excluded = true; 097 hasTransitiveDependencies = false; 098 } 099 else 100 { 101 for ( DependencyNode childNode : children ) 102 { 103 /* 104 * if any of the children has transitive d. so does the parent 105 */ 106 hasTransitiveDependencies = 107 ( searchTree( childNode, level + 1, excludes, messageFromChildren ) || hasTransitiveDependencies ); 108 } 109 } 110 111 if ( ( excluded || hasTransitiveDependencies ) && message != null ) // then generate message 112 { 113 for ( int i = 0; i < level; i++ ) 114 { 115 message.append( " " ); 116 } 117 118 message.append( node.getArtifact() ); 119 120 if ( excluded ) 121 { 122 message.append( " [excluded]\n" ); 123 } 124 125 if ( hasTransitiveDependencies ) 126 { 127 if ( level == 1 ) 128 { 129 message.append( " has transitive dependencies:" ); 130 } 131 132 message.append( "\n" ).append( messageFromChildren ); 133 } 134 } 135 136 return hasTransitiveDependencies; 137 } 138 139 public void execute( EnforcerRuleHelper helper ) 140 throws EnforcerRuleException 141 { 142 this.helper = helper; 143 144 if ( excludes == null ) 145 { 146 excludes = Collections.emptyList(); 147 } 148 if ( includes == null ) 149 { 150 includes = Collections.emptyList(); 151 } 152 153 final ArtifactMatcher exclusions = new ArtifactMatcher( excludes, includes ); 154 155 DependencyNode rootNode = null; 156 157 try 158 { 159 MavenProject project = (MavenProject) helper.evaluate( "${project}" ); 160 rootNode = createDependencyGraphBuilder().buildDependencyGraph( project, null ); 161 } 162 catch ( Exception e ) 163 { 164 throw new EnforcerRuleException( "Error: Could not construct dependency tree.", e ); 165 } 166 167 String message = getMessage(); 168 StringBuilder generatedMessage = null; 169 if ( message == null ) 170 { 171 generatedMessage = new StringBuilder(); 172 } 173 174 try 175 { 176 if ( searchTree( rootNode, 0, exclusions, generatedMessage ) ) 177 { 178 throw new EnforcerRuleException( message == null ? generatedMessage.toString() : message ); 179 } 180 } 181 catch ( InvalidVersionSpecificationException e ) 182 { 183 throw new EnforcerRuleException( "Error: Invalid version range.", e ); 184 } 185 186 } 187 188 private DependencyGraphBuilder createDependencyGraphBuilder() 189 throws ComponentLookupException 190 { 191 // CHECKSTYLE_OFF: LineLength 192 DefaultDependencyGraphBuilder builder = 193 (DefaultDependencyGraphBuilder) helper.getContainer().lookup( DependencyGraphBuilder.class.getCanonicalName(), 194 "default" ); 195 // CHECKSTYLE_ON: LineLength 196 197 builder.enableLogging( new ConsoleLogger( ConsoleLogger.LEVEL_DISABLED, "DefaultDependencyGraphBuilder" ) ); 198 199 return builder; 200 } 201 202}