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.Collection;
026import java.util.Collections;
027import java.util.List;
028import java.util.Objects;
029import java.util.Optional;
030import java.util.function.Function;
031import java.util.stream.Collectors;
032
033import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
034import org.apache.maven.execution.MavenSession;
035import org.apache.maven.model.Model;
036import org.apache.maven.model.Repository;
037import org.apache.maven.project.MavenProject;
038import org.apache.maven.project.ProjectBuildingRequest;
039import org.apache.maven.settings.Profile;
040import org.apache.maven.settings.RepositoryBase;
041import org.codehaus.plexus.util.StringUtils;
042
043/**
044 * This rule checks that this pom or its parents don't define a repository.
045 *
046 * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
047 */
048@Named("requireNoRepositories")
049public final class RequireNoRepositories extends AbstractStandardEnforcerRule {
050
051    private static final String VERSION = " version:";
052
053    /**
054     * Whether to ban non-plugin repositories. By default they are banned.
055     *
056     * @see #setBanRepositories(boolean)
057     */
058    private boolean banRepositories = true;
059
060    /**
061     * Whether to ban plugin repositories. By default they are banned.
062     *
063     * @see #setBanPluginRepositories(boolean)
064     */
065    private boolean banPluginRepositories = true;
066
067    /**
068     * Specify explicitly allowed non-plugin repositories. This is a list of ids.
069     *
070     * @see #setAllowedRepositories(List)
071     */
072    private List<String> allowedRepositories;
073
074    /**
075     * Specify explicitly allowed plugin repositories. This is a list of ids.
076     *
077     * @see #setAllowedPluginRepositories(List)
078     */
079    private List<String> allowedPluginRepositories;
080
081    /**
082     * Whether to allow repositories which only resolve snapshots. By default they are banned.
083     *
084     * @see #setAllowSnapshotRepositories(boolean)
085     */
086    private boolean allowSnapshotRepositories = false;
087
088    /**
089     * Whether to allow plugin repositories which only resolve snapshots. By default they are banned.
090     *
091     * @see #setAllowSnapshotPluginRepositories(boolean)
092     */
093    private boolean allowSnapshotPluginRepositories = false;
094
095    private final MavenSession session;
096
097    @Inject
098    public RequireNoRepositories(MavenSession session) {
099        this.session = Objects.requireNonNull(session);
100    }
101
102    public void setBanRepositories(boolean banRepositories) {
103        this.banRepositories = banRepositories;
104    }
105
106    public void setBanPluginRepositories(boolean banPluginRepositories) {
107        this.banPluginRepositories = banPluginRepositories;
108    }
109
110    public void setAllowedRepositories(List<String> allowedRepositories) {
111        this.allowedRepositories = allowedRepositories;
112    }
113
114    public void setAllowedPluginRepositories(List<String> allowedPluginRepositories) {
115        this.allowedPluginRepositories = allowedPluginRepositories;
116    }
117
118    public void setAllowSnapshotRepositories(boolean allowSnapshotRepositories) {
119        this.allowSnapshotRepositories = allowSnapshotRepositories;
120    }
121
122    public void setAllowSnapshotPluginRepositories(boolean allowSnapshotPluginRepositories) {
123        this.allowSnapshotPluginRepositories = allowSnapshotPluginRepositories;
124    }
125
126    @Override
127    public void execute() throws EnforcerRuleException {
128
129        // Maven 4 Model contains repositories defined in settings.xml
130        // As workaround we exclude repositories defined in settings.xml
131        // https://issues.apache.org/jira/browse/MNG-7228
132        if (banRepositories) {
133            Collection<String> reposIdsFromSettings = getRepoIdsFromSettings(Profile::getRepositories);
134            if (!reposIdsFromSettings.isEmpty()) {
135                getLog().debug(() -> "Allow repositories from settings: " + reposIdsFromSettings);
136            }
137
138            allowedRepositories = Optional.ofNullable(allowedRepositories).orElseGet(ArrayList::new);
139            allowedRepositories.addAll(reposIdsFromSettings);
140        }
141
142        if (banPluginRepositories) {
143            Collection<String> reposIdsFromSettings = getRepoIdsFromSettings(Profile::getPluginRepositories);
144            if (!reposIdsFromSettings.isEmpty()) {
145                getLog().debug(() -> "Allow plugin repositories from settings: " + reposIdsFromSettings);
146            }
147
148            allowedPluginRepositories =
149                    Optional.ofNullable(allowedPluginRepositories).orElseGet(ArrayList::new);
150            allowedPluginRepositories.addAll(reposIdsFromSettings);
151        }
152
153        List<MavenProject> sortedProjects = session.getProjectDependencyGraph().getSortedProjects();
154
155        List<Model> models = new ArrayList<>();
156        for (MavenProject mavenProject : sortedProjects) {
157            getLog().debug("Scanning project: " + mavenProject.getGroupId() + ":" + mavenProject.getArtifactId()
158                    + VERSION + mavenProject.getVersion());
159            models.add(mavenProject.getOriginalModel());
160        }
161
162        List<Model> badModels = new ArrayList<>();
163
164        StringBuilder newMsg = new StringBuilder();
165        newMsg.append("Some poms have repositories defined:" + System.lineSeparator());
166
167        for (Model model : models) {
168            if (banRepositories) {
169                List<Repository> repos = model.getRepositories();
170                if (repos != null && !repos.isEmpty()) {
171                    List<String> bannedRepos =
172                            findBannedRepositories(repos, allowedRepositories, allowSnapshotRepositories);
173                    if (!bannedRepos.isEmpty()) {
174                        badModels.add(model);
175                        newMsg.append(model.getGroupId() + ":" + model.getArtifactId() + VERSION + model.getVersion()
176                                + " has repositories " + bannedRepos);
177                    }
178                }
179            }
180            if (banPluginRepositories) {
181                List<Repository> repos = model.getPluginRepositories();
182                if (repos != null && !repos.isEmpty()) {
183                    List<String> bannedRepos =
184                            findBannedRepositories(repos, allowedPluginRepositories, allowSnapshotPluginRepositories);
185                    if (!bannedRepos.isEmpty()) {
186                        badModels.add(model);
187                        newMsg.append(model.getGroupId() + ":" + model.getArtifactId() + VERSION + model.getVersion()
188                                + " has plugin repositories " + bannedRepos);
189                    }
190                }
191            }
192        }
193
194        // if anything was found, log it then append the
195        // optional message.
196        if (!badModels.isEmpty()) {
197            if (StringUtils.isNotEmpty(getMessage())) {
198                newMsg.append(getMessage());
199            }
200
201            throw new EnforcerRuleException(newMsg.toString());
202        }
203    }
204
205    private Collection<String> getRepoIdsFromSettings(
206            Function<Profile, List<org.apache.maven.settings.Repository>> getRepositoriesFunc) {
207
208        List<String> activeProfileIds = Optional.ofNullable(session.getProjectBuildingRequest())
209                .map(ProjectBuildingRequest::getActiveProfileIds)
210                .orElse(Collections.emptyList());
211
212        List<String> inactiveProfileIds = Optional.ofNullable(session.getProjectBuildingRequest())
213                .map(ProjectBuildingRequest::getInactiveProfileIds)
214                .orElse(Collections.emptyList());
215
216        return session.getSettings().getProfiles().stream()
217                .filter(p -> activeProfileIds.contains(p.getId()))
218                .filter(p -> !inactiveProfileIds.contains(p.getId()))
219                .map(getRepositoriesFunc)
220                .flatMap(Collection::stream)
221                .map(RepositoryBase::getId)
222                .collect(Collectors.toSet());
223    }
224
225    /**
226     * @param repos          all repositories, never {@code null}
227     * @param allowedRepos   allowed repositories, never {@code null}
228     * @param allowSnapshots
229     * @return List of banned repositoreis.
230     */
231    private static List<String> findBannedRepositories(
232            List<Repository> repos, List<String> allowedRepos, boolean allowSnapshots) {
233        List<String> bannedRepos = new ArrayList<>(allowedRepos.size());
234        for (Repository r : repos) {
235            if (!allowedRepos.contains(r.getId())) {
236                if (!allowSnapshots
237                        || r.getReleases() == null
238                        || r.getReleases().isEnabled()) {
239                    // if we are not allowing snapshots and this repo is enabled for releases
240                    // it is banned.  We don't care whether it is enabled for snapshots
241                    // if you define a repo and don't enable it for anything, then we have nothing
242                    // to worry about
243                    bannedRepos.add(r.getId());
244                }
245            }
246        }
247        return bannedRepos;
248    }
249
250    @Override
251    public String toString() {
252        return String.format(
253                "RequireNoRepositories[message=%s, banRepositories=%b, allowSnapshotRepositories=%b, allowedRepositories=%s, "
254                        + "banPluginRepositories=%b, allowSnapshotPluginRepositories=%b, allowedPluginRepositories=%s]",
255                getMessage(),
256                banRepositories,
257                allowSnapshotRepositories,
258                allowedRepositories,
259                banPluginRepositories,
260                allowSnapshotPluginRepositories,
261                allowedPluginRepositories);
262    }
263}