package de.uniba.dsg.bpmnspector.schematron.preprocessing;
import de.uniba.dsg.bpmnspector.common.importer.BPMNProcess;
import de.uniba.dsg.bpmnspector.common.util.ConstantHelper;
import de.uniba.dsg.bpmnspector.refcheck.utils.JDOMUtils;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Does the preprocessing step for creating only one document contenting
* everything and a namespace table
*
* @author Philipp Neugebauer
* @author Matthias Geiger
* @version 1.0
*
*/
public class PreProcessor {
private static final Logger LOGGER;
private static final String ALL_QNAME_ATTRIBUTES = "//bpmn:*/@sourceRef | //bpmn:*/@targetRef | "
+ "//bpmn:*/@calledElement | //bpmn:*/@processRef | "
+ "//bpmn:*/@dataStoreRef | //bpmn:*/@categoryValueRef | //bpmn:*/@messageRef | "
+ "//bpmn:*/@correlationKeyRef | //bpmn:*/@correlationPropertyRef | //bpmn:*/@structureRef |"
+ "//bpmn:*/@evaluatesToTypeRef | //bpmn:*/@itemRef | //bpmn:*/@type | "
+ "//bpmn:*/@definitionalCollaborationRef | //bpmn:*/@parameterRef | //bpmn:*/@operationRef |"
+ "//bpmn:*/@calledElement | //bpmn:*/@itemSubjectRef | //bpmn:*/@dataStoreRef | "
+ "//bpmn:*/@attachedToRef | //bpmn:*/@activityRef | //bpmn:*/@errorRef | //bpmn:*/@escalationRef | "
+ "//bpmn:*/@source | //bpmn:*/@target | //bpmn:*/@signalRef | //bpmn:*/@partitionElementRef";
private static final String ALL_IDREF_ATTRIBUTES = "//bpmn:*/@sourceRef | //bpmn:*/@targetRef | //bpmn:*/@default |"
+ "//bpmn:*/@inputDataRef | //bpmn:*/@outputDataRef | //bpmn:*/@dataObjectRef";
private static final String ALL_QNAME_ELEMENTS = "//bpmn:eventDefinitionRef | "
+ "//bpmn:incoming | //bpmn:outgoing | //bpmn:dataInputRefs | "
+ "//bpmn:dataOutputRefs | //bpmn:correlationPropertyRef | //bpmn:categoryValueRef |"
+ "//bpmn:inMessageRef | //bpmn:outMessageRef | //bpmn:errorRef | //bpmn:choreographyRef |"
+ "//bpmn:interfaceRef | //bpmn:endPointRef | //bpmn:participantRef | //bpmn:innerParticipantRef |"
+ "//bpmn:outerParticipantRef | //bpmn:supports | //bpmn:resourceRef | //bpmn:supportedInterfaceRefs |"
+ "//bpmn:loopDataInputRef | //bpmn:loopDataOutputRef | //bpmn:oneBehaviorEventRef |"
+ "//bpmn:noneBehaviorEventRef";
private static final String ALL_IDREF_ELEMENTS = "//bpmn:dataInputRefs | "
+ "//bpmn:optionalInputRefs | //bpmn:whileExecutingInputRefs | //bpmn:outputSetRefs | "
+ "//bpmn:dataOutputRefs | //bpmn:optionalOutputRefs | //bpmn:whileExecutingOutputRefs | "
+ "//bpmn:inputSetRefs | //bpmn:sourceRef | //bpmn:targetRef | //bpmn:flowNodeRef";
private final XPathFactory xPathFactory = XPathFactory.instance();
static {
LOGGER = LoggerFactory.getLogger(PreProcessor.class.getSimpleName());
}
/**
*
* Performs the pre-processing step for creating one document including the
* content of all imported BPMN processes of the document
*
* @param process
* the BPMNProcess which should be used as starting point
* @return a cloned org.jdom2.Document representation of the BPMN process
* including all imported nodes
*/
public Document preProcess(BPMNProcess process) {
LOGGER.info("Starting to preprocess file: " + process.getBaseURI());
Document cloneOfDoc = process.getProcessAsDoc().clone();
// Preprocessing can be skipped if no files are imported and there is no Prefix used for the targetNamespace
if(process.getChildren().isEmpty() && !JDOMUtils.getUsedPrefixForTargetNamespace(cloneOfDoc).isPresent()) {
LOGGER.info("Skipping preprocessing.");
return cloneOfDoc;
}
// get all nodes which potentially refer to other files
List<Attribute> refAttributes = createXPathExpressionForAllQNameAttributes().evaluate(cloneOfDoc);
List<Element> refElements = createXPathExpressionForAllQNameElements().evaluate(cloneOfDoc);
// for each attribute: rename IDs which can be used globally
refAttributes.stream()
.filter(attribute -> attribute.getValue().contains(":"))
.forEach(attribute -> renameGlobalIds(process, attribute));
// for each element: rename IDs which can be used globally
refElements.stream().filter(element -> element.getText().contains(":"))
.forEach(element -> renameGlobalIds(process, element));
// for each import: perform further processing
for(BPMNProcess imported : process.getChildren()){
// rename IDs in imported file
renameIds(imported);
LOGGER.debug("Checking imported importedProcess for further imports.");
Document result = preProcess(imported);
LOGGER.debug("integration of document will be done now");
addNodesToDocument(result, cloneOfDoc);
}
LOGGER.info("Preprocessing of file {} completed.", process.getBaseURI());
return cloneOfDoc;
}
/**
* helper method to rename all global ids in the bpmn files to be able to
* merge them into one file and to detect later the errors in the right
* files. the colon is replaced by an underscore because ids in the one
* merged file can't contain colons
*
* @param process
* the document which should be validated
* @param attribute
* the attribute with the ID to be renamed
*/
private void renameGlobalIds(BPMNProcess process, Attribute attribute) {
String prefix = attribute.getValue().substring(0,
attribute.getValue().indexOf(":"));
String newPrefix;
// special treatment if a prefix is used for the local targetNamespace
if(prefix.equals(JDOMUtils.getUsedPrefixForTargetNamespace(process.getProcessAsDoc()).orElse(""))) {
// replaces usage of 'tns:ID' with 'ID'
newPrefix = "";
} else {
newPrefix = getNewPrefixByOldPrefix(process, prefix)+"_";
}
String newId = attribute.getValue().replace(prefix + ":", newPrefix);
LOGGER.debug("new prefix '{}' for ID '{}' - new ID: {}", newPrefix, attribute.getValue(), newId);
attribute.setValue(newId);
}
/**
* helper method to rename all global ids in the bpmn files to be able to
* merge them into one file and to detect later the errors in the right
* files. the colon is replaced by an underscore because ids in the one
* merged file can't contain colons
*
* @param process
* the document which should be validated
* @param element
* the element with the ID to be renamed
*/
private void renameGlobalIds(BPMNProcess process, Element element) {
String prefix = element.getText().substring(0,
element.getText().indexOf(":"));
String newPrefix;
// special treatment if a prefix is used for the local targetNamespace
if(prefix.equals(JDOMUtils.getUsedPrefixForTargetNamespace(process.getProcessAsDoc()).orElse(""))) {
// replaces usage of 'tns:ID' with 'ID'
newPrefix = "";
} else {
newPrefix = getNewPrefixByOldPrefix(process, prefix)+"_";
}
String newId = element.getText().replace(prefix + ":", newPrefix);
LOGGER.debug("new prefix '{}' for ID '{}' - new ID: {}", newPrefix, element.getText(), newId);
element.setText(newId);
}
private String getNewPrefixByOldPrefix(BPMNProcess process,
String oldPrefix) {
Namespace namespace = process.getProcessAsDoc().getRootElement().getNamespace(
oldPrefix);
String nspURI = "";
if(namespace!=null) {
nspURI = namespace.getURI();
}
String newPrefix = "";
for (BPMNProcess imported : process.getChildren()) {
if (nspURI.equals(imported.getNamespace())) {
newPrefix = imported.getGeneratedPrefix();
}
}
return newPrefix;
}
/**
* adds to all nodes new and unique prefixes in the given document for the
* validation process and violation searching
*
* @param importedProcess the process in which the ids should be changed
*/
private void renameIds(BPMNProcess importedProcess) {
List<Attribute> attributes = createXPathExpressionForAllIDsAndReferenceAttributes().evaluate(importedProcess.getProcessAsDoc());
List<Element> elements = createXPathExpressionForAllReferenceElements().evaluate(importedProcess.getProcessAsDoc());
attributes.stream().filter(x -> !x.getValue().contains(":"))
.forEach(x -> {
LOGGER.debug("Updating attribute ID '{}' new value: {}", x.getValue(), importedProcess.getGeneratedPrefix()+"_"+x.getValue());
x.setValue(importedProcess.getGeneratedPrefix()+"_"+x.getValue());
});
elements.stream().filter(x-> !x.getText().contains(":"))
.forEach(x -> {
LOGGER.debug("Updating element {} ID '{}' new value: {}", x.getName(), x.getText(), importedProcess.getGeneratedPrefix()+"_"+x.getText());
x.setText(importedProcess.getGeneratedPrefix()+"_"+x.getText());
});
}
/**
* adds the children of importDefinitionsNode to the definitionsNode of the
* given headFileDocument
*
* @param importedProcess
* the definitionsNode of the document, which should be added to
* the headFileDocument
* @param headDocument
* the document, where the nodes should be added
*
*/
private void addNodesToDocument(Document importedProcess,
Document headDocument) {
Element definitionsHead = headDocument.getRootElement();
Element definitionsImported = importedProcess.getRootElement();
for(Element element : definitionsImported.getChildren()) {
Element clone = element.clone();
definitionsHead.addContent(clone);
}
}
/**
* Creates an XPathExpression filtering all Attributes which are used to reference another BPMN element
*/
private XPathExpression<Attribute> createXPathExpressionForAllQNameAttributes() {
return xPathFactory.compile(ALL_QNAME_ATTRIBUTES, Filters.attribute(), null,
getBpmnNamespace());
}
/**
* Creates an XPathExpression filtering all Elements which are used to reference another BPMN element
*/
private XPathExpression<Element> createXPathExpressionForAllQNameElements() {
return xPathFactory.compile(ALL_QNAME_ELEMENTS, Filters.element(), null,
getBpmnNamespace());
}
/**
* Creates an XPathExpression filtering all Attributes which define and use IDs
* (regardless of the used type (ID, IDREF or QName))
*/
private XPathExpression<Attribute> createXPathExpressionForAllIDsAndReferenceAttributes() {
String expStr = "//bpmn:*/@id | " + ALL_IDREF_ATTRIBUTES +" | "+ ALL_QNAME_ATTRIBUTES;
return xPathFactory.compile(expStr, Filters.attribute(), null,
getBpmnNamespace());
}
/**
* Creates an XPathExpression filtering all Elements which use IDs
* (regardless of the used type (ID, IDREF or QName))
*/
private XPathExpression<Element> createXPathExpressionForAllReferenceElements() {
String expStr = ALL_IDREF_ELEMENTS + " | " + ALL_QNAME_ELEMENTS;
return xPathFactory.compile(expStr, Filters.element(), null,
getBpmnNamespace());
}
private Namespace getBpmnNamespace() {
return Namespace.getNamespace("bpmn", ConstantHelper.BPMNNAMESPACE);
}
}