package gov.nasa.jpl.mbee.mdk.mms.actions; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.nomagic.actions.ActionsCategory; import com.nomagic.actions.NMAction; 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.openapi.uml.ModelElementsManager; import com.nomagic.magicdraw.openapi.uml.ReadOnlyElementException; import com.nomagic.magicdraw.openapi.uml.SessionManager; import com.nomagic.task.ProgressStatus; import com.nomagic.task.RunnableWithProgress; import com.nomagic.ui.ProgressStatusRunner; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element; import gov.nasa.jpl.mbee.mdk.actions.ClipboardAction; import gov.nasa.jpl.mbee.mdk.api.incubating.MDKConstants; import gov.nasa.jpl.mbee.mdk.api.incubating.convert.Converters; import gov.nasa.jpl.mbee.mdk.emf.EMFBulkImporter; import gov.nasa.jpl.mbee.mdk.json.JacksonUtils; import gov.nasa.jpl.mbee.mdk.mms.json.JsonPatchFunction; import gov.nasa.jpl.mbee.mdk.util.Changelog; import gov.nasa.jpl.mbee.mdk.util.Utils; import gov.nasa.jpl.mbee.mdk.mms.sync.delta.SyncElement; import gov.nasa.jpl.mbee.mdk.mms.sync.local.LocalSyncProjectEventListenerAdapter; import gov.nasa.jpl.mbee.mdk.mms.sync.local.LocalSyncTransactionCommitListener; import gov.nasa.jpl.mbee.mdk.validation.*; import gov.nasa.jpl.mbee.mdk.util.Pair; import javax.annotation.CheckForNull; import java.awt.event.ActionEvent; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Map; /** * Created by igomes on 9/27/16. */ public class UpdateClientElementAction extends RuleViolationAction implements AnnotationAction, IRuleViolationAction, RunnableWithProgress { private static final String NAME = "Update Element from MMS"; private final String sysmlId; private final Element element; private final ObjectNode elementObjectNode; private final Project project; private final Changelog<String, ObjectNode> failedChangelog = new Changelog<>(); private ValidationSuite validationSuite = new ValidationSuite("Update Changelog"); private ValidationRule editableValidationRule = new ValidationRule("Element Editability", "The element to be updated shall be editable.", ViolationSeverity.WARNING), failedChangeValidationRule = new ValidationRule("Failed Change", "The element shall not fail to change.", ViolationSeverity.ERROR), equivalentElementValidationRule = new ValidationRule("Element Equivalency", "The changed element shall be equivalent to the source element.", ViolationSeverity.WARNING), successfulChangeValidationRule = new ValidationRule("Successful Change", "The element shall successfully change.", ViolationSeverity.INFO), deletionOnSuccessValidationRule = new ValidationRule("Deletion on Success", "The element to be deleted shall only be deleted if all elements to be created/updated are successfully imported.", ViolationSeverity.WARNING); { validationSuite.addValidationRule(editableValidationRule); validationSuite.addValidationRule(failedChangeValidationRule); validationSuite.addValidationRule(equivalentElementValidationRule); validationSuite.addValidationRule(successfulChangeValidationRule); validationSuite.addValidationRule(deletionOnSuccessValidationRule); } private Collection<ObjectNode> elementsToUpdate; private Collection<String> elementsToDelete; public UpdateClientElementAction(Project project) { this(null, null, null, project); } public UpdateClientElementAction(String sysmlId, Element element, ObjectNode elementObjectNode, Project project) { super(UpdateClientElementAction.class.getSimpleName(), NAME, null, null); this.sysmlId = sysmlId; this.element = element; this.elementObjectNode = elementObjectNode; this.project = project; } @Override public void execute(Collection<Annotation> annotations) { elementsToUpdate = new ArrayList<>(annotations.size()); elementsToDelete = new ArrayList<>(annotations.size()); for (Annotation annotation : annotations) { for (NMAction action : annotation.getActions()) { if (action instanceof UpdateClientElementAction) { ObjectNode objectNode = ((UpdateClientElementAction) action).getElementObjectNode(); if (objectNode != null) { elementsToUpdate.add(objectNode); } else { elementsToDelete.add(sysmlId); } break; } } } ProgressStatusRunner.runWithProgressStatus(this, NAME, true, 0); } @Override public boolean canExecute(Collection<Annotation> annotations) { return true; } public Element getElement() { return element; } public ObjectNode getElementObjectNode() { return elementObjectNode; } public Changelog<String, ObjectNode> getFailedChangelog() { return failedChangelog; } @Override public void actionPerformed(@CheckForNull ActionEvent actionEvent) { elementsToUpdate = new ArrayList<>(1); elementsToDelete = new ArrayList<>(1); if (elementObjectNode != null) { elementsToUpdate.add(elementObjectNode); } else { elementsToDelete.add(sysmlId); } ProgressStatusRunner.runWithProgressStatus(this, NAME, true, 0); } @Override public void run(ProgressStatus progressStatus) { validationSuite.getValidationRules().forEach(validationRule -> validationRule.getViolations().clear()); LocalSyncTransactionCommitListener localSyncTransactionCommitListener = LocalSyncProjectEventListenerAdapter.getProjectMapping(project).getLocalSyncTransactionCommitListener(); if ((elementsToUpdate == null || elementsToUpdate.isEmpty()) && (elementsToDelete == null || elementsToDelete.isEmpty())) { Application.getInstance().getGUILog().log("[INFO] No MMS changes to update locally."); return; } if (elementsToUpdate != null && !elementsToUpdate.isEmpty()) { Application.getInstance().getGUILog().log("[INFO] Attempting to create/update " + NumberFormat.getInstance().format(elementsToUpdate.size()) + " element" + (elementsToUpdate.size() != 1 ? "s" : "") + " locally."); if (localSyncTransactionCommitListener != null) { localSyncTransactionCommitListener.setDisabled(true); } EMFBulkImporter emfBulkImporter = new EMFBulkImporter(NAME) { @Override public void onSuccess() { if (elementsToDelete != null && !elementsToDelete.isEmpty()) { Application.getInstance().getGUILog().log("[INFO] Attempting to delete " + NumberFormat.getInstance().format(elementsToDelete.size()) + " element" + (elementsToDelete.size() != 1 ? "s" : "") + " locally."); if (localSyncTransactionCommitListener != null) { localSyncTransactionCommitListener.setDisabled(true); } if (!SessionManager.getInstance().isSessionCreated()) { SessionManager.getInstance().createSession(UpdateClientElementAction.class.getName() + " Deletes"); } for (String id : elementsToDelete) { Exception exception = null; Element element = Converters.getIdToElementConverter().apply(id, project); if (element == null) { continue; } try { ModelElementsManager.getInstance().removeElement(element); } catch (ReadOnlyElementException | RuntimeException e) { exception = e; } if (exception == null) { successfulChangeValidationRule.addViolation(project.getPrimaryModel(), "[" + Changelog.ChangeType.DELETED.name() + "] " + element.getHumanName()); } else { (exception instanceof ReadOnlyElementException ? editableValidationRule : failedChangeValidationRule).addViolation(element, "[DELETE FAILED] " + exception.getMessage()); failedChangelog.addChange(id, null, Changelog.ChangeType.DELETED); } } if (localSyncTransactionCommitListener != null) { localSyncTransactionCommitListener.setDisabled(false); } if (SessionManager.getInstance().isSessionCreated()) { SessionManager.getInstance().closeSession(); } } } @Override public void onFailure() { if (elementsToDelete != null) { for (String id : elementsToDelete) { Element element = Converters.getIdToElementConverter().apply(id, project); if (element == null) { continue; } deletionOnSuccessValidationRule.addViolation(element, "[DELETE SKIPPED] " + deletionOnSuccessValidationRule.getDescription()); failedChangelog.addChange(id, null, Changelog.ChangeType.DELETED); } } } }; Changelog<String, Pair<Element, ObjectNode>> changelog = emfBulkImporter.apply(elementsToUpdate, project, progressStatus); for (Map.Entry<Pair<Element, ObjectNode>, Exception> entry : emfBulkImporter.getFailedElementMap().entrySet()) { Element element = entry.getKey().getKey(); ObjectNode objectNode = entry.getKey().getValue(); Exception exception = entry.getValue(); JsonNode sysmlIdJsonNode = objectNode.get(MDKConstants.ID_KEY); if (sysmlIdJsonNode == null || !sysmlIdJsonNode.isTextual()) { continue; } String sysmlId = sysmlIdJsonNode.asText(); // TODO Abstract this stuff to a converter @donbot String name = null; if (element == null) { JsonNode nameJsonNode = objectNode.get(MDKConstants.NAME_KEY); if (nameJsonNode != null && nameJsonNode.isTextual()) { name = nameJsonNode.asText("<>"); } if (name == null || name.isEmpty()) { name = "<>"; } } ValidationRuleViolation validationRuleViolation = new ValidationRuleViolation(element != null ? element : project.getPrimaryModel(), "[" + (element != null ? "UPDATE" : "CREATE") + " FAILED]" + (element == null ? " " + objectNode.get(MDKConstants.TYPE_KEY).asText("Element") + " " + name + " : " + sysmlId : "") + (element == null && exception != null ? " -" : "") + (exception != null ? " " + (exception instanceof ReadOnlyElementException ? "Element is not editable." : exception.getMessage()) : "")); ActionsCategory copyActionsCategory = new ActionsCategory("COPY", "Copy..."); copyActionsCategory.setNested(true); validationRuleViolation.addAction(copyActionsCategory); copyActionsCategory.addAction(new ClipboardAction("ID", sysmlId)); JsonNode diff = null; if (element != null) { copyActionsCategory.addAction(new ClipboardAction("Element Hyperlink", "mdel://" + element.getID())); ObjectNode elementObjectNode = Converters.getElementToJsonConverter().apply(element, project); if (elementObjectNode != null) { try { copyActionsCategory.addAction(new ClipboardAction("Local JSON", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(elementObjectNode))); } catch (JsonProcessingException ignored) { } diff = JsonPatchFunction.getInstance().apply(elementObjectNode, objectNode); } } try { copyActionsCategory.addAction(new ClipboardAction("MMS JSON", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(objectNode))); } catch (JsonProcessingException ignored) { } if (diff != null) { try { copyActionsCategory.addAction(new ClipboardAction("Diff", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(diff))); } catch (JsonProcessingException ignored) { } } (exception instanceof ReadOnlyElementException ? editableValidationRule : failedChangeValidationRule).addViolation(validationRuleViolation); failedChangelog.addChange(sysmlId, objectNode, element != null ? Changelog.ChangeType.UPDATED : Changelog.ChangeType.CREATED); } for (Map.Entry<Element, ObjectNode> entry : emfBulkImporter.getNonEquivalentElements().entrySet()) { Element element = entry.getKey(); String sysmlId = element.getLocalID(); ObjectNode clientElementObjectNode = Converters.getElementToJsonConverter().apply(element, project); ObjectNode serverElementObjectNode = entry.getValue(); JsonNode diff = JsonPatchFunction.getInstance().apply(clientElementObjectNode, serverElementObjectNode); ValidationRuleViolation validationRuleViolation = new ValidationRuleViolation(entry.getKey(), "[NOT EQUIVALENT]"); ActionsCategory copyActionsCategory = new ActionsCategory("COPY", "Copy..."); copyActionsCategory.setNested(true); validationRuleViolation.addAction(copyActionsCategory); copyActionsCategory.addAction(new ClipboardAction("ID", sysmlId)); copyActionsCategory.addAction(new ClipboardAction("Element Hyperlink", "mdel://" + element.getID())); try { copyActionsCategory.addAction(new ClipboardAction("Local JSON", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(clientElementObjectNode))); } catch (JsonProcessingException ignored) { } try { copyActionsCategory.addAction(new ClipboardAction("MMS JSON", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(serverElementObjectNode))); } catch (JsonProcessingException ignored) { } try { copyActionsCategory.addAction(new ClipboardAction("Diff", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(diff))); } catch (JsonProcessingException ignored) { } equivalentElementValidationRule.addViolation(validationRuleViolation); } for (Changelog.ChangeType changeType : Changelog.ChangeType.values()) { for (Map.Entry<String, Pair<Element, ObjectNode>> entry : changelog.get(changeType).entrySet()) { successfulChangeValidationRule.addViolation(new ValidationRuleViolation(entry.getValue().getKey(), "Source: [" + SyncElement.Type.MMS.name() + "] | Type: [" + changeType.name() + "] | Target: [" + SyncElement.Type.LOCAL.name() + "]")); } } if (localSyncTransactionCommitListener != null) { localSyncTransactionCommitListener.setDisabled(false); } } if (validationSuite.hasErrors()) { Utils.displayValidationWindow(project, validationSuite, validationSuite.getName()); } } public Collection<ObjectNode> getElementsToUpdate() { return elementsToUpdate; } public void setElementsToUpdate(Collection<ObjectNode> elementsToUpdate) { this.elementsToUpdate = elementsToUpdate; } public Collection<String> getElementsToDelete() { return elementsToDelete; } public void setElementsToDelete(Collection<String> elementsToDelete) { this.elementsToDelete = elementsToDelete; } }