001/*
002 * (C) Copyright 2012-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.platform.routing.core.impl;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Calendar;
024import java.util.Date;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.commons.lang3.BooleanUtils;
029import org.apache.commons.lang3.builder.ToStringBuilder;
030import org.nuxeo.ecm.core.api.CoreSession;
031import org.nuxeo.ecm.core.api.DocumentModel;
032import org.nuxeo.ecm.core.api.model.Property;
033import org.nuxeo.ecm.core.api.model.impl.ListProperty;
034import org.nuxeo.ecm.core.api.model.impl.MapProperty;
035import org.nuxeo.ecm.platform.routing.api.DocumentRoute;
036import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteException;
037
038/**
039 * A node for a route graph. Represents operation chains, associated task and form, output transitions and their
040 * conditions, etc.
041 *
042 * @since 5.6
043 */
044public interface GraphNode {
045
046    String MERGE_ONE = "one";
047
048    String MERGE_ALL = "all";
049
050    String PROP_NODE_ID = "rnode:nodeId";
051
052    String PROP_TITLE = "dc:title";
053
054    String PROP_START = "rnode:start";
055
056    String PROP_STOP = "rnode:stop";
057
058    String PROP_MERGE = "rnode:merge";
059
060    String PROP_COUNT = "rnode:count";
061
062    String PROP_CANCELED = "rnode:canceled";
063
064    String PROP_INPUT_CHAIN = "rnode:inputChain";
065
066    String PROP_OUTPUT_CHAIN = "rnode:outputChain";
067
068    String PROP_HAS_TASK = "rnode:hasTask";
069
070    String PROP_VARIABLES_FACET = "rnode:variablesFacet";
071
072    String PROP_TRANSITIONS = "rnode:transitions";
073
074    String PROP_TRANS_NAME = "name";
075
076    String PROP_TRANS_TARGET = "targetId";
077
078    String PROP_TRANS_CONDITION = "condition";
079
080    String PROP_TRANS_RESULT = "result";
081
082    String PROP_TRANS_CHAIN = "chain";
083
084    String PROP_TRANS_LABEL = "label";
085
086    /**
087     * @since 7.1 a transition can hold a custom path
088     */
089    String PROP_TRANS_PATH = "path";
090
091    String PROP_TASK_ASSIGNEES = "rnode:taskAssignees";
092
093    String PROP_TASK_ASSIGNEES_VAR = "rnode:taskAssigneesExpr";
094
095    String PROP_TASK_ASSIGNEES_PERMISSION = "rnode:taskAssigneesPermission";
096
097    String PROP_TASK_DUE_DATE = "rnode:taskDueDate";
098
099    String PROP_TASK_DIRECTIVE = "rnode:taskDirective";
100
101    String PROP_TASK_LAYOUT = "rnode:taskLayout";
102
103    String PROP_TASK_BUTTONS = "rnode:taskButtons";
104
105    String PROP_BTN_NAME = "name";
106
107    String PROP_BTN_LABEL = "label";
108
109    String PROP_BTN_FILTER = "filter";
110
111    String PROP_BTN_VALIDATE = "validate";
112
113    String PROP_NODE_X_COORDINATE = "rnode:taskX";
114
115    String PROP_NODE_Y_COORDINATE = "rnode:taskY";
116
117    /**
118     * @since 5.7.3 a node can create multiple tasks, in this case, this stores the status of the last task ended
119     */
120    String PROP_NODE_BUTTON = "rnode:button";
121
122    String PROP_NODE_START_DATE = "rnode:startDate";
123
124    String PROP_NODE_END_DATE = "rnode:endDate";
125
126    String PROP_NODE_LAST_ACTOR = "rnode:lastActor";
127
128    String PROP_TASK_DOC_TYPE = "rnode:taskDocType";
129
130    String PROP_TASK_NOTIFICATION_TEMPLATE = "rnode:taskNotificationTemplate";
131
132    String PROP_TASK_DUE_DATE_EXPR = "rnode:taskDueDateExpr";
133
134    /** @since 5.7.2 */
135    String PROP_EXECUTE_ONLY_FIRST_TRANSITION = "rnode:executeOnlyFirstTransition";
136
137    /**
138     * The sub-route model id (expression) to run, if present.
139     *
140     * @since 5.7.2
141     */
142    String PROP_SUB_ROUTE_MODEL_EXPR = "rnode:subRouteModelExpr";
143
144    /**
145     * The sub-route instance id being run while this node is suspended.
146     *
147     * @since 5.7.2
148     */
149    String PROP_SUB_ROUTE_INSTANCE_ID = "rnode:subRouteInstanceId";
150
151    /**
152     * The sub-route variables to set (key/value list).
153     *
154     * @since 5.7.2
155     */
156    String PROP_SUB_ROUTE_VARS = "rnode:subRouteVariables";
157
158    /** @since 5.7.2 */
159    String PROP_KEYVALUE_KEY = "key";
160
161    /** @since 5.7.2 */
162    String PROP_KEYVALUE_VALUE = "value";
163
164    // @since 5.7.2
165    String PROP_ESCALATION_RULES = "rnode:escalationRules";
166
167    // @since 5.7.2
168    String PROP_ESCALATION_RULE_ID = "name";
169
170    // @since 5.7.2
171    String PROP_ESCALATION_RULE_LABEL = "label";
172
173    // @since 5.7.2
174    String PROP_ESCALATION_RULE_MULTIPLE_EXECUTION = "multipleExecution";
175
176    // @since 5.7.2
177    String PROP_ESCALATION_RULE_CONDITION = "condition";
178
179    // @since 5.7.2
180    String PROP_ESCALATION_RULE_CHAIN = "chain";
181
182    // @since 5.7.2
183    String PROP_ESCALATION_RULE_EXECUTED = "executed";
184
185    // @since 5.9.3
186    String PROP_LAST_EXECUTION_TIME = "executionDate";
187
188    // @since 5.7.3
189    String PROP_HAS_MULTIPLE_TASKS = "rnode:hasMultipleTasks";
190
191    // @since 5.7.3
192    String PROP_TASKS_INFO = "rnode:tasksInfo";
193
194    // @since 5.7.3
195    String PROP_TASK_INFO_ACTOR = "actor";
196
197    // @since 5.7.3
198    String PROP_TASK_INFO_COMMENT = "comment";
199
200    // @since 5.7.3
201    String PROP_TASK_INFO_STATUS = "status";
202
203    // @since 5.7.3
204    String PROP_TASK_INFO_ENDED = "ended";
205
206    // @since 5.7.3
207    String PROP_TASK_INFO_TASK_DOC_ID = "taskDocId";
208
209    // @since 5.7.3
210    String PROP_ALLOW_TASK_REASSIGNMENT = "rnode:allowTaskReassignment";
211
212    // @since 5.6
213    // if present as the node variable, acts as a built-in variable:
214    // is passed to the task comment and logged in by audit;
215    // internally rested when set on node having multiple tasks.
216    String NODE_VARIABLE_COMMENT = "comment";
217
218    /**
219     * The internal state of a node.
220     */
221    enum State {
222        /** Node is ready. */
223        READY("ready", "toReady"),
224        /** Merge node is waiting for more incoming transitions. */
225        WAITING("waiting", "toWaiting"),
226        /** While executing input phase. Not persisted. */
227        RUNNING_INPUT,
228        /** Task node is waiting for task to be done. */
229        SUSPENDED("suspended", "toSuspended"),
230        /** While executing output phase. Not persisted. */
231        RUNNING_OUTPUT;
232
233        private final String lifeCycleState;
234
235        private final String transition;
236
237        private State() {
238            lifeCycleState = null;
239            transition = null;
240        }
241
242        private State(String lifeCycleState, String transition) {
243            this.lifeCycleState = lifeCycleState;
244            this.transition = transition;
245        }
246
247        /**
248         * Corresponding lifecycle state.
249         */
250        public String getLifeCycleState() {
251            return lifeCycleState;
252        }
253
254        /**
255         * Transition leading to this state.
256         */
257        public String getTransition() {
258            return transition;
259        }
260
261        public static State fromString(String s) {
262            try {
263                return State.valueOf(s.toUpperCase());
264            } catch (IllegalArgumentException e) {
265                throw new IllegalArgumentException(s);
266            }
267        }
268    }
269
270    /**
271     * @since 7.1
272     */
273    class Point {
274
275        public double x;
276
277        public double y;
278
279        public Point(double x, double y) {
280            this.x = x;
281            this.y = y;
282        }
283    }
284
285    class Transition implements Comparable<Transition>, Serializable {
286
287        public GraphNode source;
288
289        public MapProperty prop;
290
291        public String id;
292
293        public String condition;
294
295        public String chain;
296
297        public String target;
298
299        public String label;
300
301        public boolean result;
302
303        /**
304         * @since 7.1
305         */
306        public List<Point> path;
307
308        /** Computed by graph. */
309        public boolean loop;
310
311        protected Transition(GraphNode source, Property p) {
312            this.source = source;
313            prop = (MapProperty) p;
314            id = (String) prop.get(PROP_TRANS_NAME).getValue();
315            condition = (String) prop.get(PROP_TRANS_CONDITION).getValue();
316            chain = (String) prop.get(PROP_TRANS_CHAIN).getValue();
317            target = (String) prop.get(PROP_TRANS_TARGET).getValue();
318            label = (String) prop.get(PROP_TRANS_LABEL).getValue();
319            Property resultProp = prop.get(PROP_TRANS_RESULT);
320            if (resultProp != null) {
321                result = BooleanUtils.isTrue(resultProp.getValue(Boolean.class));
322            }
323        }
324
325        protected void setResult(boolean bool) {
326            result = bool;
327            prop.get(PROP_TRANS_RESULT).setValue(Boolean.valueOf(bool));
328        }
329
330        @Override
331        public int compareTo(Transition other) {
332            return id.compareTo(other.id);
333        }
334
335        @Override
336        public String toString() {
337            return new ToStringBuilder(this).append("id", id).append("condition", condition).append("result", result).toString();
338        }
339
340        public String getTarget() {
341            return target;
342        }
343
344        public String getId() {
345            return id;
346        }
347
348        public String getLabel() {
349            return label;
350        }
351
352        /**
353         * @since 7.1
354         */
355        public List<Point> getPath() {
356            if (path == null) {
357                path = computePath();
358            }
359            return path;
360        }
361
362        protected List<Point> computePath() {
363            ListProperty props = (ListProperty) prop.get(PROP_TRANS_PATH);
364            List<Point> points = new ArrayList<>(props.size());
365            for (Property p : props) {
366                points.add(new Point(
367                    (Double) p.get("x").getValue(),
368                    (Double) p.get("y").getValue()));
369            }
370            return points;
371        }
372    }
373
374    public class Button implements Comparable<Button> {
375
376        public GraphNode source;
377
378        public String name;
379
380        public String label;
381
382        public String filter;
383
384        public Boolean validate;
385
386        public MapProperty prop;
387
388        public Button(GraphNode source, Property p) {
389            this.source = source;
390            this.prop = (MapProperty) p;
391            name = (String) prop.get(PROP_BTN_NAME).getValue();
392            label = (String) prop.get(PROP_BTN_LABEL).getValue();
393            filter = (String) prop.get(PROP_BTN_FILTER).getValue();
394            validate = prop.getValue(Boolean.class, PROP_BTN_VALIDATE);
395        }
396
397        @Override
398        public int compareTo(Button other) {
399            return name.compareTo(other.name);
400        }
401
402        public String getLabel() {
403            return label;
404        }
405
406        public String getName() {
407            return name;
408        }
409
410        public String getFilter() {
411            return filter;
412        }
413
414        public Boolean getValidate() {
415            return validate;
416        }
417    }
418
419    /**
420     * @since 5.7.2
421     */
422    class EscalationRule implements Comparable<EscalationRule> {
423
424        protected String id;
425
426        protected String label;
427
428        protected boolean multipleExecution;
429
430        protected String condition;
431
432        protected boolean executed;
433
434        protected String chain;
435
436        protected MapProperty prop;
437
438        protected GraphNode node;
439
440        /**
441         * @since 5.9.3
442         */
443        protected Calendar lastExcutionTime;
444
445        public EscalationRule(GraphNode node, Property p) {
446            this.prop = (MapProperty) p;
447            this.node = node;
448            this.id = (String) p.get(PROP_ESCALATION_RULE_ID).getValue();
449            this.label = (String) p.get(PROP_ESCALATION_RULE_LABEL).getValue();
450            Property multipleEvaluationProp = prop.get(PROP_ESCALATION_RULE_MULTIPLE_EXECUTION);
451            if (multipleEvaluationProp != null) {
452                multipleExecution = BooleanUtils.isTrue(multipleEvaluationProp.getValue(Boolean.class));
453            }
454            this.condition = (String) p.get(PROP_ESCALATION_RULE_CONDITION).getValue();
455            Property evaluatedProp = prop.get(PROP_ESCALATION_RULE_EXECUTED);
456            if (evaluatedProp != null) {
457                executed = BooleanUtils.isTrue(evaluatedProp.getValue(Boolean.class));
458            }
459            this.chain = (String) p.get(PROP_ESCALATION_RULE_CHAIN).getValue();
460            this.lastExcutionTime = (Calendar) p.get(PROP_LAST_EXECUTION_TIME).getValue();
461        }
462
463        @Override
464        public int compareTo(EscalationRule o) {
465            return id.compareTo(o.id);
466        }
467
468        public String getLabel() {
469            return label;
470        }
471
472        public String getChain() {
473            return chain;
474        }
475
476        public GraphNode getNode() {
477            return node;
478        }
479
480        public void setExecuted(boolean executed) {
481            this.executed = executed;
482            prop.get(PROP_ESCALATION_RULE_EXECUTED).setValue(Boolean.valueOf(executed));
483            if (executed) {
484                setExecutionTime(Calendar.getInstance());
485            }
486        }
487
488        protected void setExecutionTime(Calendar time) {
489            prop.get(PROP_LAST_EXECUTION_TIME).setValue(time);
490            this.lastExcutionTime = time;
491        }
492
493        public boolean isExecuted() {
494            return executed;
495        }
496
497        public String getId() {
498            return id;
499        }
500
501        public boolean isMultipleExecution() {
502            return multipleExecution;
503        }
504
505        /**
506         * @since 5.9.3 Returns 'null' if the node was not executed, or the executed date was not computed ( for rules
507         *        created before 5.9.3)
508         */
509        public Calendar getLastExecutionTime() {
510            if (executed && lastExcutionTime != null) {
511                return lastExcutionTime;
512            }
513            return null;
514        }
515    }
516
517    /**
518     * @since 5.7.3
519     */
520    class TaskInfo implements Comparable<TaskInfo>, Serializable {
521
522        protected String taskDocId;
523
524        protected String actor;
525
526        protected String comment;
527
528        protected String status;
529
530        protected boolean ended;
531
532        protected MapProperty prop;
533
534        protected GraphNode node;
535
536        public TaskInfo(GraphNode node, Property p) {
537            this.prop = (MapProperty) p;
538            this.node = node;
539            this.taskDocId = (String) p.get(PROP_TASK_INFO_TASK_DOC_ID).getValue();
540            this.status = (String) p.get(PROP_TASK_INFO_STATUS).getValue();
541            this.actor = (String) p.get(PROP_TASK_INFO_ACTOR).getValue();
542            this.comment = (String) p.get(PROP_TASK_INFO_COMMENT).getValue();
543            Property ended = prop.get(PROP_TASK_INFO_ENDED);
544            if (ended != null) {
545                this.ended = BooleanUtils.isTrue(ended.getValue(Boolean.class));
546            }
547        }
548
549        public TaskInfo(GraphNode node, String taskDocId) {
550            this.node = node;
551            this.prop = (MapProperty) ((ListProperty) node.getDocument().getProperty(PROP_TASKS_INFO)).addEmpty();
552            this.prop.get(PROP_TASK_INFO_TASK_DOC_ID).setValue(taskDocId);
553            this.taskDocId = taskDocId;
554        }
555
556        @Override
557        public int compareTo(TaskInfo o) {
558            return taskDocId.compareTo(o.taskDocId);
559        }
560
561        public String getTaskDocId() {
562            return taskDocId;
563        }
564
565        public String getActor() {
566            return actor;
567        }
568
569        public String getComment() {
570            return comment;
571        }
572
573        public String getStatus() {
574            return status;
575        }
576
577        public GraphNode getNode() {
578            return node;
579        }
580
581        public boolean isEnded() {
582            return ended;
583        }
584
585        public void setComment(String comment) {
586            this.comment = comment;
587            prop.get(PROP_TASK_INFO_COMMENT).setValue(comment);
588        }
589
590        public void setStatus(String status) {
591            this.status = status;
592            prop.get(PROP_TASK_INFO_STATUS).setValue(status);
593
594        }
595
596        public void setActor(String actor) {
597            this.actor = actor;
598            prop.get(PROP_TASK_INFO_ACTOR).setValue(actor);
599        }
600
601        public void setEnded(boolean ended) {
602            this.ended = ended;
603            prop.get(PROP_TASK_INFO_ENDED).setValue(Boolean.valueOf(ended));
604        }
605    }
606
607    /**
608     * Get the node id.
609     *
610     * @return the node id
611     */
612    String getId();
613
614    /**
615     * Get the node state.
616     *
617     * @return the node state
618     */
619    State getState();
620
621    /**
622     * Set the node state.
623     *
624     * @param state the node state
625     */
626    void setState(State state);
627
628    /**
629     * Checks if this is the start node.
630     */
631    boolean isStart();
632
633    /**
634     * Checks if this is a stop node.
635     */
636    boolean isStop();
637
638    /**
639     * Checks if this is a merge node.
640     */
641    boolean isMerge();
642
643    /**
644     * Checks if the merge is ready to execute (enough input transitions are present).
645     */
646    boolean canMerge();
647
648    /**
649     * Notes that this node was canceled (increments canceled counter).
650     */
651    void setCanceled();
652
653    /**
654     * Gets the canceled count for this node.
655     */
656    long getCanceledCount();
657
658    /**
659     * Cancels the tasks not ended on this node.
660     */
661    void cancelTasks();
662
663    /**
664     * Get input chain.
665     *
666     * @return the input chain
667     */
668    String getInputChain();
669
670    /**
671     * Get output chain.
672     *
673     * @return the output chain
674     */
675    String getOutputChain();
676
677    /**
678     * Checks it this node has an associated user task.
679     */
680    boolean hasTask();
681
682    /**
683     * Gets the task assignees
684     *
685     * @return the task assignees
686     */
687    List<String> getTaskAssignees();
688
689    /**
690     * Gets the due date
691     */
692    Date getTaskDueDate();
693
694    /**
695     * Gets the task directive
696     */
697    String getTaskDirective();
698
699    /**
700     * Gets the permission to the granted to the actors on this task on the document following the workflow
701     */
702    String getTaskAssigneesPermission();
703
704    /**
705     * Gets the task layout
706     */
707    String getTaskLayout();
708
709    /**
710     * @return the taskDocType. If none is specified, the default task type is returned.
711     */
712    String getTaskDocType();
713
714    String getTaskNotificationTemplate();
715
716    /**
717     * Does bookkeeping at node start.
718     */
719    void starting();
720
721    /**
722     * Does bookkeeping at node end.
723     */
724    void ending();
725
726    /**
727     * Executes an Automation chain in the context of this node.
728     *
729     * @param chainId the chain
730     */
731    void executeChain(String chainId) throws DocumentRouteException;
732
733    /** Internal during graph init. */
734    void initAddInputTransition(Transition transition);
735
736    /**
737     * Gets the input transitions.
738     */
739    List<Transition> getInputTransitions();
740
741    /**
742     * Gets the output transitions.
743     */
744    List<Transition> getOutputTransitions();
745
746    String getTaskDueDateExpr();
747
748    /**
749     * Executes an Automation chain in the context of this node for a given transition
750     *
751     * @param transition the transition
752     */
753    void executeTransitionChain(Transition transition) throws DocumentRouteException;
754
755    /**
756     * Evaluates transition conditions and returns the transitions that were true.
757     * <p>
758     * Transitions are evaluated in the order set on the node when the workflow was designed. Since @5.7.2 if the node
759     * has the property "executeOnlyFirstTransition" set to true, only the first transition evaluated to true is
760     * returned
761     *
762     * @return the true transitions
763     */
764    List<Transition> evaluateTransitions() throws DocumentRouteException;
765
766    /**
767     * Sets the graph and node variables.
768     *
769     * @param map the map of variables
770     */
771    void setAllVariables(Map<String, Object> map);
772
773    /**
774     * Sets the graph and node variables.
775     *
776     * @param map the map of variables
777     * @param allowGlobalVariablesAssignement if set to false, throw a DocumentRouteException when trying to set global
778     *            variables when not supposed to
779     * @since 7.2
780     */
781    void setAllVariables(Map<String, Object> map, final boolean allowGlobalVariablesAssignement);
782
783    /**
784     * Gets the task buttons
785     */
786    List<Button> getTaskButtons();
787
788    /**
789     * Has the node the given action.
790     *
791     * @since 7.2
792     */
793    boolean hasTaskButton(final String name);
794
795    /**
796     * Gets the document representing this node
797     */
798    DocumentModel getDocument();
799
800    /**
801     * Gets a map containing the variables currently defined on this node
802     */
803    Map<String, Serializable> getVariables();
804
805    /**
806     * Gets a map containing the Json formatted variables currently defined on this node
807     * @since 7.2
808     */
809    Map<String, Serializable> getJsonVariables();
810
811    /**
812     * Sets the property button on the node, keeping the id of the last action executed by the user on the associated
813     * task if any
814     */
815    void setButton(String status);
816
817    /**
818     * Sets the last actor on a node (user who completed the task).
819     *
820     * @param actor the user id
821     */
822    void setLastActor(String actor);
823
824    /**
825     * Evaluates the task assignees from the taskAssigneesVar
826     */
827    List<String> evaluateTaskAssignees() throws DocumentRouteException;
828
829    /**
830     * Evaluates the task due date from the taskDueDateExpr and sets it as the dueDate
831     */
832    Date computeTaskDueDate() throws DocumentRouteException;
833
834    /**
835     * Gets a map containing the workflow and node variables and workflow documents.
836     *
837     * @param detached The documents added into this map can be detached or not
838     */
839    Map<String, Serializable> getWorkflowContextualInfo(CoreSession session, boolean detached);
840
841    /**
842     * When workflow engine runs an exclusive node, it evaluates the transition one by one and stops a soon as one of
843     * the transition is evaluated to true
844     *
845     * @since 5.7.2
846     */
847    boolean executeOnlyFirstTransition();
848
849    /**
850     * Checks if this node has a sub-route model defined.
851     *
852     * @return {@code true} if there is a sub-route
853     * @since 5.7.2
854     */
855    boolean hasSubRoute() throws DocumentRouteException;
856
857    /**
858     * Gets the sub-route model id.
859     * <p>
860     * If this is present, then this node will be suspended while the sub-route is run. When the sub-route ends, this
861     * node will resume.
862     *
863     * @return the sub-route id, or {@code null} if none is defined
864     * @since 5.7.2
865     */
866    String getSubRouteModelId() throws DocumentRouteException;
867
868    /**
869     * Starts the sub-route on this node.
870     *
871     * @return the sub-route
872     * @since 5.7.2
873     */
874    DocumentRoute startSubRoute() throws DocumentRouteException;
875
876    /**
877     * Cancels the sub-route if there is one.
878     *
879     * @since 5.7.2
880     */
881    void cancelSubRoute() throws DocumentRouteException;
882
883    /**
884     * Evaluates the rules for the escalation rules and returns the ones to be executed. The rules already executed and
885     * not having the property multipleExecution = true are also ignored
886     *
887     * @since 5.7.2
888     */
889    List<EscalationRule> evaluateEscalationRules();
890
891    /**
892     * Gets the list of all escalation rules for the node
893     *
894     * @since 5.7.2
895     */
896    List<EscalationRule> getEscalationRules();
897
898    /**
899     * Checks if this node has created multiple tasks, one for each assignees.
900     *
901     * @since 5.7.3
902     */
903    boolean hasMultipleTasks();
904
905    /**
906     * Gets all the tasks info for the tasks created from this node
907     *
908     * @since 5.7.3
909     */
910    List<TaskInfo> getTasksInfo();
911
912    /**
913     * Persist the info when a new task is created from this node
914     *
915     * @since 5.7.3
916     */
917    void addTaskInfo(String taskId);
918
919    /**
920     * Persist these info from the task on the node. Status is the id of the button clicked to end the task by the
921     * actor.
922     *
923     * @since 5.7.3
924     */
925    void updateTaskInfo(String taskId, boolean ended, String status, String actor, String comment);
926
927    /**
928     * Gets all the ended tasks originating from this node. This also counts the canceled tasks.
929     *
930     * @since 5.7.3
931     */
932    List<TaskInfo> getEndedTasksInfo();
933
934    /**
935     * Gets all the ended tasks originating from this node that were processed with a status. Doesn't count the canceled
936     * tasks.
937     *
938     * @since 5.7.3
939     */
940    List<TaskInfo> getProcessedTasksInfo();
941
942    /**
943     * Returns false if all tasks created from this node were ended.
944     *
945     * @since 5.7.3
946     */
947    boolean hasOpenTasks();
948
949    /**
950     * Returns true if tasks created from this node can be reassigned.
951     *
952     * @since 5.7.3
953     */
954    boolean allowTaskReassignment();
955
956    /**
957     * Sets the variable on this node if it exists as a Node Variable.
958     *
959     * @since 5.8
960     */
961    void setVariable(String name, String value);
962
963    /**
964     * Sets the node variables.
965     *
966     * @since 5.9.3, 5.8.0-HF11
967     * @param map the map of variables
968     */
969    void setVariables(Map<String, Serializable> map);
970
971    /**
972     * Sets the variables of the workflow based on their JSON representation (especially for scalar lists). Eg.
973     * Map<String, String> map = new HashMap<String, String>();
974     * map.put("contributors","[\"John Doe\", \"John Smith\"]"); map.put("title","Test Title");
975     *
976     * @param map the map of variables
977     * @since 5.9.3, 5.8.0-HF11
978     */
979    void setJSONVariables(Map<String, String> map);
980
981    /**
982     * @since 7.4
983     */
984    void removeTaskInfo(String taskId);
985
986}