package gov.nasa.jpl.mbee.mdk.mms.validation; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.nomagic.ci.persistence.IPrimaryProject; import com.nomagic.magicdraw.core.Application; import com.nomagic.magicdraw.core.Project; import com.nomagic.magicdraw.core.ProjectUtilities; import com.nomagic.magicdraw.core.project.ProjectDescriptor; import com.nomagic.magicdraw.core.project.ProjectDescriptorsFactory; import com.nomagic.magicdraw.esi.EsiUtils; import com.nomagic.task.ProgressStatus; import gov.nasa.jpl.mbee.mdk.api.incubating.MDKConstants; import gov.nasa.jpl.mbee.mdk.http.ServerException; import gov.nasa.jpl.mbee.mdk.json.JacksonUtils; import gov.nasa.jpl.mbee.mdk.mms.MMSUtils; import gov.nasa.jpl.mbee.mdk.mms.actions.CommitBranchAction; import gov.nasa.jpl.mbee.mdk.mms.json.JsonPatchFunction; import gov.nasa.jpl.mbee.mdk.util.Pair; import gov.nasa.jpl.mbee.mdk.util.Utils; import gov.nasa.jpl.mbee.mdk.validation.ValidationRule; import gov.nasa.jpl.mbee.mdk.validation.ValidationRuleViolation; import gov.nasa.jpl.mbee.mdk.validation.ValidationSuite; import gov.nasa.jpl.mbee.mdk.validation.ViolationSeverity; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.text.NumberFormat; import java.util.*; public class BranchValidator { private final Project project; private boolean errors; private ValidationSuite validationSuite = new ValidationSuite("structure"); // private ValidationRule twcMissingBranchValidationRule = new ValidationRule("Missing in Client", "Branch shall exist in TWC if it exists in MMS.", ViolationSeverity.WARNING); private ValidationRule mmsMissingBranchValidationRule = new ValidationRule("Missing on Server", "Branch shall exist in MMS if it exists in Teamwork Cloud.", ViolationSeverity.WARNING); private ValidationRule branchEquivalenceValidationRule = new ValidationRule("Branch Equivalence", "Branch shall be represented in MagicDraw and MMS equivalently.", ViolationSeverity.WARNING); public BranchValidator(Project project) { this.project = project; // validationSuite.addValidationRule(twcMissingBranchValidationRule); validationSuite.addValidationRule(mmsMissingBranchValidationRule); validationSuite.addValidationRule(branchEquivalenceValidationRule); } public void validate(ProgressStatus progressStatus, boolean allBranches) { if (project == null) { return; } IPrimaryProject primaryProject = project.getPrimaryProject(); if (!ProjectUtilities.isRemote(primaryProject)) { return; } if (EsiUtils.getLoggedUserName() == null) { errors = true; Utils.guilog("[INFO] You need to be logged in to Teamwork Cloud first to do branch validation. Aborting."); return; } String currentBranch = EsiUtils.getCurrentBranch(primaryProject).getName(); if (currentBranch.equals("trunk")) { currentBranch = "master"; } Map<String, Pair<EsiUtils.EsiBranchInfo, ObjectNode>> clientBranches = new HashMap<>(); Map<String, ObjectNode> serverBranches = new HashMap<>(); if (progressStatus != null) { progressStatus.setDescription("Mapping Teamwork Cloud branches"); progressStatus.setIndeterminate(true); } Collection<EsiUtils.EsiBranchInfo> targetBranches = null; if (allBranches) { try { ProjectDescriptor projectDescriptor = ProjectDescriptorsFactory.createAnyRemoteProjectDescriptor(project); targetBranches = EsiUtils.getBranches(projectDescriptor); } catch (Exception e) { e.printStackTrace(); } if (targetBranches == null || targetBranches.isEmpty()) { return; } } else { targetBranches = new ArrayList<>(1); targetBranches.add(EsiUtils.getCurrentBranch(primaryProject)); } for (EsiUtils.EsiBranchInfo branch : targetBranches) { ObjectNode branchJson = getRefObjectNode(project, branch, null); if (branchJson == null) { continue; } branchJson.remove(MDKConstants.PARENT_REF_ID_KEY); JsonNode value; String entryKey; if ((value = branchJson.get(MDKConstants.ID_KEY)) != null && value.isTextual()) { entryKey = branchJson.get(MDKConstants.ID_KEY).asText(); if (allBranches || entryKey.equals(currentBranch)) { clientBranches.put(entryKey, new Pair<>(branch, branchJson)); } } } if (progressStatus != null) { progressStatus.setDescription("Mapping MMS branches"); } URIBuilder requestUri = MMSUtils.getServiceProjectsRefsUri(project); if (requestUri == null) { errors = true; Application.getInstance().getGUILog().log("[ERROR] Unable to get MMS URL. Branch validation cancelled."); return; } if (!allBranches) { requestUri.setPath(requestUri.getPath() + "/" + currentBranch); } try { HttpRequestBase request = MMSUtils.buildRequest(MMSUtils.HttpRequestType.GET, requestUri); File responseFile = MMSUtils.sendMMSRequest(project, request); ObjectNode response; try (JsonParser jsonParser = JacksonUtils.getJsonFactory().createParser(responseFile)) { response = JacksonUtils.parseJsonObject(jsonParser); } JsonNode refsArray, value; if ((refsArray = response.get("refs")) != null && refsArray.isArray()) { for (JsonNode refJson : refsArray) { if (refJson.isObject()) { ObjectNode refObjectNode = (ObjectNode) refJson; refObjectNode.remove(MDKConstants.PARENT_REF_ID_KEY); String entryKey; if ((value = refObjectNode.get(MDKConstants.ID_KEY)) != null && value.isTextual()) { entryKey = refObjectNode.get(MDKConstants.ID_KEY).asText(); if (allBranches || entryKey.equals(currentBranch)) { serverBranches.put(entryKey, refObjectNode); } } } } } } catch (IOException | URISyntaxException | ServerException e) { errors = true; e.printStackTrace(); Application.getInstance().getGUILog().log("[ERROR] Exception occurred while getting MMS branches. Branch validation cancelled. Reason: " + e.getMessage()); return; } Set<String> keySet = new HashSet<>(); keySet.addAll(clientBranches.keySet()); keySet.addAll(serverBranches.keySet()); if (progressStatus != null) { progressStatus.setDescription("Generating validation results for " + NumberFormat.getInstance().format(keySet.size()) + " branch" + (keySet.size() != 1 ? "es" : "")); progressStatus.setIndeterminate(false); progressStatus.setMax(keySet.size()); progressStatus.setCurrent(0); } for (String key : keySet) { // TODO @DONBOT 3.0.1 remove this check/skip for master branch after master is updatable if (key.equals("master")) { continue; } Pair<EsiUtils.EsiBranchInfo, ObjectNode> clientBranch = clientBranches.get(key); ObjectNode serverBranch = serverBranches.get(key); if (clientBranch == null) { //TODO @donbot 3.0.1 add support for importing MMS branch into TWC // ValidationRuleViolation v = new ValidationRuleViolation(project.getPrimaryModel(), "[BRANCH MISSING ON MMS] The MMS branch \"" + key + "\" does not have a corresponding Teamwork Cloud branch."); // // add actions here // twcMissingBranchValidationRule.addViolation(v); } else if (serverBranch == null) { ValidationRuleViolation v = new ValidationRuleViolation(project.getPrimaryModel(), "[BRANCH MISSING ON MMS] The Teamwork Cloud branch \"" + clientBranch.getKey().getName() + "\" does not have a corresponding MMS branch."); v.addAction(new CommitBranchAction(key, project, clientBranch.getKey(), false)); v.addAction(new CommitBranchAction(key, project, clientBranch.getKey(), true)); mmsMissingBranchValidationRule.addViolation(v); } else { JsonNode diff = JsonPatchFunction.getInstance().apply(clientBranch.getValue(), serverBranch); if (diff == null || diff.size() == 0) { continue; } ValidationRuleViolation v = new ValidationRuleViolation(project.getPrimaryModel(), "[BRANCH NOT EQUIVALENT] The Teamwork Cloud branch \"" + clientBranch.getKey().getName() + "\" is not equivalent to the corresponding MMS branch."); v.addAction(new CommitBranchAction(key, project, clientBranch.getKey(), false)); v.addAction(new CommitBranchAction(key, project, clientBranch.getKey(), true)); branchEquivalenceValidationRule.addViolation(v); } if (progressStatus != null) { progressStatus.increase(); } } } public static ObjectNode getRefObjectNode(Project project, EsiUtils.EsiBranchInfo branchInfo, String parentRefId) { ObjectNode refObjectNode = JacksonUtils.getObjectMapper().createObjectNode(); String name = branchInfo.getName(); if (name.equals("master")) { return null; } if (name.equals("trunk")) { name = "master"; } String id = !name.equals("master") ? branchInfo.getID().toString() : "master"; ProjectDescriptor projectDescriptor = EsiUtils.getDescriptorByBranchID(ProjectDescriptorsFactory.createAnyRemoteProjectDescriptor(project), branchInfo.getID()); refObjectNode.put(MDKConstants.ID_KEY, id); refObjectNode.put(MDKConstants.NAME_KEY, name); refObjectNode.put(MDKConstants.URI_KEY, projectDescriptor.getURI().toString()); // TODO unlink this from "master" when we support non-head branching refObjectNode.put(MDKConstants.PARENT_REF_ID_KEY, parentRefId); return refObjectNode; } public boolean hasErrors() { return this.errors; } public void showWindow() { List<ValidationSuite> vss = new ArrayList<>(); vss.add(validationSuite); if (validationSuite.hasErrors()) { Utils.displayValidationWindow(project, vss, "Branch Differences"); } } public ValidationSuite getValidationSuite() { return validationSuite; } }