diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..916cddf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
+!/.mvn/wrapper/maven-wrapper.jar
+
+.settings
+bin/
+build-reports/
+
+# Eclipse
+.classpath
+.project
+.settings/
+
+# Intellij
+.idea/
+*.iml
+*.iws
+
+# Mac
+.DS_Store
+
+# Maven
+log/
+target/
+
+.vscode
\ No newline at end of file
diff --git a/README.md b/README.md
index 526bc7b..45317d0 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,28 @@
-# camunda-coverage-generation-groovy
-Camunda BPM Unit testing coverage generation using BPMNJS, Groovy, and Camunda Test Assertion Libraries
+# Camunda Coverage Generation
+
+A Unit testing tool for generating Camunda BPMN Coverage diagrams using BPMN.js
+
+The tool uses Groovy and the Camunda Assertion framework.
+
+# How it works
+
+The coverage generation helpers provide easy to use methods to take
+"Coverage Snapshots" at any point during a unit test.
+
+Each snapshot generates "Coverage Data" which is used to generate HTML
+files in the build target folder.
+
+The HTML files have all the required data injected into them and are self-contained.
+Each .html file uses the bpmnjs renderer loaded through CDN.
+
+# How to install
+
+...
+
+# Usage example with Spock Framework
+
+...
+
+# Output Examples
+
+...
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..e71672b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,185 @@
+
+
+ 4.0.0
+ com.github.digitalstate
+ camunda-coverage-genertion-groovy
+ v0.5
+ jar
+ Camunda Converage Generation Groovy
+
+
+ UTF-8
+ UTF-8
+
+ 7.9.0
+ 1.4.190
+ 1.8
+
+
+
+
+
+
+ org.camunda.bpm
+ camunda-bom
+ pom
+ import
+ ${version.camunda}
+
+
+ org.camunda.bpm.extension
+ camunda-bpm-assert
+ 1.2
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.1
+
+
+ ${version.java}
+
+
+
+ maven-assembly-plugin
+ 2.2.1
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
+
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
+ 1.5
+
+
+ compile
+
+ compile
+ testCompile
+
+
+
+
+
+
+
+
+ maven-surefire-plugin
+ 2.18.1
+
+ false
+
+ **/*Spec.java
+
+
+
+
+
+
+
+
+
+
+
+ org.spockframework
+ spock-core
+ 1.1-groovy-2.4
+ test
+
+
+
+ org.codehaus.groovy
+ groovy-all
+ 2.4.10
+ provided
+
+
+
+
+ org.camunda.bpm
+ camunda-engine
+ provided
+
+
+
+
+ org.camunda.bpm
+ camunda-engine-plugin-spin
+ test
+
+
+
+
+ org.camunda.spin
+ camunda-spin-dataformat-json-jackson
+ test
+
+
+ org.camunda.spin
+ camunda-spin-dataformat-xml-dom
+ 1.5.1
+ test
+
+
+
+
+ org.camunda.bpm.extension
+ camunda-bpm-assert
+ provided
+
+
+
+
+
+ com.h2database
+ h2
+ test
+ ${version.h2}
+
+
+
+ org.camunda.bpm.extension
+ camunda-bpm-process-test-coverage
+ 0.3.2
+ test
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.13
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.13
+ test
+
+
+
+
diff --git a/src/main/groovy/io/digitalstate/camunda/coverage/bpmn/BpmnCoverageBuilder.groovy b/src/main/groovy/io/digitalstate/camunda/coverage/bpmn/BpmnCoverageBuilder.groovy
new file mode 100644
index 0000000..b73beb3
--- /dev/null
+++ b/src/main/groovy/io/digitalstate/camunda/coverage/bpmn/BpmnCoverageBuilder.groovy
@@ -0,0 +1,153 @@
+package io.digitalstate.camunda.coverage.bpmn
+
+import io.digitalstate.camunda.coverage.bpmn.CoverageData
+import org.camunda.bpm.engine.ProcessEngine
+import org.camunda.bpm.engine.history.HistoricDetail
+import org.camunda.bpm.engine.runtime.ProcessInstance
+import org.camunda.bpm.model.bpmn.Bpmn
+import org.camunda.bpm.model.bpmn.BpmnModelInstance
+import org.camunda.bpm.model.bpmn.instance.IntermediateCatchEvent
+import org.camunda.bpm.model.bpmn.instance.ReceiveTask
+import org.camunda.bpm.model.bpmn.instance.ServiceTask
+import org.camunda.bpm.model.bpmn.instance.SequenceFlow
+import org.camunda.bpm.model.bpmn.instance.UserTask
+import org.camunda.bpm.model.bpmn.instance.camunda.CamundaExecutionListener
+import org.camunda.bpm.model.bpmn.instance.camunda.CamundaScript
+import org.camunda.bpm.model.bpmn.instance.FlowNode
+import groovy.json.StringEscapeUtils
+
+class BpmnCoverageBuilder {
+
+ // helper method to shorten the .addInputStream params in createDeployment()
+ static InputStream resourceStream(String path){
+ return this.getClassLoader().getResource(path.toString()).newInputStream()
+ }
+
+ static InputStream sequenceFlowHistoryGenerator(){
+ return resourceStream("io/digitalstate/camunda/coverage/${sequenceFlowHistoryFileName()}")
+ }
+ static String sequenceFlowHistoryFileName(){
+ return "sequenceFlowHistoryEventGenerator.js"
+ }
+
+ static BpmnModelInstance prepModelForCoverage( String modelPath,
+ String scriptResource = 'deployment://sequenceFlowHistoryEventGenerator.js',
+ String scriptFormat = 'javascript') {
+
+ InputStream resource = resourceStream(modelPath)
+ BpmnModelInstance model = Bpmn.readModelFromStream(resource)
+ BpmnModelInstance preppedModel = setupSequenceFlowListeners(model, scriptResource, scriptFormat)
+ return preppedModel
+ }
+
+ private static BpmnModelInstance addExecutionListener(BpmnModelInstance model, String elementId, String scriptResource, String scriptFormat){
+ CamundaExecutionListener extLis = model.newInstance(CamundaExecutionListener.class)
+ CamundaScript camScript = model.newInstance(CamundaScript.class)
+ camScript.setCamundaResource(scriptResource)
+ camScript.setCamundaScriptFormat(scriptFormat)
+ extLis.setCamundaEvent('take')
+ extLis.setCamundaScript(camScript)
+
+ BpmnModelInstance newModel = model.getModelElementById(elementId).builder().addExtensionElement(extLis).done()
+ return newModel
+ }
+
+ private static BpmnModelInstance setupSequenceFlowListeners(BpmnModelInstance model, String scriptResource, String scriptFormat){
+ def sequenceFlows = model.getModelElementsByType(SequenceFlow.class).collect {it.getId()}
+ BpmnModelInstance newModel = model
+ sequenceFlows.each {
+ newModel = addExecutionListener(newModel, it, scriptResource, scriptFormat)
+ }
+ return newModel
+ }
+
+ static generateCoverageData(ProcessEngine processEngine, ProcessInstance processInstance, String coverageDataName = null, Integer coverageDataWeight = 0){
+ CoverageData coverageData = new CoverageData()
+ coverageData.name = coverageDataName
+ coverageData.weight = coverageDataWeight
+
+ String processInstanceId = processInstance.getProcessInstanceId()
+// def reportData = [:]
+
+ // Get Activity Events
+ def events = processEngine.getHistoryService()
+ .createHistoricActivityInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .finished()
+ .orderPartiallyByOccurrence()
+ .asc()
+ .list()
+ def activityEvents = events.findAll {it.activityType != 'sequenceFlow' && it.activityType != 'multiInstanceBody'}
+ .collect {it.activityId}
+ .countBy {it}
+ coverageData.activityInstancesFinished = activityEvents
+
+ // Activity Events That are still active
+ def eventsStillActive = processEngine.getHistoryService()
+ .createHistoricActivityInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .unfinished()
+ .orderPartiallyByOccurrence()
+ .asc()
+ .list()
+ def activityEventsStillActive = eventsStillActive.findAll {it.activityType != 'sequenceFlow'}
+ .collect {it.activityId}
+ coverageData.activityInstancesUnfinished = activityEventsStillActive
+
+ def sequenceFlows = events.findAll {it.activityType == 'sequenceFlow'}
+ .collect {it.activityId}
+ coverageData.sequenceFlowsFinished = sequenceFlows
+
+ String processDefinitionId = processEngine.getHistoryService()
+ .createHistoricProcessInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .singleResult()
+ .getProcessDefinitionId()
+
+ def model = processEngine.getRepositoryService()
+ .getBpmnModelInstance(processDefinitionId)
+
+ def asyncData = model.getModelElementsByType(FlowNode.class).collect {[
+ 'id': it.getId(),
+ 'asyncBefore': it.isCamundaAsyncBefore().toBoolean(),
+ 'asyncAfter': it.isCamundaAsyncAfter().toBoolean(),
+ 'exclusive': it.isCamundaExclusive().toBoolean()
+ ]}
+ coverageData.modelAsyncData = asyncData
+
+ def userTasks = model.getModelElementsByType(UserTask.class).collect {it.getId()}
+ coverageData.modelUserTasks = userTasks
+
+ def receiveTasks = model.getModelElementsByType(ReceiveTask.class).collect {it.getId()}
+ coverageData.modelReceiveTasks = receiveTasks
+
+ def externalTasks = model.getModelElementsByType(ServiceTask.class).findAll {it.getCamundaType() == 'external'}.collect {it.getId()}
+ coverageData.modelExternalTasks = externalTasks
+
+ def intermediateCatchEvents = model.getModelElementsByType(IntermediateCatchEvent.class).collect {it.getId()}
+
+ coverageData.modelIntermediateCatchEvents = intermediateCatchEvents
+
+ coverageData.bpmnModel = StringEscapeUtils.escapeJavaScript(Bpmn.convertToString(model))
+
+
+// Generate Variable Usage / Dataflow data
+ Collection variableHistory = processEngine.getHistoryService().createHistoricDetailQuery()
+ .processInstanceId(processInstanceId)
+ .disableBinaryFetching()
+ .variableUpdates()
+ .list()
+
+ List