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.execution.MavenSession; 030import org.apache.maven.plugins.enforcer.utils.ArtifactMatcher; 031import org.apache.maven.project.DefaultProjectBuildingRequest; 032import org.apache.maven.project.MavenProject; 033import org.apache.maven.project.ProjectBuildingRequest; 034import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder; 035import org.apache.maven.shared.dependency.graph.DependencyNode; 036import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyGraphBuilder; 037import org.codehaus.plexus.component.repository.exception.ComponentLookupException; 038import org.codehaus.plexus.logging.console.ConsoleLogger; 039 040/** 041 * This rule bans all transitive dependencies. There is a configuration option to exclude certain artifacts from being 042 * checked. 043 * 044 * @author Jakub Senko 045 */ 046public class BanTransitiveDependencies 047 extends AbstractNonCacheableEnforcerRule 048 implements EnforcerRule 049{ 050 051 private EnforcerRuleHelper helper; 052 053 /** 054 * Specify the dependencies that will be ignored. This can be a list of artifacts in the format 055 * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific 056 * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br> 057 * You can override this patterns by using includes. Version is a string representing standard maven version range. 058 * Empty patterns will be ignored. 059 */ 060 private List<String> excludes; 061 062 /** 063 * Specify the dependencies that will be checked. These are exceptions to excludes intended for more convenient and 064 * finer settings. This can be a list of artifacts in the format 065 * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific 066 * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br> 067 * Version is a string representing standard maven version range. Empty patterns will be ignored. 068 */ 069 private List<String> includes; 070 071 /** 072 * Searches dependency tree recursively for transitive dependencies that are not excluded, while generating nice 073 * info message along the way. 074 * 075 * @throws InvalidVersionSpecificationException 076 */ 077 private static boolean searchTree( DependencyNode node, int level, ArtifactMatcher excludes, StringBuilder message ) 078 throws InvalidVersionSpecificationException 079 { 080 081 List<DependencyNode> children = node.getChildren(); 082 083 /* 084 * if the node is deeper than direct dependency and is empty, it is transitive. 085 */ 086 boolean hasTransitiveDependencies = level > 1; 087 088 boolean excluded = false; 089 090 /* 091 * holds recursive message from children, will be appended to current message if this node has any transitive 092 * descendants if message is null, don't generate recursive message. 093 */ 094 StringBuilder messageFromChildren = message == null ? null : new StringBuilder(); 095 096 if ( excludes.match( node.getArtifact() ) ) 097 { 098 // is excluded, we don't care about descendants 099 excluded = true; 100 hasTransitiveDependencies = false; 101 } 102 else 103 { 104 for ( DependencyNode childNode : children ) 105 { 106 /* 107 * if any of the children has transitive d. so does the parent 108 */ 109 hasTransitiveDependencies = 110 ( searchTree( childNode, level + 1, excludes, messageFromChildren ) || hasTransitiveDependencies ); 111 } 112 } 113 114 if ( ( excluded || hasTransitiveDependencies ) && message != null ) // then generate message 115 { 116 for ( int i = 0; i < level; i++ ) 117 { 118 message.append( " " ); 119 } 120 121 message.append( node.getArtifact() ); 122 123 if ( excluded ) 124 { 125 message.append( " [excluded]" + System.lineSeparator() ); 126 } 127 128 if ( hasTransitiveDependencies ) 129 { 130 if ( level == 1 ) 131 { 132 message.append( " has transitive dependencies:" ); 133 } 134 135 message.append( System.lineSeparator() ).append( messageFromChildren ); 136 } 137 } 138 139 return hasTransitiveDependencies; 140 } 141 142 @Override 143 public void execute( EnforcerRuleHelper helper ) 144 throws EnforcerRuleException 145 { 146 this.helper = helper; 147 148 if ( excludes == null ) 149 { 150 excludes = Collections.emptyList(); 151 } 152 if ( includes == null ) 153 { 154 includes = Collections.emptyList(); 155 } 156 157 final ArtifactMatcher exclusions = new ArtifactMatcher( excludes, includes ); 158 159 DependencyNode rootNode = null; 160 161 try 162 { 163 MavenProject project = (MavenProject) helper.evaluate( "${project}" ); 164 MavenSession session = (MavenSession) helper.evaluate( "${session}" ); 165 166 ProjectBuildingRequest buildingRequest = 167 new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() ); 168 buildingRequest.setProject( project ); 169 170 rootNode = createDependencyGraphBuilder().buildDependencyGraph( buildingRequest, null ); 171 } 172 catch ( Exception e ) 173 { 174 throw new EnforcerRuleException( "Error: Could not construct dependency tree.", e ); 175 } 176 177 String message = getMessage(); 178 StringBuilder generatedMessage = null; 179 if ( message == null ) 180 { 181 generatedMessage = new StringBuilder(); 182 } 183 184 try 185 { 186 if ( searchTree( rootNode, 0, exclusions, generatedMessage ) ) 187 { 188 throw new EnforcerRuleException( message == null ? generatedMessage.toString() : message ); 189 } 190 } 191 catch ( InvalidVersionSpecificationException e ) 192 { 193 throw new EnforcerRuleException( "Error: Invalid version range.", e ); 194 } 195 196 } 197 198 private DependencyGraphBuilder createDependencyGraphBuilder() 199 throws ComponentLookupException 200 { 201 // CHECKSTYLE_OFF: LineLength 202 DefaultDependencyGraphBuilder builder = 203 (DefaultDependencyGraphBuilder) helper.getContainer().lookup( DependencyGraphBuilder.class.getCanonicalName(), 204 "default" ); 205 // CHECKSTYLE_ON: LineLength 206 207 builder.enableLogging( new ConsoleLogger( ConsoleLogger.LEVEL_DISABLED, "DefaultDependencyGraphBuilder" ) ); 208 209 return builder; 210 } 211 212}