package gov.nasa.jpl.mbee.mdk.emf;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.openapi.uml.ReadOnlyElementException;
import com.nomagic.magicdraw.openapi.uml.SessionManager;
import com.nomagic.magicdraw.uml.transaction.RepositoryModelValidator;
import com.nomagic.task.ProgressStatus;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;
import com.nomagic.uml2.transaction.ModelValidationResult;
import gov.nasa.jpl.mbee.mdk.api.incubating.MDKConstants;
import gov.nasa.jpl.mbee.mdk.api.incubating.annotations.SessionManaged;
import gov.nasa.jpl.mbee.mdk.api.incubating.convert.Converters;
import gov.nasa.jpl.mbee.mdk.api.incubating.convert.JsonToElementFunction;
import gov.nasa.jpl.mbee.mdk.json.ImportException;
import gov.nasa.jpl.mbee.mdk.util.Changelog;
import gov.nasa.jpl.mbee.mdk.util.MDUtils;
import gov.nasa.jpl.mbee.mdk.mms.json.JsonEquivalencePredicate;
import gov.nasa.jpl.mbee.mdk.util.Pair;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import java.text.NumberFormat;
import java.util.*;
import java.util.function.BiFunction;
/**
* Created by igomes on 9/28/16.
*/
// TODO What about locks? @donbot
public class EMFBulkImporter implements BulkImportFunction {
private final String sessionName;
private int sessionCount;
private Changelog<String, Pair<Element, ObjectNode>> changelog;
private Map<Pair<Element, ObjectNode>, Exception> failedElementMap;
private Map<Element, ObjectNode> nonEquivalentElements;
private Map<String, Element> elementCache;
private final BiFunction<String, Project, Element> bulkIdToElementConverter = (id, project) -> {
Element element = Converters.getIdToElementConverter().apply(id, project);
if (MDUtils.isDeveloperMode()) {
System.out.println("[NO CACHE] " + id + " -> " + element);
}
if (element == null && elementCache != null) {
element = elementCache.get(id);
if (MDUtils.isDeveloperMode()) {
System.out.println("[CACHE] " + id + " -> " + element);
}
}
return element;
};
public EMFBulkImporter(String sessionName) {
this.sessionName = sessionName;
}
@SessionManaged
@Override
public Changelog<String, Pair<Element, ObjectNode>> apply(Collection<ObjectNode> objectNodes, Project project, ProgressStatus progressStatus) {
String initialProgressStatusDescription = null;
long initialProgressStatusCurrent = 0;
boolean initialProgressStatusIndeterminate = false;
RepositoryModelValidator validator = new RepositoryModelValidator(project);
if (progressStatus != null) {
initialProgressStatusDescription = progressStatus.getDescription();
initialProgressStatusCurrent = progressStatus.getCurrent();
initialProgressStatusIndeterminate = progressStatus.isIndeterminate();
progressStatus.setMax(objectNodes.size() * 3);
progressStatus.setCurrent(0);
}
project.getModels().forEach(EMFBulkImporter::preloadRecursively);
try {
objectNodes = new ArrayList<>(objectNodes);
failedElementMap = new LinkedHashMap<>(objectNodes.size());
nonEquivalentElements = new LinkedHashMap<>();
Map<Element, Changelog.ChangeType> changeTypeMap = new HashMap<>(objectNodes.size());
JsonToElementFunction jsonToElementFunction = new EMFImporter() {
@Override
protected List<EStructuralFeatureOverride> getEStructuralFeatureOverrides() {
if (eStructuralFeatureOverrides == null) {
eStructuralFeatureOverrides = new ArrayList<>(super.getEStructuralFeatureOverrides());
eStructuralFeatureOverrides.remove(EStructuralFeatureOverride.OWNER);
eStructuralFeatureOverrides.add(EStructuralFeatureOverride.getOwnerEStructuralFeatureOverride(bulkIdToElementConverter));
}
return eStructuralFeatureOverrides;
}
@Override
protected List<PreProcessor> getPreProcessors() {
if (preProcessors == null) {
preProcessors = new ArrayList<>(super.getPreProcessors());
preProcessors.remove(PreProcessor.CREATE);
preProcessors.add(0, PreProcessor.getCreatePreProcessor(bulkIdToElementConverter));
}
return preProcessors;
}
@Override
protected BiFunction<String, Project, Element> getIdToElementConverter() {
return bulkIdToElementConverter;
}
};
bulkImport:
while (/*failedElementMap.isEmpty() && */!objectNodes.isEmpty()) {
changelog = new Changelog<>();
elementCache = new HashMap<>();
List<ObjectNode> retryObjectNodes = new ArrayList<>();
if (SessionManager.getInstance().isSessionCreated()) {
SessionManager.getInstance().cancelSession();
}
SessionManager.getInstance().createSession(project, sessionName + " x" + objectNodes.size() + " #" + ++sessionCount);
if (progressStatus != null) {
progressStatus.setDescription(sessionName + " - " + NumberFormat.getInstance().format(objectNodes.size()) + " elements" + (!failedElementMap.isEmpty() ? " - " + NumberFormat.getInstance().format(failedElementMap.size()) + " failed" : ""));
progressStatus.setCurrent(progressStatus.getMax() - objectNodes.size() * 3);
}
Iterator<ObjectNode> iterator = objectNodes.iterator();
while (iterator.hasNext()) {
ObjectNode objectNode = iterator.next();
JsonNode sysmlIdJsonNode = objectNode.get(MDKConstants.ID_KEY);
String sysmlId = sysmlIdJsonNode != null && sysmlIdJsonNode.isTextual() ? sysmlIdJsonNode.asText() : null;
if (MDUtils.isDeveloperMode()) {
System.out.println("[ATTEMPT 1] Attempting " + sysmlId);
}
Changelog.Change<Element> change = null;
try {
change = jsonToElementFunction.apply(objectNode, project, false);
} catch (ImportException | ReadOnlyElementException ignored) {
}
if (change == null || change.getChanged() == null) {
if (MDUtils.isDeveloperMode()) {
System.err.println("[FAILED 1] Could not create " + sysmlId);
}
// Element may fail to create on first pass, ex: Diagram (because owner doesn't exist yet + custom creation), so we need to retry after everything else.
retryObjectNodes.add(objectNode);
//failedElementMap.put(new Pair<>(Converters.getIdToElementConverter().apply(objectNode.get(MDKConstants.ID_KEY).asText(), project), objectNode), importException);
//iterator.remove();
//continue bulkImport;
}
else {
if (MDUtils.isDeveloperMode()) {
System.out.println("[SUCCESS 1] Imported " + sysmlId);
}
if (sysmlId != null) {
elementCache.put(sysmlId, change.getChanged());
}
changeTypeMap.put(change.getChanged(), change.getType());
}
//changelog.addChange(Converters.getElementToIdConverter().apply(change.getChanged()), new Pair<>(change.getChanged(), objectNode), change.getType());
if (progressStatus != null) {
progressStatus.increase();
}
}
for (ObjectNode objectNode : retryObjectNodes) {
JsonNode sysmlIdJsonNode = objectNode.get(MDKConstants.ID_KEY);
String sysmlId = sysmlIdJsonNode != null && sysmlIdJsonNode.isTextual() ? sysmlIdJsonNode.asText() : null;
if (MDUtils.isDeveloperMode()) {
System.out.println("[ATTEMPT 1.5] Attempting " + sysmlId);
}
Changelog.Change<Element> change = null;
Exception exception = new ImportException(null, objectNode, "Failed to create/update element.");
try {
change = jsonToElementFunction.apply(objectNode, project, false);
} catch (ImportException | ReadOnlyElementException e) {
exception = e;
}
if (change == null || change.getChanged() == null) {
if (MDUtils.isDeveloperMode()) {
System.err.println("[FAILED 1.5] Could not create " + sysmlId);
}
Element element = Converters.getIdToElementConverter().apply(sysmlId, project);
failedElementMap.put(new Pair<>(element != null && !element.isInvalid() && !project.isDisposed(element) ? element : null, objectNode), exception);
objectNodes.remove(objectNode);
continue bulkImport;
}
else {
if (MDUtils.isDeveloperMode()) {
System.out.println("[SUCCESS 1.5] Imported " + sysmlId);
}
if (sysmlId != null) {
elementCache.put(sysmlId, change.getChanged());
}
changeTypeMap.put(change.getChanged(), change.getType());
}
}
iterator = objectNodes.iterator();
while (iterator.hasNext()) {
ObjectNode objectNode = iterator.next();
JsonNode sysmlIdJsonNode = objectNode.get(MDKConstants.ID_KEY);
String sysmlId = sysmlIdJsonNode != null && sysmlIdJsonNode.isTextual() ? sysmlIdJsonNode.asText() : "<>";
if (MDUtils.isDeveloperMode()) {
System.out.println("[ATTEMPT 2] Attempting " + sysmlId);
}
Changelog.Change<Element> change = null;
Exception exception = new ImportException(null, objectNode, "Failed to create/update element with relationships.");
try {
change = jsonToElementFunction.apply(objectNode, project, true);
} catch (ImportException | ReadOnlyElementException e) {
exception = e;
}
if (change == null || change.getChanged() == null) {
if (MDUtils.isDeveloperMode()) {
System.err.println("[FAILED 2] Could not import " + sysmlId);
}
Element element = Converters.getIdToElementConverter().apply(sysmlId, project);
failedElementMap.put(new Pair<>(element != null && !element.isInvalid() && !project.isDisposed(element) ? element : null, objectNode), exception);
iterator.remove();
continue bulkImport;
}
else {
if (MDUtils.isDeveloperMode()) {
System.out.println("[SUCCESS 2] Imported " + sysmlId);
}
if (sysmlId != null) {
elementCache.put(sysmlId, change.getChanged());
}
Changelog.ChangeType changeType = changeTypeMap.get(change.getChanged());
changelog.addChange(Converters.getElementToIdConverter().apply(change.getChanged()), new Pair<>(change.getChanged(), objectNode), changeType != null ? changeType : change.getType());
}
if (progressStatus != null) {
progressStatus.increase();
}
}
for (Changelog.ChangeType changeType : Changelog.ChangeType.values()) {
for (Map.Entry<String, Pair<Element, ObjectNode>> entry : changelog.get(changeType).entrySet()) {
Element element = entry.getValue().getKey();
ObjectNode objectNode = entry.getValue().getValue();
Collection<ModelValidationResult> results = validator.validateChanges(Collections.singleton(element));
if (results != null && !results.isEmpty()) {
ModelValidationResult result = results.iterator().next();
if (MDUtils.isDeveloperMode()) {
System.err.println("[FAILED 3] " + result.toString());
}
failedElementMap.put(new Pair<>(element != null && !element.isInvalid() && !project.isDisposed(element) ? element : null, objectNode), new ImportException(element, objectNode, "Element failed validation after importing. Reason: " + result.getReason()));
objectNodes.remove(objectNode);
continue bulkImport;
}
if (element.isInvalid()) {
if (MDUtils.isDeveloperMode()) {
JsonNode sysmlIdJsonNode = objectNode.get(MDKConstants.ID_KEY);
String sysmlId = sysmlIdJsonNode != null && sysmlIdJsonNode.isTextual() ? sysmlIdJsonNode.asText() : "<>";
System.err.println("[FAILED 4] Could not create " + sysmlId);
}
failedElementMap.put(new Pair<>(element != null && !element.isInvalid() && !project.isDisposed(element) ? element : null, objectNode), new ImportException(element, objectNode, "Element was found to be invalid after importing."));
objectNodes.remove(objectNode);
continue bulkImport;
}
ObjectNode sourceObjectNode = Converters.getElementToJsonConverter().apply(element, project);
if (!JsonEquivalencePredicate.getInstance().test(sourceObjectNode, objectNode)) {
// currently handled as a warning instead of an error
nonEquivalentElements.put(element, objectNode);
}
if (progressStatus != null) {
progressStatus.increase();
}
}
}
if (failedElementMap.isEmpty()) {
onSuccess();
}
if (SessionManager.getInstance().isSessionCreated()) {
SessionManager.getInstance().closeSession();
}
break;
}
} finally {
if (SessionManager.getInstance().isSessionCreated()) {
SessionManager.getInstance().cancelSession();
}
if (!failedElementMap.isEmpty()) {
onFailure();
}
if (progressStatus != null) {
progressStatus.setDescription(initialProgressStatusDescription);
progressStatus.setCurrent(initialProgressStatusCurrent);
progressStatus.setIndeterminate(initialProgressStatusIndeterminate);
}
}
return changelog;
}
private static void preloadRecursively(EObject eObject) {
for (final TreeIterator<Object> allProperContents = EcoreUtil.getAllProperContents(eObject, true); allProperContents.hasNext(); allProperContents.next()) {
// just iterate to load contents
}
}
public void onSuccess() {
}
public void onFailure() {
}
public String getSessionName() {
return sessionName;
}
public int getSessionCount() {
return sessionCount;
}
public Changelog<String, Pair<Element, ObjectNode>> getChangelog() {
return changelog;
}
public Map<Pair<Element, ObjectNode>, Exception> getFailedElementMap() {
return failedElementMap;
}
public Map<Element, ObjectNode> getNonEquivalentElements() {
return nonEquivalentElements;
}
}