package gov.nasa.jpl.mbee.mdk.mms.actions;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.uml2.ext.magicdraw.classes.mdkernel.Element;
import gov.nasa.jpl.mbee.mdk.api.incubating.MDKConstants;
import gov.nasa.jpl.mbee.mdk.mms.MMSUtils;
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.validation.IRuleViolationAction;
import gov.nasa.jpl.mbee.mdk.validation.RuleViolationAction;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import javax.annotation.CheckForNull;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Created by igomes on 9/27/16.
*/
// TODO Abstract this and update to a common class @donbot
public class CommitClientElementAction extends RuleViolationAction implements AnnotationAction, IRuleViolationAction {
private static final String DEFAULT_ID = "Commit Element to MMS";
private static final int COMMIT_ELEMENT_COUNT_THRESHOLD = Integer.MAX_VALUE;
private static final int COMPLETION_DELAY = 0;
private final String elementID;
private final Element element;
private final ObjectNode elementObjectNode;
private final Project project;
public CommitClientElementAction(String elementID, Element element, ObjectNode elementObjectNode, Project project) {
super(DEFAULT_ID, DEFAULT_ID, null, null);
this.elementID = elementID;
this.element = element;
this.elementObjectNode = elementObjectNode;
this.project = project;
}
@Override
public void execute(Collection<Annotation> annotations) {
List<ObjectNode> elementsToUpdate = new LinkedList<>();
List<String> elementsToDelete = new LinkedList<>();
for (Annotation annotation : annotations) {
for (NMAction action : annotation.getActions()) {
if (action instanceof CommitClientElementAction) {
ObjectNode elementObjectNode = ((CommitClientElementAction) action).getElementObjectNode();
if (elementObjectNode != null) {
elementsToUpdate.add(elementObjectNode);
}
else if (elementID.startsWith(MDKConstants.HOLDING_BIN_ID_PREFIX)) {
Application.getInstance().getGUILog().log("[INFO] Skipping deletion of Holding Bin from MMS.");
}
else {
elementsToDelete.add(((CommitClientElementAction) action).getElementID());
}
break;
}
}
}
try {
CommitClientElementAction.request(elementsToUpdate, elementsToDelete, project);
} catch (JsonProcessingException e) {
Application.getInstance().getGUILog().log("[ERROR]: Exception occurred committing element to server. Commit aborted.");
e.printStackTrace();
}
}
@Override
public boolean canExecute(Collection<Annotation> annotations) {
return true;
}
public String getElementID() {
return elementID;
}
public Element getElement() {
return element;
}
public ObjectNode getElementObjectNode() {
return elementObjectNode;
}
@Override
public void actionPerformed(@CheckForNull ActionEvent actionEvent) {
List<ObjectNode> elementsToUpdate = new ArrayList<>(1);
List<String> elementsToDelete = new ArrayList<>(1);
if (elementObjectNode != null) {
elementsToUpdate.add(elementObjectNode);
}
else {
elementsToDelete.add(elementID);
}
try {
CommitClientElementAction.request(elementsToUpdate, elementsToDelete, project);
} catch (JsonProcessingException e) {
Application.getInstance().getGUILog().log("[ERROR]: Exception occurred committing element to server. Commit aborted.");
e.printStackTrace();
}
}
private static void request(List<ObjectNode> elementsToUpdate, List<String> elementsToDelete, Project project) throws JsonProcessingException {
if (elementsToUpdate != null && !elementsToUpdate.isEmpty()) {
Application.getInstance().getGUILog().log("[INFO] Queuing request to create/update " + NumberFormat.getInstance().format(elementsToUpdate.size()) + " element" + (elementsToUpdate.size() != 1 ? "s" : "") + " on MMS.");
int requestCapacity = elementsToUpdate.size() > COMMIT_ELEMENT_COUNT_THRESHOLD ? (elementsToUpdate.size() / COMMIT_ELEMENT_COUNT_THRESHOLD + (elementsToUpdate.size() % COMMIT_ELEMENT_COUNT_THRESHOLD != 0 ? 1 : 0)) * 2 : 1;
List<Request> requests = new ArrayList<>(requestCapacity);
List<ObjectNode> elementsToPost = new ArrayList<>(Math.min(elementsToUpdate.size(), COMMIT_ELEMENT_COUNT_THRESHOLD));
for (int i = 0; i < elementsToUpdate.size(); i++) {
elementsToPost.add(elementsToUpdate.get(i));
// send requests in chunks if above threshold
if (elementsToPost.size() == COMMIT_ELEMENT_COUNT_THRESHOLD || i + 1 == elementsToUpdate.size()) {
try {
File file = MMSUtils.createEntityFile(CommitClientElementAction.class, ContentType.APPLICATION_JSON, elementsToPost, MMSUtils.JsonBlobType.ELEMENT_JSON);
if (elementsToUpdate.size() > COMMIT_ELEMENT_COUNT_THRESHOLD) {
int requestIndex = (i + 1) / COMMIT_ELEMENT_COUNT_THRESHOLD + ((i + 1) % COMMIT_ELEMENT_COUNT_THRESHOLD != 0 ? 1 : 0) - 1;
URIBuilder requestUri = MMSUtils.getServiceProjectsRefsElementsUri(project);
if (requestUri == null) {
throw new IOException();
}
requestUri.addParameter("nodes", Boolean.toString(true));
//requestUri.addParameter("edges", Boolean.toString(false));
requests.add(requestIndex, new Request(project, MMSUtils.HttpRequestType.POST, requestUri, file, ContentType.APPLICATION_JSON, elementsToPost.size(),
"Sync Changes - Nodes - " + NumberFormat.getInstance().format(requestIndex + 1) + " / " + (requestCapacity / 2), COMPLETION_DELAY));
requestUri = MMSUtils.getServiceProjectsRefsElementsUri(project);
if (requestUri == null) {
throw new IOException();
}
//requestUri.addParameter("nodes", Boolean.toString(false));
requestUri.addParameter("edges", Boolean.toString(true));
requests.add(new Request(project, MMSUtils.HttpRequestType.POST, requestUri, file, ContentType.APPLICATION_JSON, elementsToPost.size(),
"Sync Changes - Edges - " + NumberFormat.getInstance().format(requestIndex + 1) + " / " + (requestCapacity / 2), COMPLETION_DELAY));
}
else {
URIBuilder requestUri = MMSUtils.getServiceProjectsRefsElementsUri(project);
requests.add(new Request(project, MMSUtils.HttpRequestType.POST, requestUri, file, ContentType.APPLICATION_JSON, elementsToPost.size(), "Sync Changes"));
}
} catch (IOException | URISyntaxException e) {
Application.getInstance().getGUILog().log("[ERROR] Unexpected failure processing request. Reason: " + e.getMessage());
e.printStackTrace();
return;
}
if (i + 1 != elementsToUpdate.size()) {
elementsToPost = new ArrayList<>(Math.min(elementsToUpdate.size() - (i + 1), COMMIT_ELEMENT_COUNT_THRESHOLD));
}
}
}
requests.forEach(request -> OutputQueue.getInstance().offer(request));
}
if (elementsToDelete != null && !elementsToDelete.isEmpty()) {
Application.getInstance().getGUILog().log("[INFO] Queuing request to delete " + NumberFormat.getInstance().format(elementsToDelete.size()) + " element" + (elementsToDelete.size() != 1 ? "s" : "") + " on MMS.");
try {
File file = MMSUtils.createEntityFile(CommitClientElementAction.class, ContentType.APPLICATION_JSON, elementsToDelete, MMSUtils.JsonBlobType.ELEMENT_ID);
URIBuilder requestUri = MMSUtils.getServiceProjectsRefsElementsUri(project);
OutputQueue.getInstance().offer((new Request(project, MMSUtils.HttpRequestType.DELETE, requestUri, file, ContentType.APPLICATION_JSON, elementsToDelete.size(), "Sync Changes")));
} catch (IOException | URISyntaxException e) {
Application.getInstance().getGUILog().log("[ERROR] Unexpected failure processing request. Reason: " + e.getMessage());
e.printStackTrace();
return;
}
}
}
}