001/*
002 * Copyright 2017-2023 original authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.micronaut.maven.enforcer;
017
018import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
019import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
020import org.apache.maven.model.Dependency;
021import org.apache.maven.model.Resource;
022import org.apache.maven.project.MavenProject;
023import org.codehaus.plexus.util.xml.XmlUtil;
024import org.eclipse.aether.util.artifact.JavaScopes;
025
026import javax.inject.Inject;
027import javax.inject.Named;
028import java.io.File;
029import java.io.IOException;
030import java.io.StringReader;
031import java.io.StringWriter;
032import java.util.Arrays;
033
034/**
035 * Enforcer rule to check that, when the application has YAML configuration, the SnakeYAML library is present.
036 *
037 * @author Álvaro Sánchez-Mariscal
038 * @since 4.0.0
039 */
040@Named("checkSnakeYaml")
041public class CheckSnakeYaml extends AbstractEnforcerRule {
042
043    private final MavenProject project;
044    private final Dependency snakeYaml;
045
046    @Inject
047    public CheckSnakeYaml(MavenProject project) {
048        this.project = project;
049        this.snakeYaml = new Dependency();
050        snakeYaml.setGroupId("org.yaml");
051        snakeYaml.setArtifactId("snakeyaml");
052        snakeYaml.setScope(JavaScopes.RUNTIME);
053    }
054
055    @Override
056    public void execute() throws EnforcerRuleException {
057        if (hasYamlConfiguration() && !hasSnakeYaml()) {
058            String message = "YAML configuration file detected, but SnakeYAML is not on the runtime classpath. Make sure to add the following dependency:" +
059                System.lineSeparator();
060
061            message += toXml(snakeYaml);
062            throw new EnforcerRuleException(message);
063        }
064    }
065
066    private boolean hasYamlConfiguration() {
067        return this.project.getResources().stream().anyMatch(this::hasYamlConfiguration);
068    }
069
070    private boolean hasSnakeYaml() {
071        return project.getDependencies().stream()
072            .anyMatch(d -> d.getGroupId().equals(snakeYaml.getGroupId()) && d.getArtifactId().equals(snakeYaml.getArtifactId()));
073    }
074
075    private boolean hasYamlConfiguration(Resource resource) {
076        File[] files = new File(resource.getDirectory()).listFiles();
077        if (files != null) {
078            return Arrays.stream(files).anyMatch(this::isYamlConfigurationFile);
079        } else {
080            return false;
081        }
082    }
083
084    private boolean isYamlConfigurationFile(File f) {
085        String name = f.getName().toLowerCase();
086        return name.startsWith("application") && (name.endsWith(".yml") || name.endsWith(".yaml"));
087    }
088
089    private String toXml(Dependency dependency) {
090        StringReader dependencyXml = new StringReader("<dependency>" +
091            "<groupId>" + dependency.getGroupId() + "</groupId>" +
092            "<artifactId>" + dependency.getArtifactId() + "</artifactId>" +
093            "<scope>" + dependency.getScope() + "</scope>" +
094            "</dependency>");
095        StringWriter result = new StringWriter();
096        try {
097            XmlUtil.prettyFormat(dependencyXml, result);
098            return result.toString();
099        } catch (IOException e) {
100            return dependencyXml.toString();
101        }
102    }
103
104    @Override
105    public String toString() {
106        return "CheckSnakeYaml";
107    }
108}