package gov.nasa.jpl.mbee.mdk.mms.validation; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.nomagic.actions.ActionsCategory; import com.nomagic.magicdraw.core.Application; import com.nomagic.magicdraw.core.Project; import com.nomagic.task.EmptyProgressStatus; import com.nomagic.task.ProgressStatus; import com.nomagic.task.RunnableWithProgress; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.NamedElement; 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.json.JacksonUtils; import gov.nasa.jpl.mbee.mdk.mms.actions.CommitClientElementAction; import gov.nasa.jpl.mbee.mdk.mms.actions.UpdateClientElementAction; import gov.nasa.jpl.mbee.mdk.mms.json.JsonPatchFunction; 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.util.Pair; import java.io.File; import java.io.IOException; import java.text.NumberFormat; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; /** * Created by igomes on 9/26/16. */ public class ElementValidator implements RunnableWithProgress { private Collection<Pair<Element, ObjectNode>> clientElements; private Collection<ObjectNode> serverElements; private final Project project; private Collection<File> serverElementFiles; private int notEquivalentCount = 0; private int missingInClientCount = 0; private int missingOnMmsCount = 0; private final ValidationSuite validationSuite; private ValidationRule elementEquivalenceValidationRule = new ValidationRule("Element Equivalence", "Element shall be represented in MagicDraw and MMS equivalently.", ViolationSeverity.ERROR); private Map<String, Pair<Pair<Element, ObjectNode>, ObjectNode>> invalidElements = new LinkedHashMap<>(); public ElementValidator(String name, Collection<Pair<Element, ObjectNode>> clientElements, Collection<ObjectNode> serverElements, Project project, Collection<File> serverElementFiles) { this.clientElements = clientElements; this.serverElements = serverElements; this.project = project; this.serverElementFiles = serverElementFiles; validationSuite = new ValidationSuite(name); validationSuite.addValidationRule(elementEquivalenceValidationRule); } public ElementValidator(String name, Collection<Pair<Element, ObjectNode>> clientElements, Collection<ObjectNode> serverElements, Project project) { this(name, clientElements, serverElements, project, Collections.emptyList()); } public static Collection<Pair<Element, ObjectNode>> buildElementPairs(Collection<Element> elements, Project project) { List<Pair<Element, ObjectNode>> elementPairs = new ArrayList<>(elements.size()); for (Element element : elements) { ObjectNode objectNode = Converters.getElementToJsonConverter().apply(element, project); if (objectNode == null) { continue; } elementPairs.add(new Pair<>(element, objectNode)); } return elementPairs; } @Override public void run(ProgressStatus progressStatus) { if (progressStatus == null) { progressStatus = EmptyProgressStatus.getDefault(); } progressStatus.setDescription("Mapping element(s)"); progressStatus.setIndeterminate(true); if (clientElements == null) { clientElements = new LinkedList<>(); } Map<String, Pair<Element, ObjectNode>> clientElementMap = clientElements.stream().collect(Collectors.toMap(pair -> Converters.getElementToIdConverter().apply(pair.getKey()), Function.identity())); // process the parsers against the lists, adding processed keys to processed sets in case of multiple returns Set<String> processedElementIds = new HashSet<>(); JsonToken current = null; for (File responseFile : serverElementFiles) { try (JsonParser jsonParser = JacksonUtils.getJsonFactory().createParser(responseFile)) { current = (jsonParser.getCurrentToken() == null ? jsonParser.nextToken() : jsonParser.getCurrentToken()); if (current != JsonToken.START_OBJECT) { throw new IOException("Unable to build object from JSON parser."); } while (current != JsonToken.END_OBJECT) { current = jsonParser.nextToken(); String keyName; if (current != null && (keyName = jsonParser.getCurrentName()) != null && keyName.equals("elements") && (current = jsonParser.nextToken()) == JsonToken.START_ARRAY) { current = jsonParser.nextToken(); JsonNode value; while (current != JsonToken.END_ARRAY) { if (current == JsonToken.START_OBJECT) { String id; ObjectNode currentServerElement = JacksonUtils.parseJsonObject(jsonParser); if ((value = currentServerElement.get(MDKConstants.ID_KEY)) != null && value.isTextual() && !processedElementIds.contains(id = value.asText())) { //remove element from client and server maps if present, add appropriate validations already processedElementIds.add(id); Pair<Element, ObjectNode> currentClientElement = clientElementMap.remove(id); if (currentClientElement == null) { addMissingInClientViolation(currentServerElement); } else { addElementEquivalenceViolation(currentClientElement, currentServerElement); } } } } } } } catch (IOException e) { // stuff } } if (serverElements == null) { serverElements = new LinkedList<>(); } Map<String, ObjectNode> serverElementMap = serverElements.stream().filter(json -> json.has(MDKConstants.ID_KEY) && json.get(MDKConstants.ID_KEY).isTextual()).filter(json -> !processedElementIds.contains(json.get(MDKConstants.ID_KEY).asText())) .collect(Collectors.toMap(json -> json.get(MDKConstants.ID_KEY).asText(), Function.identity())); LinkedHashSet<String> elementKeySet = new LinkedHashSet<>(); elementKeySet.addAll(clientElementMap.keySet()); elementKeySet.addAll(serverElementMap.keySet()); progressStatus.setDescription("Generating validation results for " + elementKeySet.size() + " element" + (elementKeySet.size() != 1 ? "s" : "")); progressStatus.setIndeterminate(false); progressStatus.setMax(elementKeySet.size()); progressStatus.setCurrent(0); for (String id : elementKeySet) { Pair<Element, ObjectNode> clientElement = clientElementMap.get(id); Element clientElementElement = clientElement != null ? clientElement.getKey() : null; ObjectNode clientElementObjectNode = clientElement != null ? clientElement.getValue() : null; ObjectNode serverElement = serverElementMap.get(id); if (clientElement.getKey() == null && serverElement == null) { continue; } else if (clientElement == null) { addMissingInClientViolation(serverElement); } else if (serverElement == null) { addMissingOnMmsViolation(clientElement); } else { addElementEquivalenceViolation(clientElement, serverElement); } progressStatus.increase(); } Application.getInstance().getGUILog().log("[INFO] --- Start " + validationSuite.getName() + " Summary ---"); Application.getInstance().getGUILog().log("[INFO] " + NumberFormat.getInstance().format(missingInClientCount) + " element" + (missingInClientCount != 1 ? "s are" : " is") + " missing in client."); Application.getInstance().getGUILog().log("[INFO] " + NumberFormat.getInstance().format(missingOnMmsCount) + " element" + (missingOnMmsCount != 1 ? "s are" : "is") + " missing on MMS."); Application.getInstance().getGUILog().log("[INFO] " + NumberFormat.getInstance().format(notEquivalentCount) + " element" + (notEquivalentCount != 1 ? "s are" : " is") + " not equivalent between client and MMS."); Application.getInstance().getGUILog().log("[INFO] --- End " + validationSuite.getName() + " Summary ---"); } private void addMissingInClientViolation(ObjectNode serverElement) { missingInClientCount++; JsonNode idJsonNode = serverElement.get(MDKConstants.ID_KEY); String id = idJsonNode != null ? idJsonNode.asText() : ""; JsonNode typeJsonNode = serverElement.get(MDKConstants.TYPE_KEY); String type = typeJsonNode != null ? typeJsonNode.asText("Element") : "Element"; JsonNode nameJsonNode = serverElement.get(MDKConstants.NAME_KEY); String name = nameJsonNode != null ? nameJsonNode.asText("<>") : "<>"; finishViolation(new ValidationRuleViolation(project.getPrimaryModel(), "[MISSING IN CLIENT] " + type + " " + name), id, null, serverElement, null); } private void addMissingOnMmsViolation(Pair<Element, ObjectNode> clientElement) { missingOnMmsCount++; String name = "<>"; if (clientElement.getKey() instanceof NamedElement && ((NamedElement) clientElement.getKey()).getName() != null && !((NamedElement) clientElement.getKey()).getName().isEmpty()) { name = ((NamedElement) clientElement.getKey()).getName(); } finishViolation(new ValidationRuleViolation(clientElement.getKey(), "[MISSING ON MMS] " + clientElement.getKey().getHumanType() + " " + name), clientElement.getKey().getID(), clientElement, null, null); } public void addElementEquivalenceViolation(Pair<Element, ObjectNode> clientElement, ObjectNode serverElement) { JsonNode diff = JsonPatchFunction.getInstance().apply(clientElement.getValue(), serverElement); if (diff != null && diff.size() != 0) { notEquivalentCount++; String name = "<>"; if (clientElement.getKey() instanceof NamedElement && ((NamedElement) clientElement.getKey()).getName() != null && !((NamedElement) clientElement.getKey()).getName().isEmpty()) { name = ((NamedElement) clientElement.getKey()).getName(); } finishViolation(new ValidationRuleViolation(clientElement.getKey(), "[NOT EQUIVALENT] " + clientElement.getKey().getHumanType() + " " + name), clientElement.getKey().getID(), clientElement, serverElement, diff); } } public void finishViolation(ValidationRuleViolation validationRuleViolation, String id, Pair<Element, ObjectNode> clientElement, ObjectNode serverElement, JsonNode diff) { validationRuleViolation.addAction(new CommitClientElementAction(id, clientElement != null ? clientElement.getKey() : null, clientElement != null ? clientElement.getValue() : null, project)); validationRuleViolation.addAction(new UpdateClientElementAction(id, clientElement != null ? clientElement.getKey() : null, serverElement, project)); ActionsCategory copyActionsCategory = new ActionsCategory("COPY", "Copy..."); copyActionsCategory.setNested(true); validationRuleViolation.addAction(copyActionsCategory); copyActionsCategory.addAction(new ClipboardAction("ID", id)); if (clientElement != null) { copyActionsCategory.addAction(new ClipboardAction("Element Hyperlink", "mdel://" + clientElement.getKey().getID())); try { copyActionsCategory.addAction(new ClipboardAction("Local JSON", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(clientElement.getValue()))); } catch (JsonProcessingException ignored) { } } if (serverElement != null) { try { copyActionsCategory.addAction(new ClipboardAction("MMS JSON", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(serverElement))); } catch (JsonProcessingException ignored) { } } if (diff != null) { try { copyActionsCategory.addAction(new ClipboardAction("Diff", JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(diff))); } catch (JsonProcessingException ignored) { } } elementEquivalenceValidationRule.addViolation(validationRuleViolation); invalidElements.put(id, new Pair<>(clientElement, serverElement)); } public ValidationSuite getValidationSuite() { return validationSuite; } public Map<String, Pair<Pair<Element, ObjectNode>, ObjectNode>> getInvalidElements() { return invalidElements; } }