/*******************************************************************************
* Copyright 2017 Graz University of Technology
* EAAF-Core Components has been developed in a cooperation between EGIZ,
* A-SIT Plus, A-SIT, and Graz University of Technology.
*
* Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
* https://joinup.ec.europa.eu/news/understanding-eupl-v12
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*******************************************************************************/
/*******************************************************************************
*******************************************************************************/
/*******************************************************************************
*******************************************************************************/
package at.gv.egiz.eaaf.core.impl.idp.process;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.EventReaderDelegate;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import at.gv.egiz.eaaf.core.impl.idp.process.model.EndEvent;
import at.gv.egiz.eaaf.core.impl.idp.process.model.ProcessDefinition;
import at.gv.egiz.eaaf.core.impl.idp.process.model.ProcessNode;
import at.gv.egiz.eaaf.core.impl.idp.process.model.StartEvent;
import at.gv.egiz.eaaf.core.impl.idp.process.model.TaskInfo;
import at.gv.egiz.eaaf.core.impl.idp.process.model.Transition;
/**
* Parses an XML representation of a process definition as defined by the respective XML schema.
*
transitionElements = new ArrayList<>();
final List startEvents = new ArrayList<>();
reader = new EventReaderDelegate(reader) {
@Override
public XMLEvent nextEvent() throws XMLStreamException {
XMLEvent event = super.nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
StartElement element = event.asStartElement();
QName qname = element.getName();
if (NS.equals(qname.getNamespaceURI())) {
log.trace("Found process description element '{}'.", qname.getLocalPart());
Attribute id = element.getAttributeByName(new QName("id"));
switch (qname.getLocalPart()) {
case "ProcessDefinition":
if (id != null) {
pd.setId(id.getValue());
}
break;
case "StartEvent":
StartEvent startEvent = new StartEvent();
if (id != null) {
startEvent.setId(id.getValue());
}
startEvents.add(startEvent);
break;
case "EndEvent":
EndEvent endEvent = new EndEvent();
if (id != null) {
endEvent.setId(id.getValue());
pd.getEndEvents().put(id.getValue(), endEvent);
}
break;
case "Transition":
transitionElements.add(element);
break;
case "Task":
TaskInfo taskInfo = new TaskInfo();
if (id != null) {
taskInfo.setId(id.getValue());
pd.getTaskInfos().put(id.getValue(), taskInfo);
}
Attribute async = element.getAttributeByName(new QName("async"));
if (async != null) {
taskInfo.setAsync(Boolean.valueOf(async.getValue()));
}
Attribute implementingClass = element.getAttributeByName(new QName("class"));
if (implementingClass != null) {
taskInfo.setTaskImplementingClass(implementingClass.getValue());
}
break;
}
}
break;
}
return event;
}
};
// validator is not thread-safe
Validator validator = LazyProcessDefinitionSchemaHolder.PD_SCHEMA_INSTANCE.newValidator();
validator.validate(new StAXSource(reader));
log.trace("Process definition successfully schema validated.");
// perform some basic checks
log.trace("Building model and performing some plausibility checks.");
if (startEvents.size() != 1) {
throw new ProcessDefinitionParserException("A ProcessDefinition must contain exactly one single StartEvent.");
}
pd.setStartEvent(startEvents.get(0));
// link transitions
Iterator transitions = transitionElements.iterator();
while (transitions.hasNext()) {
StartElement element = transitions.next();
Transition transition = new Transition();
Attribute id = element.getAttributeByName(new QName("id"));
if (id != null) {
transition.setId(id.getValue());
}
Attribute conditionExpression = element.getAttributeByName(new QName("conditionExpression"));
if (conditionExpression != null) {
transition.setConditionExpression(conditionExpression.getValue());
}
Attribute from = element.getAttributeByName(new QName("from"));
if (from != null) {
ProcessNode fromNode = pd.getProcessNode(from.getValue());
if (fromNode == null) {
throw new ProcessDefinitionParserException("Transition's 'from'-attribute refers to a non-existing event or task '" + from.getValue() + '.');
}
if (fromNode instanceof EndEvent) {
throw new ProcessDefinitionParserException("Transition cannot start from end event.");
}
transition.setFrom(fromNode);
fromNode.getOutgoingTransitions().add(transition);
}
Attribute to = element.getAttributeByName(new QName("to"));
if (to != null) {
ProcessNode toNode = pd.getProcessNode(to.getValue());
if (toNode == null) {
throw new ProcessDefinitionParserException("Transition's 'to'-attribute refers to a non-existing event or task '" + to.getValue() + '.');
}
transition.setTo(toNode);
toNode.getIncomingTransitions().add(transition);
}
if (transition.getConditionExpression() == null && Objects.equals(transition.getFrom(), transition.getTo())) {
throw new ProcessDefinitionParserException("Transition's 'from' equals its 'to'. Since no 'conditionExpression' has been set this will cause a loop.");
}
}
log.debug("Process definition '{}' successfully parsed.", pd.getId());
return pd;
} catch (ProcessDefinitionParserException e) {
throw e;
} catch (XMLStreamException|IOException e) {
throw new ProcessDefinitionParserException("Unable to read process definition from inputstream.", e);
} catch (SAXException e) {
throw new ProcessDefinitionParserException("Schema validation of process description failed.", e);
} catch (Exception e) {
throw new ProcessDefinitionParserException("Internal error creating process definition from inputstream.", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (XMLStreamException e) {
// error freeing resources
}
}
}
}
}