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

net.aequologica.neo.shakuntala.dagr.DagComputer Maven / Gradle / Ivy

There is a newer version: 0.6.7
Show newest version
package net.aequologica.neo.shakuntala.dagr;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URI;
import java.nio.file.Path;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;

import org.apache.http.impl.EnglishReasonPhraseCatalog;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.model.Parent;
import org.apache.maven.project.MavenProject;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.collect.Maps;

import net.aequologica.neo.dagr.model.Dag;
import net.aequologica.neo.dagr.model.Dag.Link;
import net.aequologica.neo.dagr.model.Dag.Node;
import net.aequologica.neo.dagr.model.Dag.NodeValue;
import net.aequologica.neo.dagr.model.DagWriter;
import net.aequologica.neo.shakuntala.UUIDHolder;
import net.aequologica.neo.shakuntala.utils.ClientAsyncPatch;
import net.aequologica.neo.shakuntala.utils.ClientMultipartFormPost;

public class DagComputer {

    private static Logger log = LoggerFactory.getLogger(DagComputer.class);

    private final Path          basePath;
    private final String        dagNameParam;
    private       String        dagName;
    private       String        topLevelArtifacId;
    private final int           depth;
    private final String        contextPath;
    private final MavenSession  mavenSession;
    private final String        gitRemoteName;

    private Dag dag;

    public DagComputer(final String dagNameParam, final Path basePath, final MavenSession mavenSession, final int depth, final String contextPath, final String gitRemoteName) {
        this.basePath      = basePath;
        this.dagNameParam  = dagNameParam; //  look below for dagName calculation
        this.mavenSession  = mavenSession;
        this.depth         = depth;
        this.contextPath   = contextPath;
        this.gitRemoteName = gitRemoteName;
    }

    public DagComputer compute() {

        ProjectDependencyGraph projectDependencyGraph = mavenSession.getProjectDependencyGraph();
        if (projectDependencyGraph == null) {
            return this;
        }

        List sortedProjects = projectDependencyGraph.getSortedProjects();
        if (sortedProjects == null) {
            return this;
        }

        this.dag = new Dag();
        
        {
            TimeZone tz = TimeZone.getTimeZone("UTC");
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ");
            df.setTimeZone(tz);
            String nowAsISO = df.format(new Date());

            this.dag.setDate(nowAsISO);
        }

        Map nodeMap = new HashMap<>();
        Map linkMap = new HashMap<>();

        for (MavenProject mavenProject : sortedProjects) {
            Map.Entry nodeEntry = getNodeEntry(mavenProject, nodeMap);
            if (nodeEntry == null) {
                continue;
            }
            String nodeKey = nodeEntry.getKey();
            List downstreamProjects = projectDependencyGraph.getDownstreamProjects(mavenProject, false ); /* false = no transitive, only direct downstream projects */

            // special case for top maven project
            if (mavenProject.equals(mavenSession.getTopLevelProject())) {
                this.topLevelArtifacId = mavenProject.getArtifactId();
                // 111111111111111111 
                // dagname calculation, three cases:
                // dag dagName explicitely passed as parameter, i.e. coming from shakuntala config, if has precedence over anything else
                if (this.dagNameParam != null) {
                    this.dagName = this.dagNameParam;
                } else {
                   // is there a git repo ? dagName will be full path minus .git extension of remote url and branch
                    this.dagName = dagNameFromNodeValueScmAndBranch(nodeEntry.getValue());
                    if (this.dagName == null) {
                        // probably no git repo, or some parsing error : take file system directory dagName
                        this.dagName = this.basePath.getFileName().toString();
                    }
                }
                dag.setName(this.dagName);
                
                // 22222222222222222
                // ignore top level project when when it is not linked with any other project (if there is any)
                // ( ==> case of a module-only project used to bundle together otherwise unrelated projects)
                if (downstreamProjects.size() == 0 &&  // unrelated
                        sortedProjects.size() > 1  ) { // there are other projects
                    if (dag.getName() != null && !dag.getName().isEmpty()) {
                        // when topLevelArtifacId is null, dag writer will not patch the 'build-start' dag event, but instead will post the DAG
                        this.topLevelArtifacId = null; 
                    }
                    nodeMap.remove(nodeEntry.getKey());
                    continue;
                }
                
            }
            
            // int srcPosition = getKeyAndPosition(mavenProject, 1).getValue();
            for (MavenProject downstreamProject : downstreamProjects) {
                @SuppressWarnings("unused") // do not remove targetEntry, call to getNodeEntry creates the Node if it does not exist...
                Map.Entry targetEntry = getNodeEntry(downstreamProject, nodeMap);
                Entry keyAndPosition = getKeyAndPosition(downstreamProject);
                String destKey = keyAndPosition.getKey();
                // Integer destPosition = keyAndPosition.getValue();

                if (destKey == null || destKey.isEmpty() || nodeKey.equals(destKey)) {
                    continue;
                }

                boolean isParent = false;
                Parent parent = downstreamProject.getModel().getParent();
                if (   parent.getGroupId().equals(mavenProject.getGroupId()) 
                    && parent.getArtifactId().equals(mavenProject.getArtifactId())
                    && parent.getVersion().equals(mavenProject.getVersion())) {
                    isParent = true;
                }
                
                String linkKey = nodeKey + ">>>" + destKey;
                
                {
                    Link link = linkMap.get(linkKey);
                    if (link == null){
                        link = new Link();
                        link.setU(nodeKey);
                        link.setV(destKey);
                        Dag.LinkValue linkValue = new Dag.LinkValue();
                        linkValue.setLabel("");
                        link.setValue(linkValue);
                        if (isParent) {
                            link.setClazz("parent");
                            linkMap.put(linkKey, link);
                        } else {
                            linkMap.put(linkKey, link);
                        }
                    }
                }
                
            }
        }
        
        ArrayList allNodes = new ArrayList(nodeMap.values());
        
        if (depth>1) {
            Map parentMap = new HashMap<>();
            for (Node node : nodeMap.values()) {
                String id = node.getId();
                String shortName = id.substring(0, id.indexOf('_'));
                String parentKey = shortName+"_cluster";
                Node parentNode = parentMap.get(parentKey);
                if (parentNode == null) {
                    parentNode = new Node();
                    parentNode.setId(parentKey);
                    parentNode.setClazz("cluster");
                    NodeValue parentNodeValue = new Dag.NodeValue();
                    parentNodeValue.setLabel(shortName); 
                    parentNode.setValue(parentNodeValue);
                    parentMap.put(parentKey, parentNode);
                }
            }
            for (Node node : nodeMap.values()) {
                String id = node.getId();
                String shortName = id.substring(0, id.indexOf('_'));
                String parentKey = shortName+"_cluster";
                node.setParent(parentKey);
            }
            allNodes.addAll(parentMap.values());
        }

        dag.setNodes(allNodes);
        dag.setLinks(new ArrayList(linkMap.values()));

        return this;
    }

    static String dagNameFromNodeValueScmAndBranch(NodeValue nodeValue) {
        try {
            String scm = nodeValue.getScm();
            if (scm == null ) {
                return null;
            }
            scm = scm.trim();
            URI remote = URI.create(scm);
            String ret = remote.getPath();;
            if (ret.startsWith("/")) {
                ret = ret.substring(1);
            }
            if (ret.endsWith(".git")) {
                ret = ret.substring(0, ret.length()-4);
            }
            { // <-- avoid collusion on lastSlash var
                int lastSlash = ret.indexOf("/");
                if (lastSlash != -1) {
                    ret = ret.substring(0, lastSlash);
                }
            }
            ret = ret.replace("/", ".").replace("\\", ".");
            String branch = nodeValue.getBranch();
            if (branch == null) {
                return ret;
            }
            branch = branch.trim();
            if (branch.isEmpty()) {
                return ret;
            }
            { // <-- avoid collusion on lastSlash var
                int lastSlash = branch.lastIndexOf("/");
                if (lastSlash == -1) {
                    return ret + "." + branch;
                } 
                return ret + "." + branch.substring(lastSlash+1, branch.length());
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }
    
    public Map.Entry getNodeEntry(MavenProject mavenProject, Map nodeMap ) {
        Map.Entry nodeEntry = getNode(mavenProject);

        if (nodeEntry == null) {
            return null;
        }
        
        String nodeKey = nodeEntry.getKey();
        if (nodeKey == null || nodeKey.isEmpty()) {
            return null;
        }

        Node node = nodeMap.get(nodeKey);
        if (node == null){
            node = new Node();
            node.setId(nodeKey);
            node.setName(mavenProject.getArtifactId());
            node.setClazz("default");
            nodeMap.put(nodeKey, node);
        }

        NodeValue nodeValue = nodeEntry.getValue();
        if (nodeValue != null) {
            node.setValue(nodeValue);
        }
        
        return nodeEntry;
    }


    public void write(final URI dagURI, final String proxy) {
        
        if (this.dagName == null) {
            log.warn("[shakuntala] oooooooops, null dag dagName");
            return;
        }
        
        if (dagURI == null) {
            log.warn("[shakuntala] null dag uri: {}", dagURI);
            return;
        }
        
        dag.setName(this.dagName);
        
        try {
            String slimContextPath = getSlimContextPath(contextPath);
            
            if (this.topLevelArtifacId != null) {
                URI patchBuildstartEventURI = new URI(
                        dagURI.getScheme(), 
                        dagURI.getUserInfo(), 
                        dagURI.getHost(), 
                        dagURI.getPort(), 
                        Joiner.on('/').skipNulls().join(
                                "",
                                slimContextPath,
                                "dagr-api/v1/dags",
                                dag.getName(),
                                "nodes",
                                this.topLevelArtifacId,
                                "event/BUILD_STARTED"),
                        null,
                        null);
                ClientAsyncPatch.asyncPatch(patchBuildstartEventURI, UUIDHolder.getInstance().get(), proxy);           
            } else {
                DagWriter writer = new DagSerializer();
                File tmp = new File(System.getProperty("java.io.tmpdir"), dag.getName());
                tmp.deleteOnExit();
                String tmpFileCanonicalPathAsAString = tmp.getCanonicalPath().toString();
                Writer sw = new FileWriter(tmp);
                writer.write(sw, dag);
                int statusCode = ClientMultipartFormPost.post(tmpFileCanonicalPathAsAString, dagURI, proxy);
                String reasonPhrase = EnglishReasonPhraseCatalog.INSTANCE.getReason(statusCode, Locale.getDefault());
                if (statusCode/100 == 2) {
                    
                    URI getURI = new URI(
                            dagURI.getScheme(), 
                            dagURI.getUserInfo(), 
                            dagURI.getHost(), 
                            dagURI.getPort(), 
                            Joiner.on('/').skipNulls().join(
                                    "",
                                    slimContextPath,
                                    "dagr-api/v1/dags",
                                    dag.getName()),
                            null,
                            null);
                    log.info("[shakuntala] post ok (response: {} {}) ... \n[shakuntala] fetch url: \n[shakuntala]\t{}", statusCode, reasonPhrase, getURI);
                } else {
                    log.warn("[shakuntala] oooops POSTing '{}': {} {}", dag.getName(), statusCode, reasonPhrase);
                }
            }
        } catch (Exception e) {
            log.warn("[shakuntala] oooops POSTing '{}': {}", dag.getName(), e.getMessage());
        } 
    }

    public void writeResult(URI dagURI, String proxy, boolean result) {
        try {
            final String slimContextPath = getSlimContextPath(contextPath);
            final String event;
            if (result) {
                event = "BUILD_OK";
            } else {
                event = "BUILD_ERROR";
            }
            
            if (this.topLevelArtifacId != null) {
                URI patchBuildstartEventURI = new URI(
                        dagURI.getScheme(), 
                        dagURI.getUserInfo(), 
                        dagURI.getHost(), 
                        dagURI.getPort(), 
                        Joiner.on('/').skipNulls().join(
                                "",
                                slimContextPath,
                                "dagr-api/v1/dags",
                                dag.getName(),
                                "nodes",
                                this.topLevelArtifacId,
                                "event/"+event),
                        null,
                        null);
                ClientAsyncPatch.asyncPatch(patchBuildstartEventURI, UUIDHolder.getInstance().get(), proxy);           
            } 
        } catch (Exception e) {
            log.warn("[shakuntala] oooops POSTing '{}': {}", dag.getName(), e.getMessage());
        } 

    }

    private static String getSlimContextPath(final String contextPath) {
        String slimContextPath = contextPath;
        if (contextPath!= null) {
            // remove leading and trailing slashes
            slimContextPath = contextPath.replaceAll("^/+", "").replaceAll("/+$", "");
            if (slimContextPath.isEmpty()) {
                slimContextPath = null;
            }
        }
        return slimContextPath;
    }

    private String javascriptize(String str) {
        return str.replaceAll("\\W+", "_");
//        // cf. http://stackoverflow.com/questions/7440801/how-to-convert-arbitrary-string-to-java-identifier
//        try {
//            return Arrays.toString(str.getBytes("UTF-8")).replaceAll("\\D+", "_");
//        } catch (UnsupportedEncodingException e) {
//         // UTF-8 is always supported, but this catch is required by compiler
//            return null;
//        }
    }

    private Map.Entry getKeyAndPosition(MavenProject project) {
        File canonicalFile;
        try {
            canonicalFile = project.getBasedir().getCanonicalFile();
        } catch (IOException e) {
            return null;
        }

        Path relativized = basePath.relativize(canonicalFile.toPath());

        // return concatenated dagName segments until depth
        StringBuffer sb = new StringBuffer();
        int i;
        for (i=0; i getNode(MavenProject project) {

        Map.Entry keyAndPosition = getKeyAndPosition(project);

        String nodeKey = keyAndPosition.getKey();

        if (keyAndPosition.getValue() <= this.depth) {
            NodeValue nodeValue = new Dag.NodeValue();

            // dagName
            nodeValue.setLabel(project.getArtifactId() + " " + project.getVersion());

            // gav
            nodeValue.setGubrid(project.getArtifact().toString());

            // git 
            {
                GitWrapper gitWrapper = new GitWrapper(project.getBasedir(), this.gitRemoteName);

                // remote URL
                String remoteOriginUrl = gitWrapper.getRemoteOriginUrl();
                if (remoteOriginUrl != null ) {
                    nodeValue.setScm(remoteOriginUrl);
                }
                
                // branch
                String branch = gitWrapper.getBranch();
                if (branch != null) {
                    nodeValue.setBranch(branch);
                }
            }

            return Maps.immutableEntry(nodeKey, nodeValue);
        }
        return Maps.immutableEntry(nodeKey, null);
    }
    
    static class GitWrapper {
        private final Repository localRepo;
        private final String     gitRemoteName;
        
        public GitWrapper(File basedir, String gitRemoteName) {
            Repository localRepoTmp = null;
            try {
                File gitDir = new File(basedir, ".git");
                if (gitDir != null && gitDir.exists() && gitDir.isDirectory()) {
                    localRepoTmp = new FileRepository(gitDir);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                this.localRepo     = localRepoTmp;
                this.gitRemoteName = gitRemoteName;
                
            }
        }
        
        private String getBranch() {
            if (localRepo == null ) {
                return null;
            }
            try {
                return localRepo.getFullBranch();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        private String getRemoteOriginUrl() {
            if (localRepo == null ) {
                return null;
            }
            if (localRepo.getConfig() == null ) {
                return null;
            }
            try {
                return localRepo.getConfig().getString("remote", gitRemoteName, "url");
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy