package org.activiti.crystalball.diagram;
/*
* #%L
* image-builder
* %%
* Copyright (C) 2012 - 2013 crystalball
* %%
* 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.
* #L%
*/
import org.activiti.crystalball.diagram.svg.SVGCanvasFactory;
import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricVariableUpdate;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.bpmn.behavior.BoundaryEventActivityBehavior;
import org.activiti.engine.impl.bpmn.parser.BpmnParse;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* generate audit trail diagram layer based on audit trail data in historyService
* TODO: Parallel activities
*/
public class AuditTrailProcessDiagramGenerator extends AbstractProcessDiagramLayerGenerator {
protected static Logger log = Logger.getLogger(AuditTrailProcessDiagramGenerator.class.getName());
protected HistoryService historyService;
public static final String PROCESS_INSTANCE_ID = "process_instance_id";
/** transition line hight */
private static final int TRANSITION_HEIGHT = 20;
private int maxX = 0;
private int maxY = 0;
protected boolean writeUpdates = false;
//
// Copied from ProcessDiagramGenerator
//
protected static final Map<String, ActivityDrawXYInstruction> activityDrawInstructions = new HashMap<String, ActivityDrawXYInstruction>();
// The instructions on how to draw a certain construct is
// created statically and stored in a map for performance.
static {
// start event
activityDrawInstructions.put("startEvent", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawNoneStartEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// start timer event
activityDrawInstructions.put("startTimerEvent", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawTimerStartEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// signal catch
activityDrawInstructions.put("intermediateSignalCatch", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
BoundaryEventActivityBehavior behavior = (BoundaryEventActivityBehavior)activityImpl.getActivityBehavior();
processDiagramCreator.drawCatchingSignalEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight(), behavior.isInterrupting());
}
});
// signal throw
activityDrawInstructions.put("intermediateSignalThrow", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawThrowingSignalEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// end event
activityDrawInstructions.put("endEvent", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawNoneEndEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// error end event
activityDrawInstructions.put("errorEndEvent", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawErrorEndEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// error start event
activityDrawInstructions.put("errorStartEvent", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawErrorStartEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// task
activityDrawInstructions.put("task", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// user task
activityDrawInstructions.put("userTask", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawUserTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// script task
activityDrawInstructions.put("scriptTask", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawScriptTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// service task
activityDrawInstructions.put("serviceTask", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawServiceTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// receive task
activityDrawInstructions.put("receiveTask", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawReceiveTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// send task
activityDrawInstructions.put("sendTask", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawSendTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// manual task
activityDrawInstructions.put("manualTask", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawManualTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// businessRuleTask task
activityDrawInstructions.put("businessRuleTask", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawBusinessRuleTask((String) activityImpl.getProperty("name"), x, y, activityImpl.getWidth(),
activityImpl.getHeight());
}
});
// exclusive gateway
activityDrawInstructions.put("exclusiveGateway", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawExclusiveGateway(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// inclusive gateway
activityDrawInstructions.put("inclusiveGateway", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawInclusiveGateway(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// parallel gateway
activityDrawInstructions.put("parallelGateway", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawParallelGateway(x, y, activityImpl.getWidth(), activityImpl.getHeight());
}
});
// Boundary timer
activityDrawInstructions.put("boundaryTimer", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
BoundaryEventActivityBehavior behavior = (BoundaryEventActivityBehavior)activityImpl.getActivityBehavior();
processDiagramCreator.drawCatchingTimerEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight(), behavior.isInterrupting());
}
});
// Boundary catch error
activityDrawInstructions.put("boundaryError", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
BoundaryEventActivityBehavior behavior = (BoundaryEventActivityBehavior)activityImpl.getActivityBehavior();
processDiagramCreator.drawCatchingErrorEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight(), behavior.isInterrupting());
}
});
// Boundary signal event
activityDrawInstructions.put("boundarySignal", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
BoundaryEventActivityBehavior behavior = (BoundaryEventActivityBehavior)activityImpl.getActivityBehavior();
processDiagramCreator.drawCatchingSignalEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight(), behavior.isInterrupting());
}
});
// timer catch event
activityDrawInstructions.put("intermediateTimer", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
BoundaryEventActivityBehavior behavior = (BoundaryEventActivityBehavior)activityImpl.getActivityBehavior();
processDiagramCreator.drawCatchingTimerEvent(x, y, activityImpl.getWidth(), activityImpl.getHeight(), behavior.isInterrupting());
}
});
// subprocess
activityDrawInstructions.put("subProcess", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
Boolean isExpanded = (Boolean) activityImpl.getProperty(BpmnParse.PROPERTYNAME_ISEXPANDED);
Boolean isTriggeredByEvent = (Boolean) activityImpl.getProperty("triggeredByEvent");
if(isTriggeredByEvent == null) {
isTriggeredByEvent = Boolean.TRUE;
}
if (isExpanded != null && isExpanded == false) {
processDiagramCreator.drawCollapsedSubProcess((String) activityImpl.getProperty("name"), x, y,
activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
} else {
processDiagramCreator.drawExpandedSubProcess((String) activityImpl.getProperty("name"), x, y,
activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
}
}
});
// call activity
activityDrawInstructions.put("callActivity", new ActivityDrawXYInstruction() {
public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y) {
processDiagramCreator.drawCollapsedCallActivity((String) activityImpl.getProperty("name"), x, y,
activityImpl.getWidth(), activityImpl.getHeight());
}
});
}
public AuditTrailProcessDiagramGenerator() {
super();
}
public AuditTrailProcessDiagramGenerator(ProcessDiagramCanvasFactory canvasFactory) {
super(canvasFactory);
}
protected static void drawActivity(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity, Map<String,Object> varUpdates, Integer x, Integer y, boolean writeUpdates) {
String type = (String) activity.getProperty("type");
ActivityDrawXYInstruction drawInstruction = activityDrawInstructions.get(type);
if (drawInstruction != null) {
drawInstruction.draw(processDiagramCanvas, activity, x, y);
// Gather info on the multi instance marker
boolean multiInstanceSequential = false, multiInstanceParallel = false, collapsed = false;
String multiInstance = (String) activity.getProperty("multiInstance");
if (multiInstance != null) {
if ("sequential".equals(multiInstance)) {
multiInstanceSequential = true;
} else {
multiInstanceParallel = true;
}
}
// Gather info on the collapsed marker
Boolean expanded = (Boolean) activity.getProperty(BpmnParse.PROPERTYNAME_ISEXPANDED);
if (expanded != null) {
collapsed = !expanded;
}
// Actually draw the markers
processDiagramCanvas.drawActivityMarkers(x, y, activity.getWidth(), activity.getHeight(), multiInstanceSequential,
multiInstanceParallel, collapsed);
if (writeUpdates && !varUpdates.isEmpty())
processDiagramCanvas.drawStringToNode( varUpdates.toString(), x, y, activity.getWidth()+10, activity.getHeight());
}
// TODO: Nested activities (boundary events)
//
// for (ActivityImpl nestedActivity : activity.getActivities()) {
// drawActivity(processDiagramCanvas, nestedActivity, highLightedActivities);
// }
}
protected interface ActivityDrawXYInstruction {
/**
* draw activity on the given position.
*
* @see org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator.ActivityDrawInstruction
* @param processDiagramCreator
* @param activityImpl
* @param x
* @param y
*/
void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl, int x, int y);
}
public InputStream generateLayer(String imageType, Map<String, Object> params) {
// get process instance from which we will get audit trail layer
final String processInstanceId = (String) params.get( PROCESS_INSTANCE_ID );
ProcessDiagramCanvas canvas = generateDiagram( processInstanceId);
return canvas.generateImage(imageType);
}
protected ProcessDiagramCanvas generateDiagram( String processInstanceId) {
if ( historyService.createHistoricProcessInstanceQuery().processInstanceId( processInstanceId).count() == 0) {
log.info("Process instanceId["+ processInstanceId +"] does not have any history");
return null;
}
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
final String processDefinitionId = historicProcessInstance.getProcessDefinitionId();
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ( ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition( processDefinitionId ));
ProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(processInstanceId, processDefinition);
// TODO: Draw pool shape, if process is participant in collaboration
// TODO: Draw lanes
// Draw activities
// ordering can be source of problems.
// ExecutionId is String which contains number.
// timestamps are not precise enough
// .orderByHistoricActivityInstanceStartTime()
// .asc()
// .orderByHistoricActivityInstanceEndTime()
// .asc()
List<HistoricActivityInstance> historicActivities = historyService.createHistoricActivityInstanceQuery()
.processInstanceId( processInstanceId)
.orderByHistoricActivityInstanceStartTime()
.asc()
.orderByHistoricActivityInstanceEndTime()
.asc()
.list();
int y = 0;
if ( !historicActivities.isEmpty()) {
ActivityImpl activity = findActivity(processDefinition, historicActivities.get(0).getActivityId());
drawActivity( processDiagramCanvas, activity, getVarUpdates(historicActivities.get(0)), (maxX - activity.getWidth())/2, y, writeUpdates);
y += activity.getHeight();
for ( int i=1; i < historicActivities.size(); i++ ) {
// draw transition
processDiagramCanvas.drawSequenceflow( maxX/2, y, maxX/2, y+TRANSITION_HEIGHT, false);
y += TRANSITION_HEIGHT;
// draw activity
activity = findActivity(processDefinition, historicActivities.get(i).getActivityId());
drawActivity( processDiagramCanvas, activity, getVarUpdates(historicActivities.get(i)), (maxX - activity.getWidth())/2, y, writeUpdates);
y += activity.getHeight();
}
}
return processDiagramCanvas;
}
/**
* get var updates in the node
* @param historicActivityInstance
* @return
*/
private Map<String, Object> getVarUpdates(HistoricActivityInstance historicActivityInstance) {
Map<String, Object> updates = new HashMap<String,Object>();
List<HistoricDetail> historicUpdates = historyService.createHistoricDetailQuery()
.processInstanceId( historicActivityInstance.getProcessInstanceId())
.activityInstanceId( historicActivityInstance.getId())
.variableUpdates()
.list();
for (HistoricDetail historicDetail : historicUpdates)
updates.put( ((HistoricVariableUpdate) historicDetail).getVariableName(), ((HistoricVariableUpdate) historicDetail).getValue());
return updates;
}
protected ProcessDiagramCanvas initProcessDiagramCanvas(String processInstanceId,
ProcessDefinitionEntity processDefinition) {
int minX = 0;
int minY = 0;
maxX = 0;
maxY = 0;
//TODO: participant processes are not taken into account
List<HistoricActivityInstance> historicActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
for (HistoricActivityInstance historicActivity : historicActivityList) {
ActivityImpl activity = findActivity( processDefinition, historicActivity.getActivityId());
if (activity != null) {
// width
if (activity.getWidth() > maxX) {
maxX = activity.getWidth();
}
// height
maxY += activity.getHeight();
// + transition size
maxY += TRANSITION_HEIGHT;
}
}
//TODO: lanes are not taken into consideration
maxX += 10;
// correct image height
maxY += 10 - TRANSITION_HEIGHT;
return canvasFactory.createCanvas(maxX, maxY, minX, minY);
}
private static ActivityImpl findActivity(ProcessDefinitionEntity processDefinition, String activityId) {
if (activityId == null)
return null;
for (ActivityImpl activity : processDefinition.getActivities()) {
if ( activityId.equals( activity.getId() ))
return activity;
}
return null;
}
public HistoryService getHistoryService() {
return historyService;
}
public void setHistoryService(HistoryService historyService) {
this.historyService = historyService;
}
public boolean isWriteUpdates() {
return writeUpdates;
}
public void setWriteUpdates(boolean writeUpdates) {
this.writeUpdates = writeUpdates;
}
}