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.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.nio.file.Files;
028import java.util.Objects;
029
030import org.apache.maven.enforcer.rule.api.AbstractEnforcerRuleConfigProvider;
031import org.apache.maven.enforcer.rule.api.EnforcerRuleError;
032import org.apache.maven.enforcer.rules.utils.ExpressionEvaluator;
033import org.apache.maven.plugin.MojoExecution;
034import org.codehaus.plexus.util.xml.Xpp3Dom;
035import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
036import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
037
038/**
039 * An enforcer rule that will provide rules configuration from an external resource.
040 *
041 * @author <a href="mailto:gastaldi@apache.org">George Gastaldi</a>
042 * @since 3.2.0
043 */
044@Named("externalRules")
045public final class ExternalRules extends AbstractEnforcerRuleConfigProvider {
046    private static final String LOCATION_PREFIX_CLASSPATH = "classpath:";
047
048    /**
049     * The external rules location. If it starts with "classpath:", the resource is read from the classpath.
050     * Otherwise, it is handled as a filesystem path, either absolute, or relative to <code>${project.basedir}</code>
051     */
052    private String location;
053
054    private final MojoExecution mojoExecution;
055
056    private final ExpressionEvaluator evaluator;
057
058    @Inject
059    public ExternalRules(MojoExecution mojoExecution, ExpressionEvaluator evaluator) {
060        this.mojoExecution = Objects.requireNonNull(mojoExecution);
061        this.evaluator = Objects.requireNonNull(evaluator);
062    }
063
064    public void setLocation(String location) {
065        this.location = location;
066    }
067
068    @Override
069    public Xpp3Dom getRulesConfig() throws EnforcerRuleError {
070
071        try (InputStream descriptorStream = resolveDescriptor()) {
072            Xpp3Dom enforcerRules = Xpp3DomBuilder.build(descriptorStream, "UTF-8");
073            if (enforcerRules.getChildCount() == 1 && "enforcer".equals(enforcerRules.getName())) {
074                return enforcerRules.getChild(0);
075            } else {
076                throw new EnforcerRuleError("Enforcer rules configuration not found in: " + location);
077            }
078        } catch (IOException | XmlPullParserException e) {
079            throw new EnforcerRuleError(e);
080        }
081    }
082
083    private InputStream resolveDescriptor() throws EnforcerRuleError {
084        InputStream descriptorStream;
085        if (location != null) {
086            if (location.startsWith(LOCATION_PREFIX_CLASSPATH)) {
087                String classpathLocation = location.substring(LOCATION_PREFIX_CLASSPATH.length());
088                getLog().debug("Read rules form classpath location: " + classpathLocation);
089                ClassLoader classRealm = mojoExecution.getMojoDescriptor().getRealm();
090                descriptorStream = classRealm.getResourceAsStream(classpathLocation);
091                if (descriptorStream == null) {
092                    throw new EnforcerRuleError("Location '" + classpathLocation + "' not found in classpath");
093                }
094            } else {
095                File descriptorFile = evaluator.alignToBaseDirectory(new File(location));
096                getLog().debug("Read rules form file location: " + descriptorFile);
097                try {
098                    descriptorStream = Files.newInputStream(descriptorFile.toPath());
099                } catch (IOException e) {
100                    throw new EnforcerRuleError("Could not read descriptor in " + descriptorFile, e);
101                }
102            }
103        } else {
104            throw new EnforcerRuleError("No location provided");
105        }
106        return descriptorStream;
107    }
108
109    @Override
110    public String toString() {
111        return String.format("ExternalRules[location=%s]", location);
112    }
113}