All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.finos.legend.sdlc.versions.VersionQualifiedPackageMojo Maven / Gradle / Ivy

// Copyright 2021 Goldman Sachs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.finos.legend.sdlc.versions;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.finos.legend.sdlc.domain.model.entity.Entity;
import org.finos.legend.sdlc.serialization.EntityLoader;
import org.finos.legend.sdlc.serialization.EntitySerializer;
import org.finos.legend.sdlc.serialization.EntitySerializers;
import org.finos.legend.sdlc.tools.entity.EntityPaths;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.lang.model.SourceVersion;

@Mojo(name = "version-qualify-packages", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class VersionQualifiedPackageMojo extends AbstractMojo
{
    @Parameter(required = true)
    private File[] entitySourceDirectories;

    @Parameter(defaultValue = "${project.build.outputDirectory}")
    private File outputDirectory;

    @Parameter(defaultValue = "${project}", readonly = true)
    private MavenProject mavenProject;

    @Parameter
    private String versionAlias;

    @Parameter(defaultValue = "true")
    private boolean useParentInfoIfPresent;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException
    {
        long start = System.nanoTime();

        getLog().info("entity input directories: " + Arrays.toString(this.entitySourceDirectories));
        getLog().info("output directory: " + this.outputDirectory);
        getLog().info("use parent info if present: " + this.useParentInfoIfPresent);
        getLog().info("groupId: " + findGroupId());
        getLog().info("artifactId: " + findArtifactId());
        getLog().info("version: " + findVersion());
        if (this.versionAlias != null)
        {
            getLog().info("version alias: \"" + this.versionAlias + "\"");
        }

        try
        {
            validateProjectInfo();
        }
        catch (MojoFailureException e)
        {
            getLog().error(String.format("Error generating entities with version qualified packages: %s (%.9fs)", e.getMessage(), (System.nanoTime() - start) / 1_000_000_000.0), e);
            throw e;
        }

        try
        {
            getLog().info("Generating entities with version qualified packages");
            List transformedEntities = transformEntities();
            serializeEntities(transformedEntities);
            getLog().info(String.format("Done (%.9fs)", (System.nanoTime() - start) / 1_000_000_000.0));
        }
        catch (MojoExecutionException | MojoFailureException e)
        {
            getLog().error(String.format("Error generating entities with version qualified packages: %s (%.9fs)", e.getMessage(), (System.nanoTime() - start) / 1_000_000_000.0), e);
            throw e;
        }
        catch (Exception e)
        {
            String message = "Error generating entities with version qualified packages: " + e.getMessage();
            getLog().error(String.format("%s (%.9fs)", message, (System.nanoTime() - start) / 1_000_000_000.0), e);
            throw new MojoExecutionException(message, e);
        }
    }

    private void validateProjectInfo() throws MojoFailureException
    {
        if (this.versionAlias == null)
        {
            String version = findVersion();
            if ((version == null) || version.isEmpty())
            {
                throw new MojoFailureException("A non-empty version is required");
            }
        }
        else if (!this.versionAlias.matches("\\w++"))
        {
            throw new MojoFailureException("Invalid version alias: \"" + this.versionAlias + "\"");
        }

        String groupId = findGroupId();
        if ((groupId == null) || groupId.isEmpty() || !SourceVersion.isName(groupId))
        {
            StringBuilder builder = new StringBuilder("A valid groupId is required, found: ");
            if (groupId == null)
            {
                builder.append("null");
            }
            else
            {
                builder.append('"').append(groupId).append('"');
            }
            throw new MojoFailureException(builder.toString());
        }

        String artifactId = findArtifactId();
        if ((artifactId == null) || artifactId.isEmpty() || !artifactId.matches("^\\w++(-\\w++)*+$"))
        {
            StringBuilder builder = new StringBuilder("A valid artifactId is required, found: ");
            if (artifactId == null)
            {
                builder.append("null");
            }
            else
            {
                builder.append('"').append(artifactId).append('"');
            }
            throw new MojoFailureException(builder.toString());
        }
    }

    private String findVersion()
    {
        return findMavenProjectInfo(MavenProject::getVersion);
    }

    private String findGroupId()
    {
        return findMavenProjectInfo(MavenProject::getGroupId);
    }

    private String findArtifactId()
    {
        return findMavenProjectInfo(MavenProject::getArtifactId);
    }

    private  T findMavenProjectInfo(Function infoAccessor)
    {
        if (this.useParentInfoIfPresent)
        {
            MavenProject parent = this.mavenProject.getParent();
            if (parent != null)
            {
                T value = infoAccessor.apply(parent);
                if (value != null)
                {
                    return value;
                }
            }
        }
        return infoAccessor.apply(this.mavenProject);
    }

    private List transformEntities() throws Exception
    {
        long transformStart = System.nanoTime();
        getLog().info("Qualifying entity packages by version");

        EntityPathTransformer transformer = EntityPathTransformer.newTransformer(getPathTransformationFunction());
        try (EntityLoader entityLoader = EntityLoader.newEntityLoader(this.entitySourceDirectories))
        {
            entityLoader.getAllEntities().forEach(transformer::addEntity);
        }
        List transformedEntities = transformer.transformEntities();
        getLog().info(String.format("Done qualifying packages by version for %,d entities (%.9fs)", transformedEntities.size(), (System.nanoTime() - transformStart) / 1_000_000_000.0));
        return transformedEntities;
    }

    private void serializeEntities(List entities) throws IOException
    {
        long serializeStart = System.nanoTime();
        getLog().info(String.format("Serializing %,d entities to %s", entities.size(), this.outputDirectory));
        Path outputDirPath = this.outputDirectory.toPath();
        Path entitiesDir = outputDirPath.resolve("entities");
        EntitySerializer entitySerializer = EntitySerializers.getDefaultJsonSerializer();
        for (Entity entity : entities)
        {
            entitySerializer.serializeToFile(entity, entitiesDir);
        }
        getLog().info(String.format("Done serializing %,d entities to %s (%.9fs)", entities.size(), this.outputDirectory, (System.nanoTime() - serializeStart) / 1_000_000_000.0));
    }

    private Function getPathTransformationFunction() throws Exception
    {
        Map pathMap = new HashMap<>();

        // main project
        String projectPrefix = getPackagePrefix(findGroupId(), findArtifactId(), findVersion(), this.versionAlias);
        try (EntityLoader entityLoader = EntityLoader.newEntityLoader(this.entitySourceDirectories))
        {
            entityLoader.getAllEntities().forEach(e -> forEachPackageableElementPath(e, path -> pathMap.computeIfAbsent(path, projectPrefix::concat)));
        }

        // dependencies
        List dependencies = this.mavenProject.getDependencies();
        if ((dependencies != null) && !dependencies.isEmpty())
        {
            Set dependencyKeys = dependencies.stream().map(DependencyArtifactKey::new).collect(Collectors.toSet());
            this.mavenProject.getArtifacts()
                    .stream()
                    .filter(a -> dependencyKeys.contains(new DependencyArtifactKey(a)))
                    .forEach(artifact ->
                    {
                        Pattern prefixPattern = Pattern.compile(appendVersionPackage(appendGroupIdPackage(new StringBuilder("^\\Q"), artifact.getGroupId()).append("::\\E\\w+\\Q::"), artifact.getVersion()).append("::\\E").toString());
                        try (EntityLoader entityLoader = EntityLoader.newEntityLoader(artifact.getFile()))
                        {
                            entityLoader.getAllEntities().forEach(e -> forEachPackageableElementPath(e, path ->
                            {
                                Matcher matcher = prefixPattern.matcher(path);
                                if (matcher.find())
                                {
                                    String pathWithoutPrefix = path.substring(matcher.end());
                                    pathMap.putIfAbsent(pathWithoutPrefix, path);
                                }
                            }));
                        }
                        catch (Exception e)
                        {
                            throw new RuntimeException("Error reading from artifact " + artifact.getId(), e);
                        }
                    });
        }

        return getLog().isWarnEnabled() ?
                path ->
                {
                    String transformed = pathMap.get(path);
                    if (transformed == null)
                    {
                        getLog().warn("No transformation found for: " + path);
                        return path;
                    }
                    return transformed;
                } :
                path -> pathMap.getOrDefault(path, path);
    }

    private static void forEachPackageableElementPath(Entity entity, Consumer pathConsumer)
    {
        forEachPackageableElementPath(entity.getContent(), pathConsumer);
    }

    private static void forEachPackageableElementPath(Object value, Consumer pathConsumer)
    {
        if (value instanceof Map)
        {
            Map map = (Map) value;
            Object pkg = map.get("package");
            if (pkg instanceof String)
            {
                Object name = map.get("name");
                if (name instanceof String)
                {
                    String path = pkg + EntityPaths.PACKAGE_SEPARATOR + name;
                    pathConsumer.accept(path);
                }
            }
            map.values().forEach(v -> forEachPackageableElementPath(v, pathConsumer));
        }
        else if (value instanceof Iterable)
        {
            ((Iterable) value).forEach(v -> forEachPackageableElementPath(v, pathConsumer));
        }
    }

    private static String getPackagePrefix(String groupId, String artifactId, String version, String versionAlias)
    {
        StringBuilder builder = new StringBuilder(groupId.length() + artifactId.length() + ((versionAlias == null) ? version : versionAlias).length() + 16);

        // groupId
        appendGroupIdPackage(builder, groupId).append(EntityPaths.PACKAGE_SEPARATOR);

        // artifactId
        appendArtifactIdPackage(builder, artifactId).append(EntityPaths.PACKAGE_SEPARATOR);

        // version/versionAlias
        if (versionAlias == null)
        {
            appendVersionPackage(builder, version);
        }
        else
        {
            builder.append(versionAlias);
        }
        builder.append(EntityPaths.PACKAGE_SEPARATOR);

        return builder.toString();
    }

    private static StringBuilder appendGroupIdPackage(StringBuilder builder, String groupId)
    {
        int start = 0;
        for (int end = groupId.indexOf('.'); end != -1; start = end + 1, end = groupId.indexOf('.', start))
        {
            builder.append(groupId, start, end).append(EntityPaths.PACKAGE_SEPARATOR);
        }
        return builder.append(groupId, start, groupId.length());
    }

    private static StringBuilder appendArtifactIdPackage(StringBuilder builder, String artifactId)
    {
        builder.append(artifactId);
        for (int i = builder.length() - artifactId.length(); i < builder.length(); i++)
        {
            if (!isValidPackageNameCharacter(builder.charAt(i)))
            {
                builder.setCharAt(i, '_');
            }
        }
        return builder;
    }

    private static StringBuilder appendVersionPackage(StringBuilder builder, String version)
    {
        return version.matches("\\d++(\\.\\d++)*+") ?
                builder.append('v').append(version.replace('.', '_')) :
                builder.append("vX_X_X");
    }

    private static boolean isValidPackageNameCharacter(char c)
    {
        return (c == '_') ||
                ((c >= 'a') && (c <= 'z')) ||
                ((c >= 'A') && (c <= 'Z')) ||
                ((c >= '0') && (c <= '9'));
    }

    private static final class DependencyArtifactKey
    {
        private final String groupId;
        private final String artifactId;

        private DependencyArtifactKey(String groupId, String artifactId)
        {
            this.groupId = groupId;
            this.artifactId = artifactId;
        }

        private DependencyArtifactKey(Dependency dependency)
        {
            this(dependency.getGroupId(), dependency.getArtifactId());
        }

        private DependencyArtifactKey(Artifact artifact)
        {
            this(artifact.getGroupId(), artifact.getArtifactId());
        }

        @Override
        public boolean equals(Object other)
        {
            if (this == other)
            {
                return true;
            }

            if (!(other instanceof DependencyArtifactKey))
            {
                return false;
            }

            DependencyArtifactKey otherKey = (DependencyArtifactKey) other;
            return Objects.equals(this.groupId, otherKey.groupId) && Objects.equals(this.artifactId, otherKey.artifactId);
        }

        @Override
        public int hashCode()
        {
            return Objects.hashCode(this.groupId) + (827 * Objects.hashCode(this.artifactId));
        }

        @Override
        public String toString()
        {
            return this.groupId + ":" + this.artifactId;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy