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;
020
021import javax.inject.Inject;
022import javax.inject.Named;
023
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.List;
027import java.util.Objects;
028
029import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
030import org.apache.maven.enforcer.rules.utils.ArtifactMatcher;
031import org.apache.maven.model.Dependency;
032import org.apache.maven.model.DependencyManagement;
033import org.apache.maven.project.MavenProject;
034
035/**
036 * This rule bans all scope values except for {@code import} from dependencies within the dependency management.
037 * There is a configuration option to ignore certain dependencies in this check.
038 */
039@Named("banDependencyManagementScope")
040public final class BanDependencyManagementScope extends AbstractStandardEnforcerRule {
041
042    /**
043     * Specify the dependencies that will be ignored. This can be a list of artifacts in the format
044     * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific
045     * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0'). Version is a
046     * string representing standard Maven version range. Empty patterns will be ignored.
047     *
048     * @see {@link #setExcludes(List)}
049     */
050    private List<String> excludes = null;
051
052    /**
053     * If {@code true} the dependencyManagement from imported dependencyManagement and parent pom's is checked as well,
054     * otherwise only the local dependencyManagement defined in the current project's pom.xml.
055     */
056    private boolean checkEffectivePom = false;
057
058    private final MavenProject project;
059
060    @Inject
061    public BanDependencyManagementScope(MavenProject project) {
062        this.project = Objects.requireNonNull(project);
063    }
064
065    @Override
066    public void execute() throws EnforcerRuleException {
067        // only evaluate local depMgmt, without taking into account inheritance and interpolation
068        DependencyManagement depMgmt = checkEffectivePom
069                ? project.getModel().getDependencyManagement()
070                : project.getOriginalModel().getDependencyManagement();
071        if (depMgmt != null && depMgmt.getDependencies() != null) {
072            List<Dependency> violatingDependencies = getViolatingDependencies(depMgmt);
073            if (!violatingDependencies.isEmpty()) {
074                String message = getMessage();
075                StringBuilder buf = new StringBuilder();
076                if (message == null) {
077                    message = "Scope other than 'import' is not allowed in 'dependencyManagement'";
078                }
079                buf.append(message + System.lineSeparator());
080                for (Dependency violatingDependency : violatingDependencies) {
081                    buf.append(getErrorMessage(project, violatingDependency));
082                }
083                throw new EnforcerRuleException(buf.toString());
084            }
085        }
086    }
087
088    protected List<Dependency> getViolatingDependencies(DependencyManagement depMgmt) {
089        final ArtifactMatcher excludesMatcher;
090        if (excludes != null) {
091            excludesMatcher = new ArtifactMatcher(excludes, Collections.emptyList());
092        } else {
093            excludesMatcher = null;
094        }
095        List<Dependency> violatingDependencies = new ArrayList<>();
096        for (Dependency dependency : depMgmt.getDependencies()) {
097            if (dependency.getScope() != null && !"import".equals(dependency.getScope())) {
098                if (excludesMatcher != null && excludesMatcher.match(dependency)) {
099                    getLog().debug("Skipping excluded dependency " + dependency + " with scope "
100                            + dependency.getScope());
101                    continue;
102                }
103                violatingDependencies.add(dependency);
104            }
105        }
106        return violatingDependencies;
107    }
108
109    private static CharSequence getErrorMessage(MavenProject project, Dependency violatingDependency) {
110        return "Banned scope '" + violatingDependency.getScope() + "' used on dependency '"
111                + violatingDependency.getManagementKey() + "' @ "
112                + formatLocation(project, violatingDependency.getLocation(""))
113                + System.lineSeparator();
114    }
115
116    public void setExcludes(List<String> theExcludes) {
117        this.excludes = theExcludes;
118    }
119
120    @Override
121    public String toString() {
122        return String.format(
123                "BanDependencyManagementScope[message=%s, excludes=%s, checkEffectivePom=%b]",
124                getMessage(), excludes, checkEffectivePom);
125    }
126}