package de.uniba.dsg.bpmnspector.refcheck;
import api.ValidationException;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* This class loads the references form the references.xml file. The XML file
* needs to fit the references.xsd schema. The references will be structured by
* the classes {@link BPMNElement} and {@link Reference}.
*
* @author Andreas Vorndran
* @author Matthias Geiger
*
* @version 1.0
* @see BPMNElement
* @see Reference
*
*/
public class ReferenceLoader {
private final List<SAXParseException> xsdErrorList;
/**
* Constructor
*/
public ReferenceLoader() {
xsdErrorList = new ArrayList<>();
}
/**
* This method loads the XML file and gives the data structure of
* BPMNElements and References back.
*
* @param referencesPath
* path to the XML file with the references
* @param xsdPath
* path to the XSD file for the references
* @return a hash map with the element names as keys and as value the
* BPMNElements which contain the references
* @throws ValidationException
* if problems occurred while loading, traversing or checking
* the 'references.xml' file against its XSD
*/
public Map<String, BPMNElement> load(String referencesPath,
String xsdPath) throws ValidationException {
try (InputStream refPathStream = getClass().getResourceAsStream(referencesPath)) {
validateReferencesFile(referencesPath, xsdPath);
Document document = new SAXBuilder().build(refPathStream);
Element root = document.getRootElement();
Map<String, BPMNElement> bpmnElements = new HashMap<>();
List<Element> elems = root.getChildren();
// Create References for all <element>'s in the file which do not have a defined parent
// all other <element>'s will be processed when their parent is processed
for(Element elem : elems) {
if(elem.getChild("parent")==null && !bpmnElements.containsKey(elem.getAttributeValue("name"))) {
createBPMNElementAndReferences(root, elem, bpmnElements, new ArrayList<>());
}
}
return bpmnElements;
} catch (JDOMException | IOException e) {
throw new ValidationException("Problems occurred while traversing the file '"+referencesPath+"'", e);
}
}
private void createBPMNElementAndReferences(Element rootElem, Element elementToAdd,
Map<String, BPMNElement> processedElements,
List<Reference> referencesFromParent) {
String elementName = elementToAdd.getAttributeValue("name");
String parent = elementToAdd.getChildText("parent");
// create list of references
// use referencesFromParent as a base - references are inherited
List<Reference> references = new ArrayList<>(referencesFromParent);
// add further References directly defined for the current elementToAdd
createReferencesOfElement(elementToAdd, references);
List<String> children = null;
// separates possible child elements (element names of the
// element, which inherits the references from the current
// element)
List<Element> childElements= elementToAdd.getChildren("child");
if (!childElements.isEmpty()) {
children = new ArrayList<>();
for (Element child : childElements) {
children.add(child.getText());
// process child
createBPMNElementAndReferencesForChild(rootElem, child.getText(), elementName, processedElements, references);
}
}
BPMNElement bpmnElement = new BPMNElement(elementName, parent, children, references);
// add newly created Element to Map of all processed elements
processedElements.put(elementName, bpmnElement);
}
private void createBPMNElementAndReferencesForChild(Element rootElem, String nameOfChild, String nameOfParent,
Map<String, BPMNElement> processedElements,
List<Reference> referencesFromParent) {
// Check whether there is a child which defines further references,
// i.e., there exists a <element name=$nameOfChild>
Optional<Element> childToProcess = rootElem.getChildren("element").stream()
.filter(element -> nameOfChild.equals(element.getAttributeValue("name")))
.findAny();
if(childToProcess.isPresent()) {
// further definitions for child are present -> process child
createBPMNElementAndReferences(rootElem, childToProcess.get(), processedElements, referencesFromParent);
} else {
// no further references for child -> create BPMNElement for Child and add child directly with referencesFromParent
processedElements.put(nameOfChild, new BPMNElement(nameOfChild, nameOfParent, null, referencesFromParent));
}
}
private void createReferencesOfElement(Element element, List<Reference> referenceList) {
List<Element> referencesInFile = element
.getChildren("reference");
for (Element reference : referencesInFile) {
int number = Integer.parseInt(reference.getAttributeValue("number"));
String referenceName = reference.getChild("name").getText();
ArrayList<String> types = null;
List<Element> typesInFile = reference.getChildren("type");
if (!typesInFile.isEmpty()) {
types = new ArrayList<>();
for (Element type : typesInFile) {
types.add(type.getText());
}
}
boolean qname = convertToBoolean(reference
.getAttributeValue("qname"));
boolean attribute = convertToBoolean(reference
.getAttributeValue("attribute"));
Reference bpmnReference = new Reference(number,
referenceName, types, qname, attribute);
referenceList.add(bpmnReference);
}
}
private void validateReferencesFile(String referencesPath, String xsdPath)
throws ValidationException, IOException {
try (InputStream refPathStream = getClass().getResourceAsStream(referencesPath);
InputStream xsdPathStream = getClass().getResourceAsStream(xsdPath)) {
SchemaFactory schemaFactory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory
.newSchema(new StreamSource(xsdPathStream));
Validator validator = schema.newValidator();
validator.setErrorHandler(new XSDValidationLoggingErrorHandler());
validator.validate(new StreamSource(refPathStream));
if (!xsdErrorList.isEmpty()) {
StringBuilder xsdErrorText = new StringBuilder(200)
.append("While the XSD validation of the 'references.xml' file the following errors occurred:")
.append(System.lineSeparator());
for (SAXParseException saxParseException : xsdErrorList) {
xsdErrorText.append("line: ")
.append(saxParseException.getLineNumber())
.append(':')
.append(saxParseException.getMessage())
.append(System.lineSeparator());
}
throw new ValidationException(xsdErrorText.toString());
}
} catch (SAXException e) {
throw new ValidationException("Problems occurred while trying to check the references XML file against the corresponding XSD file.", e);
}
}
private boolean convertToBoolean(String string) {
return "true".equals(string);
}
/**
* Inner class for getting the XSD violations.
*
* @author Andreas Vorndran
*
*/
private class XSDValidationLoggingErrorHandler implements ErrorHandler {
@Override
public void error(SAXParseException exception) throws SAXException {
xsdErrorList.add(exception);
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
xsdErrorList.add(exception);
}
@Override
public void warning(SAXParseException exception) throws SAXException {
xsdErrorList.add(exception);
}
}
}