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 @Override 140 public void execute( EnforcerRuleHelper helper ) 141 throws EnforcerRuleException 142 { 143 this.helper = helper; 144 145 if ( excludes == null ) 146 { 147 excludes = Collections.emptyList(); 148 } 149 if ( includes == null ) 150 { 151 includes = Collections.emptyList(); 152 } 153 154 final ArtifactMatcher exclusions = new ArtifactMatcher( excludes, includes ); 155 156 DependencyNode rootNode = null; 157 158 try 159 { 160 MavenProject project = (MavenProject) helper.evaluate( "${project}" ); 161 rootNode = createDependencyGraphBuilder().buildDependencyGraph( project, null ); 162 } 163 catch ( Exception e ) 164 { 165 throw new EnforcerRuleException( "Error: Could not construct dependency tree.", e ); 166 } 167 168 String message = getMessage(); 169 StringBuilder generatedMessage = null; 170 if ( message == null ) 171 { 172 generatedMessage = new StringBuilder(); 173 } 174 175 try 176 { 177 if ( searchTree( rootNode, 0, exclusions, generatedMessage ) ) 178 { 179 throw new EnforcerRuleException( message == null ? generatedMessage.toString() : message ); 180 } 181 } 182 catch ( InvalidVersionSpecificationException e ) 183 { 184 throw new EnforcerRuleException( "Error: Invalid version range.", e ); 185 } 186 187 } 188 189 private DependencyGraphBuilder createDependencyGraphBuilder() 190 throws ComponentLookupException 191 { 192 // CHECKSTYLE_OFF: LineLength 193 DefaultDependencyGraphBuilder builder = 194 (DefaultDependencyGraphBuilder) helper.getContainer().lookup( DependencyGraphBuilder.class.getCanonicalName(), 195 "default" ); 196 // CHECKSTYLE_ON: LineLength 197 198 builder.enableLogging( new ConsoleLogger( ConsoleLogger.LEVEL_DISABLED, "DefaultDependencyGraphBuilder" ) ); 199 200 return builder; 201 } 202 203}