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); } } }