package de.uniba.dsg.bpmnspector.refcheck; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import api.ValidationException; import api.ValidationResult; import api.Violation; import de.uniba.dsg.bpmnspector.BpmnProcessValidator; import de.uniba.dsg.bpmnspector.common.importer.BPMNProcess; import de.uniba.dsg.bpmnspector.refcheck.utils.JDOMUtils; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.Namespace; import org.jdom2.filter.Filter; import org.jdom2.filter.Filters; import org.jdom2.located.LocatedElement; import org.jdom2.util.IteratorIterable; import org.slf4j.LoggerFactory; /** * @author Andreas Vorndran * @author Matthias Geiger * @version 1.0 * @see ValidationResult * @see ValidationException * */ public class BPMNReferenceValidator implements BpmnProcessValidator { private Map<String, BPMNElement> bpmnRefElements; private final ReferenceChecker referenceChecker; private static final String RESULT_TEXT_TEMPLATE = "Reference check of file %s finished; %d violations found."; private static final org.slf4j.Logger LOGGER = LoggerFactory .getLogger(BPMNReferenceValidator.class.getSimpleName()); /** * Constructor sets the defaults. And loads the reference definitions to be used for validations. * * @throws ValidationException * if problems with the language files exist */ public BPMNReferenceValidator() throws ValidationException { loadReferences(); referenceChecker = new ReferenceChecker(bpmnRefElements); } /** * This method validates a set of BPMN processes given in the parameter * process regarding reference correctness, i.e., exist all references and are they referencing an allowed * BPMN element. * * @param process * the base process which should be checked; must not be <code>null</code> * @param validationResult * the ValidationResult to be used; must not be <code>null</code> * @throws ValidationException * if technical problems occurred * @throws NullPointerException * if one of the parameters is <code>null</code> */ @Override public void validate(BPMNProcess process, ValidationResult validationResult) throws ValidationException { Objects.requireNonNull(process, "process must not be null."); Objects.requireNonNull(validationResult, "validationResult must not be null."); try { List<BPMNProcess> processesToCheck = new ArrayList<>(); process.getAllProcessesRecursively(processesToCheck); for (BPMNProcess processToCheck : processesToCheck) { startValidation(processToCheck, validationResult); } String resultText = String .format(RESULT_TEXT_TEMPLATE, process.getBaseURI(), validationResult.getViolations().size()); LOGGER.info(resultText); } catch (ValidationException e) { LOGGER.error("Validation of process {} failed, due to an error: {}", process.getBaseURI(), e); } } /** * This method loads the BPMN elements with the checkable references for the * validation. * * @throws ValidationException * if technical problems with the files "references.xml" and * "references.xsd" occurred */ private void loadReferences() throws ValidationException { ReferenceLoader referenceLoader = new ReferenceLoader(); bpmnRefElements = referenceLoader.load("/references.xml", "/references.xsd"); StringBuilder bpmnElementsLogText = new StringBuilder(500); bpmnElementsLogText.append("Imported BPMNElements: "); for (String key : bpmnRefElements.keySet()) { bpmnElementsLogText.append(String.format("%s :: %s", key, bpmnRefElements.get(key))); bpmnElementsLogText.append(System.lineSeparator()); } LOGGER.debug(bpmnElementsLogText.toString()); } /** * This method validates the BPMN process stored in the parameter baseProcess. * * @param baseProcess * the ProcessFileSet of the file, which should be validated * @param validationResult * the ValidationResult to be used to store the validation results * @throws ValidationException * if technical problems occurred */ private void startValidation(BPMNProcess baseProcess, ValidationResult validationResult) throws ValidationException { LOGGER.debug("Starting to process {} :", baseProcess.getBaseURI()); Document baseDocument = baseProcess.getProcessAsDoc(); // Get all Elements to Check from Base BPMN Process Map<String, Element> elements = JDOMUtils.getAllElements(baseDocument); String ownPrefix = ""; // special case if a prefix is used for the target namespace Element rootNode = baseDocument.getRootElement(); String targetNamespace = rootNode.getAttributeValue("targetNamespace"); if(targetNamespace!=null) { for (Namespace namespace : rootNode.getNamespacesInScope()) { if (targetNamespace.equals(namespace.getURI())) { ownPrefix = namespace.getPrefix(); break; } } } LOGGER.debug("ownprefix after getAllElements():" + ownPrefix); // Store all Elements and their IDs in referenced Files into nested Map: // outerKey: namespace, innerKey: Id Map<String, Map<String, Element>> importedElements = new HashMap<>(); JDOMUtils.getAllElementsGroupedByNamespace(importedElements, baseProcess, new ArrayList<>()); // log ns prefixes of all imported files StringBuilder importedFilesLogText = new StringBuilder(100) .append("Elements found by namespace: ") .append(System.lineSeparator()); for (Map.Entry entry : importedElements.entrySet()) { importedFilesLogText .append("Namespace prefix: ") .append(entry.getKey()).append(System.lineSeparator()) .append(entry.getValue()) .append(System.lineSeparator()); } LOGGER.debug(importedFilesLogText.toString()); // get all elements of the file to validate their references Filter<Element> filter = Filters.element(); IteratorIterable<Element> list = baseDocument.getDescendants(filter); while (list.hasNext()) { Element currentElement = list.next(); String currentName = currentElement.getName(); // check if the current element can have references if (bpmnRefElements.containsKey(currentName)) { for (Reference checkingReference : bpmnRefElements.get(currentName).getReferences()) { // try to get the reference ID String referencedId = null; int line = -1; int column = -1; if (checkingReference.isAttribute()) { referencedId = currentElement .getAttributeValue(checkingReference .getName()); line = ((LocatedElement) currentElement).getLine(); column = ((LocatedElement) currentElement).getColumn(); } else { for (Element child : currentElement.getChildren()) { if (child.getName().equals( checkingReference.getName())) { referencedId = child.getText(); line = ((LocatedElement) child).getLine(); column = ((LocatedElement) child).getColumn(); break; } } } // if the current element has the reference start // the validation if (referencedId != null) { LOGGER.debug(String.format("Checking the Reference: %s :: %s", currentName, checkingReference.getName())); referenceChecker.validateReferenceType(elements, importedElements, validationResult, currentElement, line, column, checkingReference, referencedId, ownPrefix); } } } } // log violations if (!validationResult.isValid()) { StringBuilder violationListLogText = new StringBuilder("VIOLATIONSLIST:") .append(System.lineSeparator()); for (Violation violation : validationResult.getViolations()) { violationListLogText.append(violation.getMessage()).append( System.lineSeparator()); } LOGGER.info(violationListLogText.toString()); } } }