package gov.nasa.jpl.mbee.mdk.generator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.nomagic.magicdraw.core.Application;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.core.ProjectUtilities;
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.uml2.ext.magicdraw.classes.mdkernel.*;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Package;
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.docgen.docbook.DBBook;
import gov.nasa.jpl.mbee.mdk.emf.EMFImporter;
import gov.nasa.jpl.mbee.mdk.http.ServerException;
import gov.nasa.jpl.mbee.mdk.json.ImportException;
import gov.nasa.jpl.mbee.mdk.json.JacksonUtils;
import gov.nasa.jpl.mbee.mdk.mms.MMSUtils;
import gov.nasa.jpl.mbee.mdk.mms.json.JsonPatchFunction;
import gov.nasa.jpl.mbee.mdk.mms.json.JsonEquivalencePredicate;
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.mms.sync.queue.OutputQueue;
import gov.nasa.jpl.mbee.mdk.mms.sync.queue.Request;
import gov.nasa.jpl.mbee.mdk.mms.validation.ImageValidator;
import gov.nasa.jpl.mbee.mdk.model.DocBookOutputVisitor;
import gov.nasa.jpl.mbee.mdk.model.Document;
import gov.nasa.jpl.mbee.mdk.util.Changelog;
import gov.nasa.jpl.mbee.mdk.util.MDUtils;
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 gov.nasa.jpl.mbee.mdk.viewedit.DBAlfrescoVisitor;
import gov.nasa.jpl.mbee.mdk.viewedit.ViewHierarchyVisitor;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.json.simple.JSONArray;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author dlam
*/
public class ViewPresentationGenerator implements RunnableWithProgress {
private ValidationSuite suite = new ValidationSuite("View Instance Generation");
private ValidationRule uneditableContent = new ValidationRule("Uneditable", "uneditable", ViolationSeverity.ERROR);
private ValidationRule uneditableElements = new ValidationRule("Uneditable elements", "uneditable elements", ViolationSeverity.WARNING);
private ValidationRule viewInProject = new ValidationRule("viewInProject", "viewInProject", ViolationSeverity.WARNING);
private ValidationRule updateFailed = new ValidationRule("updateFailed", "updateFailed", ViolationSeverity.ERROR);
private ValidationRule viewDoesNotExist = new ValidationRule("viewDoesNotExist", "viewDoesNotExist", ViolationSeverity.ERROR);
private PresentationElementUtils presentationElementUtils;
private final Set<Element> rootViews;
private final Project project;
private final boolean recurse;
private final List<ValidationSuite> vss = new ArrayList<>();
private final Set<Element> processedElements;
private boolean failure;
public ViewPresentationGenerator(Set<Element> rootViews, Project project, boolean recurse) {
this(rootViews, project, recurse, null, null);
}
public ViewPresentationGenerator(Set<Element> rootViews, Project project, boolean recurse, PresentationElementUtils presentationElementUtils) {
this(rootViews, project, recurse, presentationElementUtils, null);
}
public ViewPresentationGenerator(Set<Element> rootViews, Project project, boolean recurse, PresentationElementUtils presentationElementUtils, Set<Element> processedElements) {
if (rootViews == null || rootViews.isEmpty()) {
throw new IllegalArgumentException();
}
this.rootViews = rootViews;
this.project = project;
this.processedElements = processedElements != null ? processedElements : new HashSet<>(rootViews.size());
this.recurse = recurse;
this.presentationElementUtils = presentationElementUtils;
if (this.presentationElementUtils == null) {
this.presentationElementUtils = new PresentationElementUtils();
}
suite.addValidationRule(uneditableContent);
suite.addValidationRule(viewInProject);
suite.addValidationRule(updateFailed);
suite.addValidationRule(uneditableElements);
suite.addValidationRule(viewDoesNotExist);
vss.add(suite);
}
@Override
public void run(ProgressStatus progressStatus) {
progressStatus.init("Initializing", 6);
// Ensure no existing session so we have full control of whether to close/cancel further sessions.
// no wild sessions spotted as of 06/05/16
if (SessionManager.getInstance().isSessionCreated(project)) {
SessionManager.getInstance().closeSession(project);
}
Map<String, Pair<ObjectNode, InstanceSpecification>> instanceSpecificationMap = new LinkedHashMap<>();
Map<String, Pair<ObjectNode, Slot>> slotMap = new LinkedHashMap<>();
Map<String, ViewMapping> viewMap = new LinkedHashMap<>();
for (Element rootView : rootViews) {
if (MDUtils.isDeveloperMode()) {
//Application.getInstance().getGUILog().log("Generating " + rootView.getHumanName() + " (" + rootView.getLocalID() + ").");
}
// STAGE 1: Calculating view structure
progressStatus.setDescription("Calculating view structure");
progressStatus.setCurrent(1);
DocumentValidator dv = new DocumentValidator(rootView);
dv.validateDocument();
if (dv.isFatal()) {
dv.printErrors(false);
return;
}
// first run a local generation of the view model to get the current model view structure
DocumentGenerator dg = new DocumentGenerator(rootView, dv, null, false);
Document dge = dg.parseDocument(true, recurse, false);
new PostProcessor().process(dge);
DocBookOutputVisitor docBookOutputVisitor = new DocBookOutputVisitor(true);
dge.accept(docBookOutputVisitor);
DBBook book = docBookOutputVisitor.getBook();
// TODO ??
if (book == null) {
return;
}
// Use HierarchyVisitor to find all views to download related elements (instances, constraint, etc.)
ViewHierarchyVisitor viewHierarchyVisitor = new ViewHierarchyVisitor();
dge.accept(viewHierarchyVisitor);
for (Element view : viewHierarchyVisitor.getView2ViewElements().keySet()) {
if (processedElements.contains(view)) {
Application.getInstance().getGUILog().log("Detected duplicate view reference. Skipping generation for " + Converters.getElementToIdConverter().apply(view) + ".");
continue;
}
ViewMapping viewMapping = viewMap.containsKey(Converters.getElementToIdConverter().apply(view)) ?
viewMap.get(Converters.getElementToIdConverter().apply(view)) : new ViewMapping();
viewMapping.setElement(view);
viewMapping.setDbBook(book);
viewMap.put(Converters.getElementToIdConverter().apply(view), viewMapping);
}
}
// Find and delete existing view constraints to prevent ID conflict when importing. Migration should handle this,
// but best to not let the user corrupt their model. Have also noticed an MD bug where the constraint just sticks around
// after a session cancellation.
Map<Element, Constraint> viewConstraintHashMap = new HashMap<>(0);
for (ViewMapping viewMapping : viewMap.values()) {
Element view = viewMapping.getElement();
if (view == null) {
continue;
}
Constraint constraint = Utils.getViewConstraint(view);
if (constraint == null) {
Element element = Converters.getIdToElementConverter().apply(Converters.getElementToIdConverter().apply(view) + MDKConstants.VIEW_CONSTRAINT_SYSML_ID_SUFFIX, project);
if (element instanceof Constraint) {
constraint = (Constraint) element;
}
}
if (constraint != null) {
viewConstraintHashMap.put(view, constraint);
}
}
if (!viewConstraintHashMap.isEmpty()) {
try {
SessionManager.getInstance().createSession(project, "Legacy View Constraint Purge");
if (!SessionManager.getInstance().isSessionCreated(project)) {
Application.getInstance().getGUILog().log("[ERROR] MagicDraw session creation failed. View generation aborted. Please restart MagicDraw and try again.");
failure = true;
return;
}
for (Map.Entry<Element, Constraint> entry : viewConstraintHashMap.entrySet()) {
Constraint constraint = entry.getValue();
if (!constraint.isEditable()) {
updateFailed.addViolation(new ValidationRuleViolation(constraint, "[UPDATE FAILED] This view constraint <" + constraint.getLocalID() + "> could not be deleted automatically and needs to be deleted to prevent ID conflicts."));
failure = true;
continue;
}
Application.getInstance().getGUILog().log("Deleting legacy view constraint: " + Converters.getElementToIdConverter().apply(constraint));
try {
ModelElementsManager.getInstance().removeElement(constraint);
} catch (ReadOnlyElementException e) {
updateFailed.addViolation(new ValidationRuleViolation(constraint, "[UPDATE FAILED] This view constraint <" + constraint.getLocalID() + "> could not be deleted automatically and needs to be deleted to prevent ID conflicts."));
failure = true;
}
}
}
finally {
if (SessionManager.getInstance().isSessionCreated(project)) {
SessionManager.getInstance().closeSession(project);
}
}
}
if (failure) {
Utils.displayValidationWindow(project, vss, "View Generation Validation");
return;
}
// Allowing cancellation right before potentially long server queries
if (handleCancel(progressStatus)) {
return;
}
LocalSyncTransactionCommitListener localSyncTransactionCommitListener = LocalSyncProjectEventListenerAdapter.getProjectMapping(project).getLocalSyncTransactionCommitListener();
Set<Element> elementsToDelete = new HashSet<>();
// Create the session you intend to cancel to revert all temporary elements.
if (SessionManager.getInstance().isSessionCreated(project)) {
SessionManager.getInstance().closeSession(project);
}
SessionManager.getInstance().createSession(project, "View Presentation Generation - Cancelled");
if (!SessionManager.getInstance().isSessionCreated(project)) {
Application.getInstance().getGUILog().log("[ERROR] MagicDraw session creation failed. View generation aborted. Please restart MagicDraw and try again.");
failure = true;
return;
}
if (localSyncTransactionCommitListener != null) {
localSyncTransactionCommitListener.setDisabled(true);
}
// Query existing server-side JSONs for views
List<String> viewIDs = new ArrayList<>();
if (!viewMap.isEmpty()) {
// STAGE 2: Downloading existing view instances
progressStatus.setDescription("Downloading existing view instances");
progressStatus.setCurrent(2);
ObjectNode viewResponse;
try {
File responseFile = MMSUtils.getElements(project, viewMap.keySet(), progressStatus);
try (JsonParser jsonParser = JacksonUtils.getJsonFactory().createParser(responseFile)) {
viewResponse = JacksonUtils.parseJsonObject(jsonParser);
}
} catch (ServerException | IOException | URISyntaxException e) {
failure = true;
Application.getInstance().getGUILog().log("[WARNING] Server error occurred. Please check your network connection or view logs for more information.");
e.printStackTrace();
return;
}
JsonNode viewElementsJsonArray;
if (viewResponse != null && (viewElementsJsonArray = viewResponse.get("elements")) != null && viewElementsJsonArray.isArray()) {
Queue<String> instanceIDs = new LinkedList<>();
Queue<String> slotIDs = new LinkedList<>();
Property generatedFromViewProperty = Utils.getGeneratedFromViewProperty(project),
generatedFromElementProperty = Utils.getGeneratedFromElementProperty(project);
for (JsonNode elementJsonNode : viewElementsJsonArray) {
if (!elementJsonNode.isObject()) {
continue;
}
ObjectNode elementObjectNode = (ObjectNode) elementJsonNode;
// Resolve current instances in the view constraint expression
JsonNode viewOperandJsonNode = JacksonUtils.getAtPath(elementObjectNode, "/" + MDKConstants.CONTENTS_KEY + "/operand"),
sysmlIdJson = elementObjectNode.get(MDKConstants.ID_KEY);
String sysmlId;
if (sysmlIdJson != null && sysmlIdJson.isTextual() && !(sysmlId = sysmlIdJson.asText()).isEmpty()) {
viewIDs.add(sysmlId);
if (viewOperandJsonNode != null && viewOperandJsonNode.isArray()) {
// store returned ids so we can exclude view generation for elements that aren't on the mms yet
List<String> viewInstanceIDs = new ArrayList<>(viewOperandJsonNode.size());
for (JsonNode viewOperandJson : viewOperandJsonNode) {
JsonNode instanceIdJsonNode = viewOperandJson.get(MDKConstants.INSTANCE_ID_KEY);
String instanceId;
if (instanceIdJsonNode != null && instanceIdJsonNode.isTextual() && !(instanceId = instanceIdJsonNode.asText()).isEmpty()) {
if (generatedFromViewProperty != null) {
slotIDs.add(instanceId + MDKConstants.SLOT_ID_SEPARATOR + Converters.getElementToIdConverter().apply(generatedFromViewProperty));
}
if (generatedFromElementProperty != null) {
slotIDs.add(instanceId + MDKConstants.SLOT_ID_SEPARATOR + Converters.getElementToIdConverter().apply(generatedFromElementProperty));
}
instanceIDs.add(instanceId);
viewInstanceIDs.add(instanceId);
}
}
ViewMapping viewMapping = viewMap.containsKey(sysmlId) ? viewMap.get(sysmlId) : new ViewMapping();
viewMapping.setObjectNode(elementObjectNode);
viewMapping.setInstanceIDs(viewInstanceIDs);
viewMap.put(sysmlId, viewMapping);
}
}
}
// Now that all first-level instances are resolved, query for them and import client-side (in reverse order) as model elements
// Add any sections that are found along the way and loop until no more are found
List<ObjectNode> instanceObjectNodes = new ArrayList<>();
List<ObjectNode> slotObjectNodes = new ArrayList<>();
while (!instanceIDs.isEmpty() && !slotIDs.isEmpty()) {
// Allow cancellation between every depths' server query.
if (handleCancel(progressStatus)) {
return;
}
List<String> elementIDs = new ArrayList<>(instanceIDs);
elementIDs.addAll(slotIDs);
ObjectNode instanceAndSlotResponse;
try {
File responseFile = MMSUtils.getElements(project, elementIDs, progressStatus);
try (JsonParser jsonParser = JacksonUtils.getJsonFactory().createParser(responseFile)) {
instanceAndSlotResponse = JacksonUtils.parseJsonObject(jsonParser);
}
} catch (ServerException | IOException | URISyntaxException e) {
failure = true;
Application.getInstance().getGUILog().log("[WARNING] Server error occurred. Please check your network connection or view logs for more information.");
e.printStackTrace();
SessionManager.getInstance().cancelSession(project);
return;
}
instanceIDs.clear();
slotIDs.clear();
JsonNode instanceAndSlotElementsJsonArray;
if (instanceAndSlotResponse != null && (instanceAndSlotElementsJsonArray = instanceAndSlotResponse.get("elements")) != null && instanceAndSlotElementsJsonArray.isArray()) {
for (JsonNode elementJson : instanceAndSlotElementsJsonArray) {
JsonNode instanceOperandJsonArray = JacksonUtils.getAtPath(elementJson, "/specification/operand");
if (instanceOperandJsonArray != null && instanceOperandJsonArray.isArray()) {
for (JsonNode instanceOperandJson : instanceOperandJsonArray) {
JsonNode instanceIdJson = instanceOperandJson.get(MDKConstants.INSTANCE_ID_KEY);
String instanceId;
if (instanceIdJson != null && instanceIdJson.isTextual() && !(instanceId = instanceIdJson.asText()).isEmpty()) {
/*if (!instanceID.endsWith(PresentationElementUtils.ID_KEY_SUFFIX)) {
continue;
}*/
if (generatedFromViewProperty != null) {
slotIDs.add(instanceId + MDKConstants.SLOT_ID_SEPARATOR + Converters.getElementToIdConverter().apply(generatedFromViewProperty));
}
if (generatedFromElementProperty != null) {
slotIDs.add(instanceId + MDKConstants.SLOT_ID_SEPARATOR + Converters.getElementToIdConverter().apply(generatedFromElementProperty));
}
instanceIDs.add(instanceId);
}
}
}
JsonNode typeJson = elementJson.get(MDKConstants.TYPE_KEY);
if (typeJson.isTextual() && elementJson.isObject()) {
(typeJson.asText().equalsIgnoreCase("slot") ? slotObjectNodes : instanceObjectNodes).add((ObjectNode) elementJson);
}
}
}
}
// STAGE 3: Importing existing view instances
progressStatus.setDescription("Importing existing view instances");
progressStatus.setCurrent(3);
EMFImporter emfImporter = new EMFImporter() {
@Override
public List<PreProcessor> getPreProcessors() {
if (preProcessors == null) {
preProcessors = new ArrayList<>(super.getPreProcessors());
preProcessors.remove(PreProcessor.SYSML_ID_VALIDATION);
}
return preProcessors;
}
@Override
public List<EStructuralFeatureOverride> getEStructuralFeatureOverrides() {
if (eStructuralFeatureOverrides == null) {
eStructuralFeatureOverrides = new ArrayList<>(super.getEStructuralFeatureOverrides());
eStructuralFeatureOverrides.remove(EStructuralFeatureOverride.OWNER);
eStructuralFeatureOverrides.add(new EStructuralFeatureOverride(
EStructuralFeatureOverride.OWNER.getPredicate(),
(objectNode, eStructuralFeature, project, strict, element) -> {
if (element instanceof InstanceSpecification) {
element.setOwner(project.getPrimaryModel());
return element;
}
return EStructuralFeatureOverride.OWNER.getFunction().apply(objectNode, eStructuralFeature, project, strict, element);
}));
}
return eStructuralFeatureOverrides;
}
};
for (Boolean strict : Arrays.asList(false, true)) {
// importing instances in reverse order so that deepest level instances (sections and such) are loaded first
ListIterator<ObjectNode> instanceObjectNodesIterator = instanceObjectNodes.listIterator(instanceObjectNodes.size());
while (instanceObjectNodesIterator.hasPrevious()) {
if (handleCancel(progressStatus)) {
return;
}
ObjectNode instanceObjectNode = instanceObjectNodesIterator.previous();
try {
// Slots will break if imported with owner (instance) ignored, but we need to ignore InstanceSpecification owners
//Element element = ImportUtility.createElement(elementJsonNode, false, true);
Changelog.Change<Element> change = emfImporter.apply(instanceObjectNode, project, strict);
Element element = change != null ? change.getChanged() : null;
if (element instanceof InstanceSpecification) {
instanceSpecificationMap.put(Converters.getElementToIdConverter().apply(element), new Pair<>(instanceObjectNode, (InstanceSpecification) element));
}
} catch (ImportException | ReadOnlyElementException e) {
Application.getInstance().getGUILog().log("[WARNING] Failed to import instance specification " + instanceObjectNode.get(MDKConstants.ID_KEY) + ": " + e.getMessage());
instanceObjectNodesIterator.remove();
}
}
// The Alfresco service is a nice guy and returns Slots at the end so that when it's loaded in order the slots don't throw errors for missing their instances.
// However we're being fancy and loading them backwards so we had to separate them ahead of time and then load the slots after.
ListIterator<ObjectNode> slotObjectNodesIterator = slotObjectNodes.listIterator();
while (slotObjectNodesIterator.hasNext()) {
if (handleCancel(progressStatus)) {
return;
}
ObjectNode slotObjectNode = slotObjectNodesIterator.next();
try {
// Slots will break if imported with owner (instance) ignored, but we need to ignore InstanceSpecification owners
//Element element = ImportUtility.createElement(slotJsonNode, false, false);
Changelog.Change<Element> change = emfImporter.apply(slotObjectNode, project, strict);
Element element = change != null ? change.getChanged() : null;
if (element instanceof Slot) {
slotMap.put(Converters.getElementToIdConverter().apply(element), new Pair<>(slotObjectNode, (Slot) element));
}
} catch (ImportException | ReadOnlyElementException e) {
Application.getInstance().getGUILog().log("[WARNING] Failed to import slot " + slotObjectNode.get(MDKConstants.ID_KEY) + ": " + e.getMessage());
slotObjectNodesIterator.remove();
}
}
}
// Build view constraints client-side as actual Constraint, Expression, InstanceValue(s), etc.
// Note: Doing this one first since what it does is smaller in scope than ImportUtility. Potential order-dependent edge cases require further evaluation.
// Correct point of no return.
for (ViewMapping viewMapping : viewMap.values()) {
Element view = viewMapping.getElement();
/*if (handleCancel(progressStatus)) {
return;
}*/
ObjectNode viewObjectNode = viewMapping.getObjectNode();
if (viewObjectNode == null) {
continue;
}
JsonNode viewContentsJsonNode = viewObjectNode.get(MDKConstants.CONTENTS_KEY);
if (viewContentsJsonNode == null || !viewContentsJsonNode.isObject()) {
continue;
}
try {
Changelog.Change<Element> change = Converters.getJsonToElementConverter().apply((ObjectNode) viewContentsJsonNode, project, false);
if (change.getChanged() != null && change.getChanged() instanceof Expression) {
Expression expression = (Expression) change.getChanged();
// bit of massaging to filter out InstanceValues whose InstanceSpecification is deleted
expression.getOperand().stream().filter(vs -> !(vs instanceof InstanceValue) || ((InstanceValue) vs).getInstance() == null).collect(Collectors.toList()).forEach(vs -> {
elementsToDelete.add(vs);
expression.getOperand().remove(vs);
});
presentationElementUtils.getOrCreateViewConstraint(view).setSpecification(expression);
}
} catch (ImportException | ReadOnlyElementException e) {
Application.getInstance().getGUILog().log("[WARNING] Could not create view contents for " + Converters.getElementToIdConverter().apply(view) + ". The result could be that the view contents are created from scratch.");
//continue;
}
}
}
}
// STAGE 4: Generating new view instances
progressStatus.setDescription("Generating new view instances");
progressStatus.setCurrent(4);
Set<Element> views = new LinkedHashSet<>();
Map<Element, List<PresentationElementInstance>> view2pe = new LinkedHashMap<>();
Map<Element, List<PresentationElementInstance>> view2unused = new LinkedHashMap<>();
Map<Element, JSONArray> view2elements = new LinkedHashMap<>();
Map<String, ObjectNode> images = new LinkedHashMap<>();
Set<Element> skippedViews = new HashSet<>();
for (Element rootView : rootViews) {
DBAlfrescoVisitor dbAlfrescoVisitor = new DBAlfrescoVisitor(recurse, true);
try {
viewMap.get(Converters.getElementToIdConverter().apply(rootView)).getDbBook().accept(dbAlfrescoVisitor);
} catch (Exception e) {
Utils.printException(e);
e.printStackTrace();
}
views.addAll(presentationElementUtils.getViewProcessOrder(rootView, dbAlfrescoVisitor.getHierarchyElements()));
view2pe.putAll(dbAlfrescoVisitor.getView2Pe());
view2unused.putAll(dbAlfrescoVisitor.getView2Unused());
view2elements.putAll(dbAlfrescoVisitor.getView2Elements());
images.putAll(dbAlfrescoVisitor.getImages());
views.removeAll(processedElements);
}
for (Element view : views) {
if (ProjectUtilities.isElementInAttachedProject(view)) {
ValidationRuleViolation violation = new ValidationRuleViolation(view, "[IN MODULE] This view is in a module and was not processed.");
viewInProject.addViolation(violation);
skippedViews.add(view);
}
if (!viewIDs.contains(Converters.getElementToIdConverter().apply(view))) {
ValidationRuleViolation violation = new ValidationRuleViolation(view, "View does not exist on MMS. Generation skipped.");
viewDoesNotExist.addViolation(violation);
skippedViews.add(view);
}
}
if (failure) {
Utils.displayValidationWindow(project, vss, "View Generation Validation");
SessionManager.getInstance().cancelSession(project);
return;
}
try {
for (Element view : views) {
if (skippedViews.contains(view)) {
continue;
}
// Using null package with intention to cancel session and delete instances to prevent model validation error.
handlePes(view2pe.get(view), null);
presentationElementUtils.updateOrCreateConstraintFromPresentationElements(view, view2pe.get(view));
}
/*if (handleCancel(progressStatus)) {
return;
}*/
// commit to MMS
LinkedList<ObjectNode> elementsToCommit = new LinkedList<>();
Queue<Pair<InstanceSpecification, Element>> instanceToView = new LinkedList<>();
for (Element view : views) {
if (skippedViews.contains(view)) {
continue;
}
// Sends the full view JSON if it doesn't exist on the server yet. If it does exist, it sends just the
// portion of the JSON required to update the view contents.
ObjectNode clientViewJson = Converters.getElementToJsonConverter().apply(view, project);
if (clientViewJson == null) {
skippedViews.add(view);
continue;
}
if (view2elements.get(view) != null) {
ArrayNode displayedElements = clientViewJson.putArray(MDKConstants.DISPLAYED_ELEMENT_IDS_KEY);
for (Object id : view2elements.get(view)) {
displayedElements.add((String) id);
}
}
Object o;
ObjectNode serverViewJson = (o = viewMap.get(Converters.getElementToIdConverter().apply(view))) != null ? ((ViewMapping) o).getObjectNode() : null;
if (!JsonEquivalencePredicate.getInstance().test(clientViewJson, serverViewJson)) {
if (MDUtils.isDeveloperMode()) {
Application.getInstance().getGUILog().log("View diff for " + Converters.getElementToIdConverter().apply(view) + ": " + JsonPatchFunction.getInstance().apply(clientViewJson, serverViewJson).toString());
}
elementsToCommit.add(clientViewJson);
}
for (PresentationElementInstance presentationElementInstance : view2pe.get(view)) {
if (presentationElementInstance.getInstance() != null) {
instanceToView.add(new Pair<>(presentationElementInstance.getInstance(), view));
}
}
}
String viewInstanceBinId = MDKConstants.VIEW_INSTANCES_BIN_PREFIX + Converters.getIProjectToIdConverter().apply(project.getPrimaryProject());
while (!instanceToView.isEmpty()) {
Pair<InstanceSpecification, Element> pair = instanceToView.remove();
InstanceSpecification instance = pair.getKey();
List<InstanceSpecification> subInstances = presentationElementUtils.getCurrentInstances(instance, pair.getValue()).getAll();
for (InstanceSpecification subInstance : subInstances) {
instanceToView.add(new Pair<>(subInstance, pair.getValue()));
}
ObjectNode clientInstanceSpecificationJson = Converters.getElementToJsonConverter().apply(instance, project);
if (clientInstanceSpecificationJson == null) {
continue;
}
// override owner of pei to store parallel to the model
clientInstanceSpecificationJson.put(MDKConstants.OWNER_ID_KEY, viewInstanceBinId);
ObjectNode serverInstanceSpecificationJson = instanceSpecificationMap.containsKey(Converters.getElementToIdConverter().apply(instance)) ?
instanceSpecificationMap.get(Converters.getElementToIdConverter().apply(instance)).getKey() : null;
if (!JsonEquivalencePredicate.getInstance().test(clientInstanceSpecificationJson, serverInstanceSpecificationJson)) {
if (MDUtils.isDeveloperMode()) {
Application.getInstance().getGUILog().log("View Instance diff for " + Converters.getElementToIdConverter().apply(instance) + ": " + JsonPatchFunction.getInstance().apply(clientInstanceSpecificationJson, serverInstanceSpecificationJson).toString());
}
elementsToCommit.add(clientInstanceSpecificationJson);
}
for (Slot slot : instance.getSlot()) {
ObjectNode clientSlotJson = Converters.getElementToJsonConverter().apply(slot, project);
if (clientSlotJson == null) {
continue;
}
JsonNode serverSlotJson = slotMap.containsKey(Converters.getElementToIdConverter().apply(slot)) ?
slotMap.get(Converters.getElementToIdConverter().apply(slot)).getKey() : null;
if (!JsonEquivalencePredicate.getInstance().test(clientSlotJson, serverSlotJson)) {
elementsToCommit.add(clientSlotJson);
if (MDUtils.isDeveloperMode()) {
Application.getInstance().getGUILog().log("Slot diff for " + Converters.getElementToIdConverter().apply(slot) + ": " + JsonPatchFunction.getInstance().apply(clientSlotJson, serverSlotJson).toString());
}
}
}
}
// Last chance to cancel before sending generated views to the server. Point of no return.
// Correction: It's already too late. MagicDraw doesn't clean up constraints on session cancel (confirmed: 18.5), so we have to do it ourselves in the last stage.
// Once constraints are created is the actual point of no return.
/*if (handleCancel(progressStatus)) {
return;
}*/
boolean changed = false;
if (elementsToCommit.size() > 0) {
// STAGE 5: Queueing upload of generated view instances
progressStatus.setDescription("Queueing upload of generated view instances");
progressStatus.setCurrent(5);
Application.getInstance().getGUILog().log("Updating/creating " + elementsToCommit.size() + " element" + (elementsToCommit.size() != 1 ? "s" : "") + " to generate views.");
URIBuilder requestUri = MMSUtils.getServiceProjectsRefsElementsUri(project);
File sendData = MMSUtils.createEntityFile(this.getClass(), ContentType.APPLICATION_JSON, elementsToCommit, MMSUtils.JsonBlobType.ELEMENT_JSON);
OutputQueue.getInstance().offer(new Request(project, MMSUtils.HttpRequestType.POST, requestUri, sendData, ContentType.APPLICATION_JSON, elementsToCommit.size(), "Sync Changes"));
changed = true;
}
// Delete unused presentation elements
Set<String> mmsElementsToDelete = new HashSet<>();
for (List<PresentationElementInstance> presentationElementInstances : view2unused.values()) {
for (PresentationElementInstance presentationElementInstance : presentationElementInstances) {
if (presentationElementInstance.getInstance() == null) {
continue;
}
String id = Converters.getElementToIdConverter().apply(presentationElementInstance.getInstance());
if (id == null) {
continue;
}
mmsElementsToDelete.add(id);
}
}
if (elementsToCommit.size() > 0) {
Application.getInstance().getGUILog().log("Deleting " + mmsElementsToDelete.size() + " unused presentation element" + (mmsElementsToDelete.size() != 1 ? "s" : "") + ".");
URIBuilder requestUri = MMSUtils.getServiceProjectsRefsElementsUri(project);
File sendData = MMSUtils.createEntityFile(this.getClass(), ContentType.APPLICATION_JSON, mmsElementsToDelete, MMSUtils.JsonBlobType.ELEMENT_ID);
OutputQueue.getInstance().offer(new Request(project, MMSUtils.HttpRequestType.DELETE, requestUri, sendData, ContentType.APPLICATION_JSON, elementsToCommit.size(), "View Generation"));
changed = true;
}
if (!changed) {
Application.getInstance().getGUILog().log("No changes required to generate views.");
}
// STAGE 6: Finishing up
progressStatus.setDescription("Finishing up");
progressStatus.setCurrent(6);
// Cleaning up after myself. While cancelSession *should* undo all elements created, there are certain edge
// cases like the underlying constraint not existing in the containment tree, but leaving a stale constraint
// on the view block.
for (Pair<ObjectNode, Slot> pair : slotMap.values()) {
if (pair.getValue() != null) {
elementsToDelete.add(pair.getValue());
}
}
for (Pair<ObjectNode, InstanceSpecification> pair : instanceSpecificationMap.values()) {
if (pair.getValue() != null) {
elementsToDelete.add(pair.getValue());
}
}
for (Element element : views) {
Constraint constraint = Utils.getViewConstraint(element);
if (constraint == null) {
continue;
}
elementsToDelete.add(constraint);
ValueSpecification valueSpecification = constraint.getSpecification();
if (valueSpecification == null) {
continue;
}
elementsToDelete.add(valueSpecification);
List<ValueSpecification> operands;
if (!(valueSpecification instanceof Expression) || (operands = ((Expression) valueSpecification).getOperand()) == null) {
continue;
}
for (ValueSpecification operand : operands) {
elementsToDelete.add(operand);
InstanceSpecification instanceSpecification;
if (!(operand instanceof InstanceValue) || (instanceSpecification = ((InstanceValue) operand).getInstance()) == null) {
continue;
}
elementsToDelete.add(instanceSpecification);
for (Slot slot : instanceSpecification.getSlot()) {
elementsToDelete.add(slot);
elementsToDelete.addAll(slot.getValue());
}
}
}
for (Element element : elementsToDelete) {
try {
ModelElementsManager.getInstance().removeElement(element);
} catch (ReadOnlyElementException ignored) {
System.out.println("Could not clean up " + element.getLocalID());
}
}
// used to skip redundant view generation attempts when using multi-select or ElementGroups; see GenerateViewPresentationAction
processedElements.addAll(views);
} catch (Exception e) {
failure = true;
Utils.printException(e);
} finally {
// cancel session so all elements created get deleted automatically
if (SessionManager.getInstance().isSessionCreated(project)) {
SessionManager.getInstance().cancelSession(project);
}
if (localSyncTransactionCommitListener != null) {
localSyncTransactionCommitListener.setDisabled(false);
}
}
ImageValidator iv = new ImageValidator(images, images);
// this checks images generated from the local generation against what's on the web based on checksum
iv.validate(project);
// Auto-validate - https://cae-jira.jpl.nasa.gov/browse/MAGICDRAW-45
for (ValidationRule validationRule : iv.getSuite().getValidationRules()) {
for (ValidationRuleViolation validationRuleViolation : validationRule.getViolations()) {
if (!validationRuleViolation.getActions().isEmpty()) {
validationRuleViolation.getActions().get(0).actionPerformed(null);
}
}
}
if (suite.hasErrors()) {
Utils.displayValidationWindow(project, vss, "View Generation Validation");
}
}
private void handlePes(List<PresentationElementInstance> pes, Package p) {
for (PresentationElementInstance pe : pes) {
if (pe.getChildren() != null && !pe.getChildren().isEmpty()) {
handlePes(pe.getChildren(), p);
}
presentationElementUtils.updateOrCreateInstance(pe, p);
}
}
private boolean handleCancel(ProgressStatus progressStatus) {
if (progressStatus.isCancel()) {
failure = true;
if (SessionManager.getInstance().isSessionCreated(project)) {
SessionManager.getInstance().cancelSession(project);
}
Application.getInstance().getGUILog().log("View generation cancelled.");
return true;
}
return false;
}
public List<ValidationSuite> getValidations() {
return vss;
}
public boolean isFailure() {
return failure;
}
private class ViewMapping {
private Element element;
private ObjectNode objectNode;
private List<String> instanceIDs;
private DBBook dbBook;
public Element getElement() {
return element;
}
public void setElement(Element element) {
this.element = element;
}
public ObjectNode getObjectNode() {
return objectNode;
}
public void setObjectNode(ObjectNode objectNode) {
this.objectNode = objectNode;
}
public List<String> getInstanceIDs() {
return instanceIDs;
}
public void setInstanceIDs(List<String> instanceIDs) {
this.instanceIDs = instanceIDs;
}
public DBBook getDbBook() {
return dbBook;
}
public void setDbBook(DBBook dbBook) {
this.dbBook = dbBook;
}
}
}