net.aequologica.neo.shakuntala.dagr.DagComputer Maven / Gradle / Ivy
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