Petriflow
Petriflow language extends Petri nets with other components. As the underlying model, we use place/transition nets enriched by reset arcs, inhibitor arcs and read arcs. The read arcs appear quite necessary in order to model unbounded number of concurrent reading of data in a case. To meet modern business modelling requirements other layers were brought to the language on top of Petri nets. Roles are the first layer to extend Petri nets. Roles layer defines who can fire transitions to which they are bound. Data variables were added as the second layer on top of modelled processes. Data variables represent all properties of an instance of a process during its life-cycle. To have more control over process instance data, data field actions were added to Petriflow. Actions can define relations or dependencies between data fields in the model of a process or generate values based on a process instance state. All extensions and layers create the right tool for modelling complex, yet simple to understand models of any process that comes to mind.
Documentation
- 1 Model
- 1.1 Petri Net
- 1.2 Transition
- 1.3 Place
- 1.4 Arc
- 1.4.1 Regular arc
- 1.4.2 Reset arc
- 1.4.3 Inhibitor arc
- 1.4.4 Read arc
- 1.4.5 Variable arc
- 1.5 Transaction
- 1.6 Roles
- 1.7 Data fields
- 2 Instance
Model
Petri Net
XSD schema
<xs:element name="document"> <xs:complexType> <xs:sequence> <xs:element ref="id" minOccurs="0"/> <xs:element ref="version" minOccurs="0"/> <xs:element ref="initials" minOccurs="0"/> <xs:element name="title" minOccurs="0" type="i18nStringType"/> <xs:element ref="icon" minOccurs="0"/> <xs:element ref="defaultRole" minOccurs="0"/> <xs:element ref="transitionRole" minOccurs="0"/> <xs:element ref="caseName" minOccurs="0"/> <xs:element ref="transaction" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="role" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="data" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="mapping" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="i18n" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="transition" maxOccurs="unbounded"/> <xs:element ref="place" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="arc" maxOccurs="unbounded" minOccurs="0"/> </xs:sequence> </xs:complexType> </xs:element>
Net document
<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="petriflow_schema.xsd"> <id>1</id> <defaultRole>true</defaultRole> <caseName>New quote</caseName> <transaction>...</transaction> ... <role>...</role> ... <data>...</data> ... <mapping>...</mapping> ... <i18n>...</i18n> ... <transition>...</transition> ... <place>...</place> ... <arc>...</arc> ... </document>
Object
PetriNet - ObjectId _id - String importId - String identifier - I18nString title - I18nString defaultCaseName - String initials - String icon - LocalDateTime creationDate - String version - Author author - Map<String, Place> places - Map<String, Transition> transitions - Map<String, List<Arc>> arcs - Map<String, Field> dataSet - Map<String, ProcessRole> roles - Map<String, Transaction> transactions - boolean initialized - String importXmlPath
Transition
XSD schema
<xs:element name="transition"> <xs:complexType> <xs:sequence> <xs:element ref="id"/> <xs:element ref="x"/> <xs:element ref="y"/> <xs:element ref="label"/> <xs:element ref="icon" minOccurs="0"/> <xs:element ref="priority" minOccurs="0"/> <xs:element ref="assignPolicy" minOccurs="0"/> <xs:element ref="dataFocusPolicy" minOccurs="0"/> <xs:element ref="finishPolicy" minOccurs="0"/> <xs:element ref="trigger" minOccurs="0" maxOccurs="unbounded"/> <xs:element ref="transactionRef" minOccurs="0"/> <xs:element ref="roleRef" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="dataRef" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="dataGroup" maxOccurs="unbounded" minOccurs="0"/> <xs:element ref="event" maxOccurs="unbounded" minOccurs="0"/> </xs:sequence> </xs:complexType> </xs:element>
Object
Transition - ObjectId _id - String importId - Position position - I18nString title; - Map<String, DataGroup> dataGroups - LinkedHashMap<String, DataFieldLogic> dataSet - Map<String, Set<RolePermission>> roles - List<Trigger> triggers - Integer priority - AssignPolicy assignPolicy - String icon - DataFocusPolicy dataFocusPolicy - FinishPolicy finishPolicy - Map<EventType, Event> events - String defaultRoleId
Place
XSD schema
<xs:element name="place"> <xs:complexType> <xs:sequence> <xs:element ref="id"/> <xs:element ref="x"/> <xs:element ref="y"/> <xs:element ref="label"/> <xs:element ref="tokens"/> <xs:choice> <xs:element ref="isStatic"/> <xs:element ref="static"/> </xs:choice> </xs:sequence> </xs:complexType> </xs:element>
Arc
XSD schema
<xs:element name="arc"> <xs:complexType> <xs:sequence> <xs:element ref="id"/> <xs:element name="type" type="arc_type" default="regular"/> <xs:element ref="sourceId"/> <xs:element ref="destinationId"/> <xs:element ref="multiplicity"/> <xs:element ref="breakPoint" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element>
Element type is restricted to values:
regular
reset
inhibitor
read
variable
Regular arc
XSD schema
<arc> <id>1</id> <type>regular</type> <sourceId>2</sourceId> <destinationId>3</destinationId> <multiplicity>1</multiplicity> </arc>
Reset arc
XSD schema
<arc> <id>1</id> <type>reset</type> <sourceId>2</sourceId> <destinationId>3</destinationId> <multiplicity>1</multiplicity> </arc>
Inhibitor arc
XSD schema
<arc> <id>1</id> <type>inhibitor</type> <sourceId>2</sourceId> <destinationId>3</destinationId> <multiplicity>1</multiplicity> </arc>
Read arc
XSD schema
<arc> <id>1</id> <type>read</type> <sourceId>2</sourceId> <destinationId>3</destinationId> <multiplicity>1</multiplicity> </arc>
Variable arc
Variable arcs have the same behaviour as regular arcs except the multiplicity is read from a number data field. Data field is identified by multiplicity tag which contains fields id.
Variable arc
<data type="number"> <id>500001</id> <title>vararc_byt_true</title> <init>0.0</init> </data> ... <arc> <id>3270</id> <type>variable</type> <sourceId>414</sourceId> <destinationId>2284</destinationId> <multiplicity>500001</multiplicity> </arc>
Transaction
XSD schema
<xs:element name="transaction"> <xs:complexType> <xs:sequence> <xs:element ref="id"/> <xs:element ref="title"/> </xs:sequence> </xs:complexType> </xs:element>
Roles
XSD schema
<xs:element name="role"> <xs:complexType> <xs:sequence> <xs:element ref="id"/> <xs:choice> <xs:element ref="title"/> <xs:element ref="name"/> </xs:choice> </xs:sequence> </xs:complexType> </xs:element>
Data fields
XSD schema
<xs:element name="data"> <xs:complexType> <xs:sequence> <xs:element ref="id"/> <xs:element ref="title"/> <xs:element ref="placeholder" minOccurs="0"/> <xs:element ref="desc" minOccurs="0"/> <xs:element ref="values" minOccurs="0" maxOccurs="unbounded"/> <xs:element ref="valid" minOccurs="0" maxOccurs="unbounded"/> <xs:element ref="init" minOccurs="0"/> <xs:element ref="encryption" minOccurs="0"/> <xs:element ref="action" minOccurs="0" maxOccurs="unbounded"/> <xs:element ref="actionRef" minOccurs="0" maxOccurs="unbounded"/> <xs:element ref="documentRef" minOccurs="0"/> <xs:element ref="remote" minOccurs="0"/> </xs:sequence> <xs:attribute type="data_type" name="type" use="required"/> <xs:attribute type="xs:boolean" name="immediate"/> </xs:complexType> </xs:element>
Object
Field - ObjectId _id - String importId - I18nString name - I18nString description - I18nString placeholder - ObjectNode behavior - T value - Long order - boolean immediate - String encryption
Instance
Case
Object
Case - ObjectId _id - String visualId - PetriNet petriNet - String processIdentifier - Map<String, Integer> activePlaces - String title - String color - String icon - LocalDateTime creationDate - LinkedHashMap<String, DataField> dataSet - LinkedHashSet<String> immediateDataFields - List<Field> immediateData - Author author - Map<String, Integer> resetArcTokens - Set<TaskPair> tasks
Task
Object
Task - ObjectId _id - String processId - String caseId - String transitionId - I18nString title - String caseColor - String caseTitle - Integer priority - Long userId - User user - List<Trigger> triggers - Map<String, Map<String, Boolean>> roles - LocalDateTime startDate - LocalDateTime finishDate - Long finishedBy - String transactionId - Boolean requiredFilled - LinkedHashSet<String> immediateDataFields - List<Field> immediateData - String icon - AssignPolicy assignPolicy - DataFocusPolicy dataFocusPolicy - FinishPolicy finishPolicy
Actions
- 1 make <Field f>,<Closure behaviour> on <Transition t> when <Closure<Boolean> condition>
- 2 change <Field> about <Closure>
- 3 change <Field f> value <Closure calculation>
- 4 change <Field> choices <Closure choices>
- 5 generate <String method,Closure repeat> into <Field f>
- 6 changeCaseProperty <String property> about <Closure supplier>
- 7 Case createCase(String identifier, String title = null, String color = "", User author = userService.loggedOrSystem)
- 8 Case createCase(PetriNet net, String title = net.defaultCaseName.defaultValue, String color = "", User author = userService.loggedOrSystem)
- 9 List<Case> findCases(Closure<Predicate> predicate)
- 10 List<Case> findCases(Closure<Predicate> predicate, Pageable page)
- 11 Case findCase(Closure<Predicate> predicate)
- 12 List<Task> findTasks(Closure<Predicate> predicate)
- 13 List<Task> findTasks(Closure<Predicate> predicate, Pageable page)
- 14 Task findTask(Closure<Predicate> predicate)
- 15 close <List<Transition>>
- 16 execute <String transitionId> where <Closure<Predicate>> with <Map>
- 17 Task assignTask(String transitionId, Case aCase = useCase, User user = userService.loggedOrSystem)
- 18 Task assignTask(Task task, User user = userService.loggedOrSystem)
- 19 assignTasks(List<Task> tasks, User assignee = userService.loggedOrSystem)
- 20 cancelTask(String transitionId, Case aCase = useCase, User user = userService.loggedOrSystem)
- 21 cancelTask(Task task, User user = userService.loggedOrSystem)
- 22 cancelTasks(List<Task> tasks, User user = userService.loggedOrSystem)
- 23 finishTask(String transitionId, Case aCase = useCase, User user = userService.loggedOrSystem)
- 24 finishTask(Task task, User user = userService.loggedOrSystem)
- 25 finishTasks(List<Task> tasks, User user = userService.loggedOrSystem)
- 26 setData(Task task, Map dataSet)
- 27 setData(Transition transition, Map dataSet)
- 28 setData(String transitionId, Case useCase, Map dataSet)
- 29 Map<String, Field> getData(Task task)
- 30 Map<String, Field> getData(Transition transition)
- 31 Map<String, Field> getData(String transitionId, Case useCase)
- 32 User assignRole(String roleId, User user = userService.loggedUser)
- 33 User loggedUser()
make <Field f>,<Closure behaviour> on <Transition t> when <Closure<Boolean> condition>
Changes behaviour of given data field f on transition t, iff condition returns true. Behaviour can be one of:
visible,
editable,
required,
optional,
hidden.
forbidden
Example
garage_check: f.garage_check, garage_cost: f.garage_cost, garage: t.garage; make garage_cost,visible on garage when { return garage_check.value == true; }
change <Field> about <Closure>
Deprecated
See change value.
change <Field f> value <Closure calculation>
Sets new value to data field f returned by calculation closure. If the returned value is null, fields value is set to default value. If the returned value is unchanged, fields value is unchanged and actions with a trigger set on given field are not triggered.
Example
period: f.108001, sum: f.308011; change period value { def limit = 20.0; if (period.value == "polročná") limit = 40.0; if (period.value == "štvrťročná") limit = 80.0; if ((sum.value as Double) < (limit as Double)) return "ročná"; return unchanged; }
change <Field> choices <Closure choices>
Sets a new set of choices to data field f.
Example
other: f.410001, field: f.this; change field choices { if (other.value == "Nehnutelnost") return field.choices + ["rozostavaná stavba"]; return field.choices; }
generate <String method,Closure repeat> into <Field f>
Calls method and saves generated value into data field f. The field can be only of type Text or File. If repeat is equal to always new value is generated on each run of action. If repeat is equal to once new value is generated only if fields value is null.
Example
self: f.this; generate "Insurance.offerPDF",always into self
changeCaseProperty <String property> about <Closure supplier>
Changes the property of the current case, the new value is generated by the supplier.
Example
trans: t.this; changeCaseProperty "icon" about { trans.icon }
Case createCase(String identifier, String title = null, String color = "", User author = userService.loggedOrSystem)
Creates a new instance of the newest version of net identified by the identifier. If the title is not specified, nets default case name is used. If the colour is null, the default colour is used (black at the moment).
Example
createCase("create_case_net","Create Case Case","color-fg-amber-500", otherUser); createCase("create_case_net","Create Case Case","color-fg-amber-500"); createCase("create_case_net","Create Case Case"); createCase("create_case_net");
Case createCase(PetriNet net, String title = net.defaultCaseName.defaultValue, String color = "", User author = userService.loggedOrSystem)
Creates a new instance of the given net. If the title is not specified, nets default case name is used. If the colour is null, the default colour is used (black at the moment).
Example
def net = petriNetService.getNewestVersionByIdentifier("insurance") createCase(net) createCase(net, "My insurance") createCase(net, "My insurance", "color-fg-amber-500") createCase(net, "Other insurance", "color-fg-amber-500", otherUser)
List<Case> findCases(Closure<Predicate> predicate)
Finds all the cases that match the given predicate. The predicate is a groovy closure that accepts QCase object and returns QueryDSL Predicate.
Example
List<Case> cases = findCases( { it.title.eq("Case 1") } ); ... List<Case> cases = findCases( { it.dataSet.get("name").value.eq("Jozko") } );
List<Case> findCases(Closure<Predicate> predicate, Pageable page)
Finds all the cases that match the given predicate. The predicate is a groovy closure that accepts QCase object and returns QueryDSL Predicate. Pageable determines the requested page number, page size, sort fields, and sort direction.
Example
// returns the first page of 5 cases sorted by the title List<Case> cases = findCases( { it.dataSet.get("name").value.eq("Jozko") }, new PageRequest(0, 10, Sort.by("title").ascending() ) ); ... // returns the second page of 5 cases sorted from the newest to oldest List<Case> cases = findCases( { it.dataSet.get("name").value.eq("Jozko") }, new PageRequest(1, 5, Sort.by("creationDate").descending() ) );
Case findCase(Closure<Predicate> predicate)
Finds the first case that matches the given predicate. The predicate is a groovy closure that accepts QCase object and returns QueryDSL Predicate.
Example
Case useCase = findCase( { it.title.eq("Case 1") & it.processIdentifier.eq("insurance") } ); ... Case useCase = findCase( { it.dataSet.get("name").value.eq("Jozko") & it.processIdentifier.eq("insurance") } );
List<Task> findTasks(Closure<Predicate> predicate)
Finds all tasks that match the given predicate. The predicate is a groovy closure that accepts QCase object and returns QueryDSL Predicate.
Example
def useCase = findCase(...) Task task = findTask( { it.caseId.eq(useCase.stringId) & it.transitionId.eq("edit_limit") } );
List<Task> findTasks(Closure<Predicate> predicate, Pageable page)
Finds all tasks that match the given predicate. The predicate is a groovy closure that accepts QCase object and returns QueryDSL Predicate. Pageable determines the requested page number, page size, sort fields, and sort direction.
Example
// find 10 tasks sorted by priority def newTasks = findTasks( { it.transitionId.eq("new_task") }, new PageRequest(0, 10, Sort.by("priority").descending() ) )
Task findTask(Closure<Predicate> predicate)
Finds the first task that matches the given predicate. The predicate is a groovy closure that accepts QCase object and returns QueryDSL Predicate.
Example
List<Task> tasks = findTasks( { it.transitionId.eq("edit_limit") } ) ... def useCase = findCase(...) List<Task> tasks = findTasks( { it.caseId.eq(useCase.stringId) } );
close <List<Transition>>
Deprecated
See cancel.
execute <String transitionId> where <Closure<Predicate>> with <Map>
Executes all fireable transitions identified by the transitionId in all case where the predicate returns true. For each task following actions are called:
assign to the system user
save new data values
finish.
The predicate is a list of Querydsl queries. Every case property can be used in a query. For more info see querydsl doc and QCase javadoc.
Example
field: f.field; execute "synchronized" where ([ "title eq Case 1" ] as List) with ([ "field": [ value: 128.0, type: "number" ] ] as Map)
Task assignTask(String transitionId, Case aCase = useCase, User user = userService.loggedOrSystem)
Assign the task in current case with given transitionId. Optional parameter aCase identifies case which the task belongs to. Optional parameter user identifies actor who will perform assign.
Example
selectedUser: f.select_controler; if (selectedUser.value) { def aCase = findCase({ it.author.id.eq(selectedUser.value.id) }) def user = userService.findById(selectedUser.value.id, false) assignTask("control", aCase, user); }
Task assignTask(Task task, User user = userService.loggedOrSystem)
Assign the task to user. Optional parameter user identifies actor who will perform assign.
Example
selectedUser: f.select_controler; if (selectedUser.value) { def usecase = findCase({ it.title("Some case") }).first() def task = findTask({ it.importId.eq("control") & it.caseId.eq(usecase.stringId) }) def user = userService.findById(selectedUser.value.id, false) assignTask(task, user); }
assignTasks(List<Task> tasks, User assignee = userService.loggedOrSystem)
Assign the tasks to user. Optional parameter user identifies actor who will perform assign.
Example
// find all my cases and assign all their control tasks to me def cases = findCases( { it.author.id.eq(loggedUser().id)) } ) def caseIds = cases.collect { it.stringId } def tasks = findTasks({ it.importId.eq("control") & it.caseId.in(cases) }) assignTasks(tasks)
cancelTask(String transitionId, Case aCase = useCase, User user = userService.loggedOrSystem)
Cancels the task in current case with given transitionId. Optional parameter aCase identifies case which the task belongs to. Optional parameter user identifies actor who will perform cancel.
Example
def taskId = "work_task"; def aCase = findCase({ it.author.id.eq(loggedUser().id) }) cancelTask(taskId, aCase);
cancelTask(Task task, User user = userService.loggedOrSystem)
Cancels the provided task. Optional parameter user identifies actor who will perform cancel.
Example
// cancel the task "work_task", currently assigned to me, in the current case def task = findTask( { it.transitionId.eq("work_task") } ); cancelTask(task);
cancelTasks(List<Task> tasks, User user = userService.loggedOrSystem)
Cancels all the provided tasks. Optional parameter user identifies actor who will perform cancel.
Example
// cancel the task "work_task", currently assigned to me, in the current case def tasks = findTasks( { it.transitionId.eq("work_task") } ); cancelTasks(tasks);
finishTask(String transitionId, Case aCase = useCase, User user = userService.loggedOrSystem)
Finish the task in current case with given transitionId. Optional parameter aCase identifies case which the task belongs to. Optional parameter user identifies actor who will perform cancel.
Example
// finish the task "work_task", currently assigned to me, in the current case def taskId = "work_task"; def aCase = findCase({ it.author.id.eq(loggedUser().id) }) finishTask(taskId, aCase);
finishTask(Task task, User user = userService.loggedOrSystem)
Finish the provided task. Optional parameter user identifies actor who will perform cancel.
Example
// finish the task "work_task", currently assigned to me in current case def task = findTask( { it.transitionId.eq("work_task") & it.caseId.eq(useCase.stringId) & it.userId.eq(loggedUser().id) } ); finishTask(task);
finishTasks(List<Task> tasks, User user = userService.loggedOrSystem)
Finish all the provided tasks. Optional parameter user identifies actor who will perform cancel.
Example
// finish all the tasks "work_task", currently assigned to me def tasks = findTasks( { it.transitionId.eq("work_task") & it.userId.eq(loggedUser().id) } ); finishTasks(tasks);
setData(Task task, Map dataSet)
Sets values of data fields on given task. Values are mapped to data fields in dataSet using data fields import Id as key.
Example
def usecase = findCase({ it.title.eq("Limits") }).first() def task = findTask({ it.caseId.eq(usecase.stringId & it.transitionId.eq("edit_limit")) }) setData(task, [ "new_limit": [ "value": "10000", "type" : "number" ], ])
setData(Transition transition, Map dataSet)
Sets values of data fields on task of transition in current case. Values are mapped to data fields in dataSet using data fields import Id as key.
Example
transition: t.edit_limit; setData(transition, [ "new_limit": [ "value": "10000", "type" : "number" ], ])
setData(String transitionId, Case useCase, Map dataSet)
Sets values of data fields on task identified by transitionId of given case. Values are mapped to data fields in dataSet using data fields import Id as key.
Example
def usecase = findCase({ it.title.eq("Limits") }).first() setData("edit_limit", usecase, [ "new_limit": [ "value": "10000", "type" : "number" ], ])
Map<String, Field> getData(Task task)
Gets all data fields on given task, mapped by its import Id.
Example
actual_limit: f.actual_limit; def usecase = findCase({ it.title.eq("Limits") }).first() def task = findTask({ it.transitionId.eq("view_limit") & it.caseId.eq(usecase.stringId) }) def data = getData(task) change actual_limit value { data["remote_limit"].value }
Map<String, Field> getData(Transition transition)
Gets all data fields on the task of transition in the current case, mapped by its import Id.
Example
view_limit: t.view_limit; actual_limit: f.actual_limit; def data = getData(view_limit) change actual_limit value { data["remote_limit"].value }
Map<String, Field> getData(String transitionId, Case useCase)
Gets all data fields on the task defined by its transitionId in given case, mapped by its import Id.
Example
view_limit: t.view_limit; def usecase = findCase({ it.title.eq("Limits") }).first() def data = getData("view_limit", usecase) change actual_limit value { data["remote_limit"].value }
User assignRole(String roleId, User user = userService.loggedUser)
Assigns role identified by roleId to user. User is optional parameter, default value is currently logged user. Returns updated object of user.
Example
transition: t.task; assignRole(transition.defaultRoleId);
User loggedUser()
Returns currently logged user.
Example
userField: t.user; change userField value { return loggedUser() }