package de.uniba.dsg.bpmnspector.refcheck;
import api.*;
import de.uniba.dsg.bpmnspector.refcheck.utils.ViolationMessageCreator;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.xpath.XPathHelper;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ReferenceChecker {
public static final String CONSTRAINT_REF_TYPE = "REF_TYPE";
public static final String CONSTRAINT_REF_EXISTENCE = "REF_EXISTENCE";
private final Map<String, BPMNElement> bpmnRefElements;
private static final org.slf4j.Logger LOGGER = LoggerFactory
.getLogger(ReferenceChecker.class.getSimpleName());
public ReferenceChecker(Map<String, BPMNElement> bpmnRefElements) {
this.bpmnRefElements = bpmnRefElements;
}
/**
* This method validates the current element against the checking reference.
* Some already existing information will be expected as parameters.
*
* @param elements
* the elements of the root file (= <code>bpmnFile</code>)
* @param importedElements
* the elements which have been defined in imported files
* @param validationResult
* the validation result for adding found violations
* @param currentElement
* the current element to validate
* @param line
* the line of the reference in the root file for the violation
* message
* @param column
* the column of the reference
* @param checkingReference
* the reference to validate against
* @param referencedId
* the referenced ID
* @param ownPrefix
* the namespace prefix of the current element
* @throws ValidationException thrown if errors occur during creation of the Violation
*/
public void validateReferenceType(Map<String, Element> elements,
Map<String, Map<String, Element>> importedElements,
ValidationResult validationResult, Element currentElement, int line,
int column, Reference checkingReference, String referencedId, String ownPrefix) throws ValidationException {
if (checkingReference.isQname()) {
// Check whether the QName is prefixed
if (referencedId.contains(":")) { // reference ID is prefixed and
// therefore probably to find in an imported file
String[] parts = referencedId.split(":");
String prefix = parts[0];
String importedId = parts[1];
String namespace = "";
for (Namespace nsp : currentElement.getNamespacesInScope()) {
if (nsp.getPrefix().equals(prefix)) {
namespace = nsp.getURI();
break;
}
}
Map<String, Element> relevantImportedElements = importedElements
.get(namespace);
// Checking whether the namespace is used for the root and/or the imported file
// to determine the correct element to be checked
if (ownPrefix.equals(prefix)) { // namespace is used for the root file
// check whether element with ID importedId exists in root or another file
if (elements.containsKey(importedId)) { // elem is in root file
checkTypeAndAddViolation(validationResult, line, column, currentElement, checkingReference,
elements.get(importedId));
} else if (relevantImportedElements != null
&& relevantImportedElements.containsKey(importedId)) { // element is found in the imported file
checkTypeAndAddViolation(validationResult, line, column, currentElement, checkingReference,
relevantImportedElements.get(importedId));
} else {
createAndAddExistenceViolation(validationResult, line, column, currentElement, checkingReference);
}
} else if (relevantImportedElements != null) { // NOPMD
// namespace is used by an imported file
if (relevantImportedElements.containsKey(importedId)) {
checkTypeAndAddViolation(validationResult, line, column, currentElement, checkingReference,
relevantImportedElements.get(importedId));
} else {
createAndAddExistenceViolation(validationResult, line, column, currentElement, checkingReference);
}
} else {
// invalid namespace: not used in any file case if the namespace is not used for the root file or an
// imported file import does not exist or is no BPMN file (as it has to be)
createAndAddExistenceViolation(validationResult, line, column, currentElement, checkingReference);
}
} else { // no QName prefix - the elem has to be in the root file
if (elements.containsKey(referencedId)) {
checkTypeAndAddViolation(validationResult, line, column, currentElement, checkingReference,
elements.get(referencedId));
} else {
createAndAddExistenceViolation(validationResult, line, column, currentElement, checkingReference);
}
}
} else { // reference uses IDREF - ref has to exist in root file
if (elements.containsKey(referencedId)) {
checkTypeAndAddViolation(validationResult, line, column, currentElement, checkingReference,
elements.get(referencedId));
} else {
createAndAddExistenceViolation(validationResult, line, column, currentElement, checkingReference);
}
}
}
/**
* This method validates the type of the referenced element against the
* checking reference and adds a violation if found.
*
* @param validationResult
* the violation list for adding found violations
* @param line
* the line of the reference in the root file for the violation
* message
* @param column
* the column of the reference
* @param currentElement
* the current element to validate
* @param checkingReference
* the reference to validate against
* @param referencedElement
* the referenced element to validate
* @throws ValidationException thrown if errors occur during creation of the Violation
*/
private void checkTypeAndAddViolation(ValidationResult validationResult, int line, int column,
Element currentElement, Reference checkingReference,
Element referencedElement) throws ValidationException {
boolean validType = false;
List<String> referencedTypes = checkingReference.getTypes();
// do not check references which are only for existence validation
if (referencedTypes != null) {
// find all possible types (with subtypes/children)
List<String> types = new ArrayList<>(referencedTypes);
boolean childfound;
do {
childfound = false;
List<String> typesCopy = new ArrayList<>(types);
for (String type : typesCopy) {
if (bpmnRefElements.containsKey(type)) {
BPMNElement bpmnElement = bpmnRefElements.get(type);
List<String> children = bpmnElement.getChildren();
if (children != null) {
for (String child : children) {
if (!typesCopy.contains(child)) {
types.add(child);
childfound = true;
}
}
}
}
}
} while (childfound);
// validate if the referenced element has one of the correct types
for (String type : types) {
if (referencedElement.getName().equals(type)) {
validType = true;
break;
}
}
if (!validType) {
createAndAddReferenceTypeViolation(validationResult, line, column,
currentElement, checkingReference, referencedElement,
types);
}
}
}
/**
* Creates and adds a found reference type violation to the list of
* violations.
*
* @param validationResult
* the validationResult for adding the found violation
* @param line
* the line of the reference in the root file
* @param column
* the column of the reference
* @param currentElement
* the element causing the violation
* @param checkingReference
* the violated reference
* @param referencedElement
* the actually referenced element
* @param types
* a list of allowed reference types
* @throws ValidationException thrown if the Validation could not be created
*/
private void createAndAddReferenceTypeViolation(ValidationResult validationResult, int line, int column,
Element currentElement, Reference checkingReference, Element referencedElement, List<String> types) throws ValidationException {
String message = ViolationMessageCreator.createTypeViolationMessage(currentElement.getName(), line,
checkingReference.getName(), referencedElement.getName(), types.toString());
Violation violation = new Violation(createLocation(line, column, currentElement), message, CONSTRAINT_REF_TYPE);
validationResult.addViolation(violation);
}
/**
* Adds a found existence violation to the list.
*
* @param validationResult
* the validation result for adding the found violation
* @param line
* the line of the reference in the root file
* @param column
* the column of the reference in the root file
* @param currentElement
* the element causing the violation
* @param checkingReference
* the violated reference
* @throws ValidationException thrown if the Validation could not be created
*/
public void createAndAddExistenceViolation(ValidationResult validationResult,
int line, int column, Element currentElement,
Reference checkingReference) throws ValidationException {
String message = ViolationMessageCreator
.createExistenceViolationMessage(currentElement.getName(), checkingReference.getName(), line,
ViolationMessageCreator.DEFAULT_MSG, XPathHelper.getAbsolutePath(currentElement));
Violation violation = new Violation(createLocation(line, column, currentElement), message, CONSTRAINT_REF_EXISTENCE);
validationResult.addViolation(violation);
}
private Location createLocation(int line, int column, Element currentElement) throws ValidationException {
try {
URI baseUri = currentElement.getXMLBaseURI();
Path locationPath = Paths.get(baseUri);
return new Location(locationPath,
new LocationCoordinate(line, column));
} catch (URISyntaxException e) {
throw new ValidationException("Base URI of current element " + currentElement.getName()
+ " could not be restored.", e);
}
}
}