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