package gov.nasa.jpl.mbee.mdk.mms.actions; /** * Created by ablack on 3/16/17. */ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.nomagic.ci.persistence.versioning.IVersionDescriptor; import com.nomagic.esi.core.msg.info.impl.BranchInfoImpl; import com.nomagic.esi.core.msg.info.impl.CommitInfoImpl; import com.nomagic.magicdraw.annotation.Annotation; import com.nomagic.magicdraw.annotation.AnnotationAction; import com.nomagic.magicdraw.core.Application; import com.nomagic.magicdraw.core.Project; 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 com.nomagic.task.RunnableWithProgress; import com.nomagic.ui.ProgressStatusRunner; 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.sync.manual.ManualSyncRunner; import gov.nasa.jpl.mbee.mdk.mms.validation.BranchValidator; import gov.nasa.jpl.mbee.mdk.validation.IRuleViolationAction; import gov.nasa.jpl.mbee.mdk.validation.RuleViolationAction; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ContentType; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.URISyntaxException; import java.text.NumberFormat; import java.util.*; import java.util.stream.Collectors; public class CommitBranchAction extends RuleViolationAction implements AnnotationAction, IRuleViolationAction, RunnableWithProgress { public static final String DEFAULT_ID = CommitBranchAction.class.getSimpleName(); public static final String VALIDATE_MODEL_DEFAULT_ID = DEFAULT_ID + "_Validate_Model"; private final Project project; private final EsiUtils.EsiBranchInfo branchInfo; private final String branchName; private final boolean validateModel; public CommitBranchAction(String branchName, Project project, EsiUtils.EsiBranchInfo branchInfo) { this(branchName, project, branchInfo, false); } public CommitBranchAction(String branchName, Project project, EsiUtils.EsiBranchInfo branchInfo, boolean validateModel) { super(validateModel ? VALIDATE_MODEL_DEFAULT_ID : DEFAULT_ID, "Commit Branch" + (validateModel ? " and Validate Model" : ""), null, null); this.branchName = branchName; this.project = project; this.branchInfo = branchInfo; this.validateModel = validateModel; } @Override public boolean canExecute(Collection<Annotation> arg0) { return false; } @Override public void execute(Collection<Annotation> annos) { // do nothing } @Override public void actionPerformed(ActionEvent e) { commitAction(); } public void commitAction() { ProgressStatusRunner.runWithProgressStatus(this, "Commit Branch", true, 0); } @Override public void run(ProgressStatus progressStatus) { BranchInfoImpl branchInfoImpl = toBranchInfoImpl(branchInfo); if (branchInfoImpl == null) { Application.getInstance().getGUILog().log("[ERROR] Current branch not found. Branch commit aborted."); return; } ProjectDescriptor projectDescriptor = ProjectDescriptorsFactory.createAnyRemoteProjectDescriptor(project); ProjectDescriptor branchDescriptor = EsiUtils.getDescriptorByBranchID(projectDescriptor, branchInfoImpl.getID()); List<CommitInfoImpl> branchCommits = toCommitInfoImpls(EsiUtils.getVersions(branchDescriptor)); long startRevisionCommitId = branchInfoImpl.getStartRevision(); CommitInfoImpl startRevisionCommitInfo = branchCommits.stream().filter(version -> startRevisionCommitId == version.getID()).findAny().orElse(null); if (startRevisionCommitInfo == null) { Application.getInstance().getGUILog().log("[ERROR] Unable to find start revision commit info. Branch commit aborted."); return; } UUID parentBranchUuid = startRevisionCommitInfo.getBranchID(); EsiUtils.EsiBranchInfo parentBranchInfo = EsiUtils.getBranches(projectDescriptor).stream().filter(branch -> branch.getID().equals(parentBranchUuid)).findAny().orElse(null); if (parentBranchInfo == null) { Application.getInstance().getGUILog().log("[ERROR] Unable to find parent branch info. Branch commit aborted."); return; } String parentBranchId = parentBranchUuid.toString(); String parentBranchName = parentBranchInfo.getName(); if (parentBranchInfo.getName().equals("trunk")) { parentBranchId = "master"; parentBranchName = "master"; } int parentCommitsBehind = 0; long latestRevisionCommitId = branchInfoImpl.getLatestRevision(); CommitInfoImpl latestRevisionCommitInfo = branchCommits.stream().filter(version -> latestRevisionCommitId == version.getID()).findAny().orElse(null); if (latestRevisionCommitInfo == null) { Application.getInstance().getGUILog().log("[ERROR] Unable to find latest revision commit info of current branch. Branch commit aborted."); return; } CommitInfoImpl commit = latestRevisionCommitInfo; while (commit != null && commit.getID() != startRevisionCommitId) { parentCommitsBehind++; long directParent = commit.getDirectParent(); commit = branchCommits.stream().filter(version -> directParent == version.getID()).findAny().orElse(null); } // This is needed since on branch init TWC automatically creates a commit. This presumably doesn't make any element changes, so it shouldn't be necessary to manually sync. parentCommitsBehind--; int parentCommitsAhead = 0; ProjectDescriptor parentBranchDescriptor = EsiUtils.getDescriptorByBranchID(projectDescriptor, parentBranchUuid); List<CommitInfoImpl> parentBranchCommits = toCommitInfoImpls(EsiUtils.getVersions(parentBranchDescriptor)); BranchInfoImpl parentBranchInfoImpl = toBranchInfoImpl(parentBranchInfo); if (parentBranchInfoImpl == null) { Application.getInstance().getGUILog().log("[ERROR] Parent branch not found. Branch commit aborted."); } long parentLatestRevisionCommitId = parentBranchInfoImpl.getLatestRevision(); CommitInfoImpl parentLatestRevisionCommitInfo = parentBranchCommits.stream().filter(version -> parentLatestRevisionCommitId == version.getID()).findAny().orElse(null); if (parentLatestRevisionCommitInfo == null) { Application.getInstance().getGUILog().log("[ERROR] Unable to find latest revision commit info of parent branch. Branch commit aborted."); return; } commit = parentLatestRevisionCommitInfo; while (commit != null && commit.getID() != startRevisionCommitId) { parentCommitsAhead++; long directParent = commit.getDirectParent(); commit = parentBranchCommits.stream().filter(version -> directParent == version.getID()).findAny().orElse(null); } JsonNode parentBranchJsonNode = null; URIBuilder requestUri = MMSUtils.getServiceProjectsRefsUri(project); if (requestUri == null) { Application.getInstance().getGUILog().log("[ERROR] Unable to get MMS refs URL. Branch commit aborted."); return; } requestUri.setPath(requestUri.getPath() + "/" + parentBranchId); 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 (entryKey.equals(parentBranchId)) { parentBranchJsonNode = refObjectNode; } } } } } } catch (IOException | URISyntaxException | ServerException e) { e.printStackTrace(); Application.getInstance().getGUILog().log("[ERROR] Exception occurred while getting MMS branches. Branch commit aborted. Reason: " + e.getMessage()); return; } if (parentBranchJsonNode == null) { Application.getInstance().getGUILog().log("[ERROR] Parent branch (" + parentBranchName + ") does not exist on MMS. Please commit that one first. Branch commit aborted."); return; } requestUri = MMSUtils.getServiceProjectsRefsUri(project); if (requestUri == null) { Application.getInstance().getGUILog().log("[ERROR] Unable to get MMS refs url. Branch commit aborted."); return; } Collection<ObjectNode> refsNodes = new LinkedList<>(); ObjectNode branchNode = BranchValidator.getRefObjectNode(project, branchInfo, parentBranchId); refsNodes.add(branchNode); if (parentCommitsBehind > 0 || parentCommitsAhead > 0) { Application.getInstance().getGUILog().log("[INFO] The parent branch (" + parentBranchName + ") is " + (parentCommitsBehind > 0 ? NumberFormat.getInstance().format(parentCommitsBehind) + " commit" + (parentCommitsBehind != 1 ? "s" : "") + "behind " : "") + (parentCommitsBehind > 0 && parentCommitsAhead > 0 ? "and " : "") + (parentCommitsAhead > 0 ? NumberFormat.getInstance().format(parentCommitsAhead) + " commit" + (parentCommitsAhead != 1 ? "s" : "") + " ahead of " : "") + "the branch being created (" + branchInfo.getName() + "). " + "It is highly recommended that you manually sync the newly created branch to ensure parity."); } try { File sendFile = MMSUtils.createEntityFile(this.getClass(), ContentType.APPLICATION_JSON, refsNodes, MMSUtils.JsonBlobType.REF); HttpRequestBase request = MMSUtils.buildRequest(MMSUtils.HttpRequestType.POST, requestUri, sendFile, ContentType.APPLICATION_JSON); MMSUtils.sendMMSRequest(project, request); } catch (IOException | URISyntaxException | ServerException e) { Application.getInstance().getGUILog().log("[ERROR] Exception occurred while posting branch. Branch commit aborted. Reason: " + e.getMessage()); e.printStackTrace(); return; } Application.getInstance().getGUILog().log("[INFO] Branch creation for \"" + branchInfo.getName() + "\" on MMS initiated."); if (validateModel) { //RunnableWithProgress temp = new ManualSyncActionRunner<>(CommitClientElementAction.class, Collections.singletonList(project.getPrimaryModel()), project, -1); RunnableWithProgress temp = new ManualSyncRunner(Collections.singletonList(project.getPrimaryModel()), project, -1); ProgressStatusRunner.runWithProgressStatus(temp, "Model Initialization", true, 0); } } public BranchInfoImpl toBranchInfoImpl(EsiUtils.EsiBranchInfo esiBranchInfo) { try { Field field = Arrays.stream(esiBranchInfo.getClass().getDeclaredFields()).filter(f -> f.getType().isAssignableFrom(BranchInfoImpl.class)).findAny().orElse(null); if (field == null) { Application.getInstance().getGUILog().log("[ERROR] Branch field not found. Branch commit aborted."); return null; } field.setAccessible(true); Object o = field.get(esiBranchInfo); if (!(o instanceof BranchInfoImpl)) { Application.getInstance().getGUILog().log("[ERROR] Branch is of the wrong type. Branch commit aborted."); return null; } return (BranchInfoImpl) o; } catch (ReflectiveOperationException e) { e.printStackTrace(); Application.getInstance().getGUILog().log("[ERROR] Reflection on branch failed. Branch commit aborted."); return null; } } public List<CommitInfoImpl> toCommitInfoImpls(List<IVersionDescriptor> iVersionDescriptors) { return iVersionDescriptors.stream().map(version -> { try { Field field = Arrays.stream(version.getClass().getDeclaredFields()).filter(f -> f.getType().isAssignableFrom(CommitInfoImpl.class)).findAny().orElse(null); if (field == null) { return null; } field.setAccessible(true); Object o = field.get(version); if (!(o instanceof CommitInfoImpl)) { return null; } return (CommitInfoImpl) o; } catch (ReflectiveOperationException e) { e.printStackTrace(); return null; } }).filter(Objects::nonNull).collect(Collectors.toList()); } @Override public void updateState() { String currentBranch = EsiUtils.getCurrentBranch(project.getPrimaryProject()).getName(); if (currentBranch.equals("trunk")) { currentBranch = "master"; } setEnabled(!validateModel || branchName.equals(currentBranch)); } }