/* Copyright 2012 Thorben Lindhauer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.engine.impl.bpmn.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.ProcessDefinitionImpl;
import org.activiti.engine.impl.pvm.process.ScopeImpl;
import org.activiti.engine.impl.util.xml.Element;
import org.activiti.engine.repository.ProcessDefinition;
import de.unipotsdam.hpi.thorben.ppi.GreaterThanFunction;
import de.unipotsdam.hpi.thorben.ppi.LowerThanFunction;
import de.unipotsdam.hpi.thorben.ppi.PPI;
import de.unipotsdam.hpi.thorben.ppi.condition.ActivityEndCondition;
import de.unipotsdam.hpi.thorben.ppi.condition.ActivityStartCondition;
import de.unipotsdam.hpi.thorben.ppi.condition.PPICondition;
import de.unipotsdam.hpi.thorben.ppi.measure.instance.CountMeasure;
import de.unipotsdam.hpi.thorben.ppi.measure.instance.DataMeasure;
import de.unipotsdam.hpi.thorben.ppi.measure.instance.TimeMeasure;
import de.unipotsdam.hpi.thorben.ppi.measure.instance.entity.BaseMeasureInstance;
import de.unipotsdam.hpi.thorben.ppi.measure.instance.entity.CountMeasureInstance;
import de.unipotsdam.hpi.thorben.ppi.measure.instance.entity.DataMeasureInstance;
import de.unipotsdam.hpi.thorben.ppi.measure.instance.entity.TimeMeasureInstance;
import de.unipotsdam.hpi.thorben.ppi.measure.process.AggregatedMeasure;
import de.unipotsdam.hpi.thorben.ppi.measure.process.AggregationFunction;
import de.unipotsdam.hpi.thorben.ppi.measure.process.AverageFunction;
import de.unipotsdam.hpi.thorben.ppi.measure.process.DerivedProcessMeasure;
import de.unipotsdam.hpi.thorben.ppi.measure.process.MaximumFunction;
import de.unipotsdam.hpi.thorben.ppi.measure.process.MinimumFunction;
import de.unipotsdam.hpi.thorben.ppi.measure.process.ProcessMeasure;
import de.unipotsdam.hpi.thorben.ppi.measure.process.SumFunction;
import de.unipotsdam.hpi.thorben.ppi.measure.process.TargetFunction;
import de.unipotsdam.hpi.thorben.ppi.typehelper.DoubleHelper;
import de.unipotsdam.hpi.thorben.ppi.typehelper.IntegerHelper;
import de.unipotsdam.hpi.thorben.ppi.typehelper.LongHelper;
import de.unipotsdam.hpi.thorben.ppi.typehelper.TypeHelper;
public class PPIBpmnParse extends BpmnParse {
protected Map<String, TimeMeasure> timeMeasures = new HashMap<String, TimeMeasure>();
protected Map<String, CountMeasure> countMeasures = new HashMap<String, CountMeasure>();
protected Map<String, DataMeasure> dataMeasures = new HashMap<String, DataMeasure>();
protected Map<String, ProcessMeasure<?>> derivableProcessMeasures = new HashMap<String, ProcessMeasure<?>>();
protected Map<String, DerivedProcessMeasure> derivingMeasures = new HashMap<String, DerivedProcessMeasure>();
protected Map<String, List<Element>> ppisForProcessMeasure = new HashMap<String, List<Element>>();
// DataObjects: Id -> Name
protected Map<String, String> dataObjects = new HashMap<String, String>();
PPIBpmnParse(BpmnParser parser) {
super(parser);
}
@Override
protected void parseProcessDefinitionCustomExtensions(Element scopeElement,
ProcessDefinition definition) {
super.parseProcessDefinitionCustomExtensions(scopeElement, definition);
parsePPIElements(scopeElement, definition);
}
@Override
public void parseScope(Element scopeElement, ScopeImpl parentScope) {
// we have to parse the data objects first, otherwise they are not available when parsing the ppi elements
parseDataObjects(scopeElement, parentScope);
super.parseScope(scopeElement, parentScope);
}
private void parseDataObjects(Element scopeElement, ScopeImpl parentScope) {
List<Element> dataObjects = scopeElement.elements("dataObject");
for (Element dataObject : dataObjects) {
parseDataObject(dataObject);
}
}
private void parseDataObject(Element dataObject) {
String dataObjectId = dataObject.attribute("id");
String dataObjectName = dataObject.attribute("name");
dataObjects.put(dataObjectId, dataObjectName);
}
protected void parsePPIElements(Element scopeElement,
ProcessDefinition definition) {
Element extentionsElement = scopeElement.element("extensionElements");
if (extentionsElement != null) {
List<Element> ppiSetElements = extentionsElement.elementsNS(
BpmnParser.PPI_BPMN_EXTENSIONS_NS, "ppiset");
for (Element ppiSetElement : ppiSetElements) {
parsePPISet(ppiSetElement, definition);
}
}
}
protected void parsePPISet(Element ppiSetElement,
ProcessDefinition definition) {
List<Element> ppiElements = ppiSetElement.elementsNS(
BpmnParser.PPI_BPMN_EXTENSIONS_NS, "ppi");
for (Element ppi : ppiElements) {
registerPPIElementForProcessMeasure(ppi, definition);
}
List<Element> aggMeasureElements = ppiSetElement.elementsNS(
BpmnParser.PPI_BPMN_EXTENSIONS_NS, "aggregatedMeasure");
for (Element aggMeasure : aggMeasureElements) {
parseAggMeasure(aggMeasure, definition);
}
List<Element> derivedMeasureElements = ppiSetElement.elementsNS(
BpmnParser.PPI_BPMN_EXTENSIONS_NS, "derivedProcessMeasure");
for (Element derivedMeasure : derivedMeasureElements) {
parseDerivedMeasure(derivedMeasure, definition);
}
List<Element> timeConnectorElements = ppiSetElement.elementsNS(
BpmnParser.PPI_BPMN_EXTENSIONS_NS, "TimeConnector");
for (Element timeConnector : timeConnectorElements) {
parseTimeConnector(timeConnector, definition);
}
List<Element> appliesToConnectorElements = ppiSetElement.elementsNS(
BpmnParser.PPI_BPMN_EXTENSIONS_NS, "appliesToConnector");
for (Element appliesToConnector : appliesToConnectorElements) {
parseAppliesToConnector(appliesToConnector, definition);
}
List<Element> usesConnectorElements = ppiSetElement.elementsNS(
BpmnParser.PPI_BPMN_EXTENSIONS_NS, "uses");
for (Element usesConnector : usesConnectorElements) {
parseUsesConnector(usesConnector, definition);
}
}
private void parseDerivedMeasure(Element derivedMeasure,
ProcessDefinition definition) {
String measureId = derivedMeasure.attribute("id");
String juelFunction = derivedMeasure.attribute("function");
DerivedProcessMeasure measure = new DerivedProcessMeasure(measureId);
measure.setFunction(juelFunction);
ProcessDefinitionImpl definitionImpl = (ProcessDefinitionImpl) definition;
definitionImpl.addProcessMeasure(measure);
derivingMeasures.put(measureId, measure);
List<Element> ppisToRegisterFor = ppisForProcessMeasure.get(measureId);
if (ppisToRegisterFor != null) {
for (Element ppiElement : ppisToRegisterFor) {
parsePPI(ppiElement, new DoubleHelper(), measure, definition);
}
}
}
private void parseUsesConnector(Element usesConnector,
ProcessDefinition definition) {
String sourceMeasureId = usesConnector.attribute("sourceRef");
DerivedProcessMeasure derivedMeasure = derivingMeasures.get(sourceMeasureId);
String targetMeasureId = usesConnector.attribute("targetRef");
ProcessMeasure<?> measure = derivableProcessMeasures.get(targetMeasureId);
String variableName = usesConnector.attribute("variable");
derivedMeasure.addDerivableMeasure(variableName, measure);
}
private void parseAggMeasure(Element aggMeasure,
ProcessDefinition definition) {
List<Element> baseMeasures = aggMeasure.elements();
if (aggMeasure.elements().size() == 1) {
parseBaseMeasure(aggMeasure, baseMeasures.get(0), definition);
} else if (baseMeasures.size() > 1) {
throw new ActivitiException(
"There should be no more than one child base measure.");
} else if (baseMeasures.size() == 0) {
throw new ActivitiException(
"Currently only agg measure supported with base measures as child elements.");
}
}
private void parseBaseMeasure(Element aggregatedMeasure,
Element baseMeasure, ProcessDefinition definition) {
if (aggregatedMeasure == null) {
throw new ActivitiException(
"Base Measure has no owning aggregated measure.");
}
String tagName = baseMeasure.getTagName();
if (tagName.equals("timeMeasure")) {
parseTimeMeasure(aggregatedMeasure, baseMeasure, definition);
} else if (tagName.equals("countMeasure")) {
parseCountMeasure(aggregatedMeasure, baseMeasure, definition);
} else if (tagName.equals("dataMeasure")) {
parseDataMeasure(aggregatedMeasure, baseMeasure, definition);
}
else {
throw new ActivitiException("Unsupported base measure type "
+ baseMeasure.getTagName());
}
}
private <N extends Number, T extends BaseMeasureInstance> AggregationFunction<N, T> parseAggregationFunction(
String aggregationFunctionName, TypeHelper<N> typeHelper) {
if (aggregationFunctionName.equals("Average")) {
return new AverageFunction<N, T>(typeHelper);
} else if (aggregationFunctionName.equals("Sum")) {
return new SumFunction<N, T>(typeHelper);
} else if (aggregationFunctionName.equals("Maximum")) {
return new MaximumFunction<N, T>(typeHelper);
} else if (aggregationFunctionName.equals("Minimum")) {
return new MinimumFunction<N, T>(typeHelper);
} else {
throw new ActivitiException("Unsupported aggregation function type "
+ aggregationFunctionName);
}
}
private void parseDataMeasure(Element aggregatedMeasure,
Element baseMeasure, ProcessDefinition definition) {
// parse double measure
String dataMeasureId = baseMeasure.attribute("id");
String dataPropertyToTrack = baseMeasure.attribute("property");
DataMeasure dataMeasure = new DataMeasure(dataMeasureId, definition);
dataMeasure.setDataFieldName(dataPropertyToTrack);
dataMeasures.put(dataMeasureId, dataMeasure);
// parse aggregated measure
String aggFunction = aggregatedMeasure.attribute("aggregationfunction");
TypeHelper<Double> doubleHelper = new DoubleHelper();
AggregationFunction<Double, DataMeasureInstance> function = parseAggregationFunction(
aggFunction, doubleHelper);
AggregatedMeasure<DataMeasure, DataMeasureInstance, Double> measure = new AggregatedMeasure<DataMeasure, DataMeasureInstance, Double>();
String aggMeasureId = aggregatedMeasure.attribute("id");
measure.setId(aggMeasureId);
measure.setBaseMeasure(dataMeasure);
measure.setAggregationFunction(function);
ProcessDefinitionImpl definitionImpl = (ProcessDefinitionImpl) definition;
definitionImpl.addProcessMeasure(measure);
derivableProcessMeasures.put(aggMeasureId, measure);
List<Element> ppisToRegisterFor = ppisForProcessMeasure.get(aggMeasureId);
if (ppisToRegisterFor != null) {
for (Element ppiElement : ppisToRegisterFor) {
parsePPI(ppiElement, doubleHelper, measure, definition);
}
}
}
private void parseTimeMeasure(Element aggregatedMeasure,
Element baseMeasure, ProcessDefinition definition) {
// parse time measure
String timeMeasureId = baseMeasure.attribute("id");
TimeMeasure timeMeasure = new TimeMeasure(timeMeasureId, definition);
timeMeasures.put(timeMeasureId, timeMeasure);
// parse aggregated measure
String aggFunction = aggregatedMeasure.attribute("aggregationfunction");
TypeHelper<Long> longHelper = new LongHelper();
AggregationFunction<Long, TimeMeasureInstance> function = parseAggregationFunction(
aggFunction, longHelper);
AggregatedMeasure<TimeMeasure, TimeMeasureInstance, Long> measure = new AggregatedMeasure<TimeMeasure, TimeMeasureInstance, Long>();
// TODO tackle duplicated code in this and parseCountMeasure
String aggMeasureId = aggregatedMeasure.attribute("id");
measure.setId(aggMeasureId);
measure.setBaseMeasure(timeMeasure);
measure.setAggregationFunction(function);
ProcessDefinitionImpl definitionImpl = (ProcessDefinitionImpl) definition;
definitionImpl.addProcessMeasure(measure);
derivableProcessMeasures.put(aggMeasureId, measure);
List<Element> ppisToRegisterFor = ppisForProcessMeasure.get(aggMeasureId);
if (ppisToRegisterFor != null) {
for (Element ppiElement : ppisToRegisterFor) {
parsePPI(ppiElement, longHelper, measure, definition);
}
}
}
private <N extends Number> void parsePPI(Element ppiElement, TypeHelper<N> typeHelper, ProcessMeasure<N> measure, ProcessDefinition definition) {
String targetAttribute = ppiElement.attribute("target");
TargetFunction targetFunction = null;
N targetValue = null;
if (targetAttribute != null) {
String[] target = ppiElement.attribute("target").trim().split(" ");
targetFunction = parseTargetFunction(target[0], typeHelper);
targetValue = typeHelper.parseType(target[1]);
}
String ppiId = ppiElement.attribute("id");
PPI ppi = new PPI();
ppi.setId(ppiId);
ppi.setTargetFunction(targetFunction);
ppi.setTargetValue(targetValue);
ppi.setProcessMeasure(measure);
((ProcessDefinitionImpl) definition).addPPI(ppi);
}
private <N extends Number> TargetFunction parseTargetFunction(
String targetFunctionName, TypeHelper<N> typeHelper) {
if (targetFunctionName.equals("<")) {
return new LowerThanFunction<N>(typeHelper);
} else if (targetFunctionName.equals(">")) {
return new GreaterThanFunction<N>(typeHelper);
} else {
throw new ActivitiException("Unsupported target function type "
+ targetFunctionName);
}
}
// private PPI parsePPI(Element ppiElement, TargetFunction targetFunction,
// Number targetValue,
// ProcessMeasure<?> measure, ProcessDefinition definition) {
//
// String ppiId = ppiElement.attribute("id");
//
// PPI ppi = new PPI();
// ppi.setId(ppiId);
// ppi.setTargetFunction(targetFunction);
// ppi.setTargetValue(targetValue);
// ppi.setProcessMeasure(measure);
// return ppi;
// }
private void parseCountMeasure(Element aggregatedMeasure,
Element baseMeasure, ProcessDefinition definition) {
// parse count measure
String countMeasureId = baseMeasure.attribute("id");
CountMeasure countMeasure = new CountMeasure(countMeasureId, definition);
countMeasures.put(countMeasureId, countMeasure);
// parse aggregated measure
String aggFunction = aggregatedMeasure.attribute("aggregationfunction");
TypeHelper<Integer> intHelper = new IntegerHelper();
AggregationFunction<Integer, CountMeasureInstance> function = parseAggregationFunction(
aggFunction, intHelper);
AggregatedMeasure<CountMeasure, CountMeasureInstance, Integer> measure = new AggregatedMeasure<CountMeasure, CountMeasureInstance, Integer>();
String aggMeasureId = aggregatedMeasure.attribute("id");
measure.setId(aggMeasureId);
measure.setBaseMeasure(countMeasure);
measure.setAggregationFunction(function);
ProcessDefinitionImpl definitionImpl = (ProcessDefinitionImpl) definition;
definitionImpl.addProcessMeasure(measure);
derivableProcessMeasures.put(aggMeasureId, measure);
List<Element> ppisToRegisterFor = ppisForProcessMeasure.get(aggMeasureId);
if (ppisToRegisterFor != null) {
for (Element ppiElement : ppisToRegisterFor) {
parsePPI(ppiElement, intHelper, measure, definition);
}
}
}
private void registerPPIElementForProcessMeasure(Element ppiElement, ProcessDefinition definition) {
String measuredBy = ppiElement.attribute("measuredBy");
List<Element> ppis = ppisForProcessMeasure.get(measuredBy);
if (ppis == null) {
ppis = new ArrayList<Element>();
ppisForProcessMeasure.put(measuredBy, ppis);
}
ppis.add(ppiElement);
}
private void parseAppliesToActivity(CountMeasure measure, Element appliesToConnector,
ProcessDefinition definition) {
ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity) definition;
String targetActivityId = appliesToConnector.attribute("targetRef");
ActivityImpl activity = findActivityById(
definitionEntity.getActivities(), targetActivityId);
PPICondition condition;
String measurePoint = appliesToConnector.attribute("counatend");
if (measurePoint.equals("Start")) {
condition = new ActivityStartCondition(activity);
} else if (measurePoint.equals("End")) {
condition = new ActivityEndCondition(activity);
} else {
throw new ActivitiException(
"Unknow content of countatend tag for applies to connector "
+ appliesToConnector.attribute("id") + ": "
+ measurePoint);
}
measure.setCondition(condition);
activity.addObserver(measure);
}
private void parseAppliesToDataObject(DataMeasure measure, Element appliesToConnector,
ProcessDefinition definition) {
String dataObjectId = appliesToConnector.attribute("targetRef");
String dataObjectName = dataObjects.get(dataObjectId);
if (dataObjectName == null) {
throw new ActivitiException("There is no such data object: " + dataObjectId);
}
measure.setDataObjectName(dataObjectName);
ProcessDefinitionImpl definitionImpl = (ProcessDefinitionImpl) definition;
definitionImpl.addDataMeasure(measure);
}
private void parseAppliesToConnector(Element appliesToConnector,
ProcessDefinition definition) {
String sourceMeasureId = appliesToConnector.attribute("sourceRef");
CountMeasure countMeasure = countMeasures.get(sourceMeasureId);
if (countMeasure != null) {
parseAppliesToActivity(countMeasure, appliesToConnector, definition);
} else {
DataMeasure dataMeasure = dataMeasures.get(sourceMeasureId);
if (dataMeasure != null) {
parseAppliesToDataObject(dataMeasure, appliesToConnector, definition);
}
}
}
private void parseTimeConnector(Element timeConnector,
ProcessDefinition definition) {
String sourceMeasureId = timeConnector.attribute("sourceRef");
TimeMeasure timeMeasure = (TimeMeasure) timeMeasures
.get(sourceMeasureId);
ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity) definition;
String targetActivityId = timeConnector.attribute("targetRef");
ActivityImpl activity = findActivityById(
definitionEntity.getActivities(), targetActivityId);
String conditionType = timeConnector.attribute("conditiontype");
PPICondition condition;
String measurePoint = timeConnector.attribute("counatend");
if (measurePoint.equals("Start")) {
condition = new ActivityStartCondition(activity);
} else if (measurePoint.equals("End")) {
condition = new ActivityEndCondition(activity);
} else {
throw new ActivitiException(
"Unknow content of countatend tag for time connector "
+ timeConnector.attribute("id") + ": "
+ measurePoint);
}
if (conditionType.equals("From")) {
timeMeasure.setFromCondition(condition);
} else if (conditionType.equals("To")) {
timeMeasure.setToCondition(condition);
} else {
throw new ActivitiException(
"Unknow content of condition type tag for time connector "
+ timeConnector.attribute("id") + ": "
+ conditionType);
}
activity.addObserver(timeMeasure);
}
/**
* Returns null, if there is no such an activity with this id.
* @param activities
* @param id
* @return
*/
private ActivityImpl findActivityById(List<ActivityImpl> activities,
String id) {
for (ActivityImpl activity : activities) {
if (activity.getId().equals(id)) {
return activity;
}
}
return null;
}
}