Skip to main content

Principle

This operation manager allows you to react to the execution of an operation by executing a JavaScript script. The JavaScript script is executed using the Graal engine embedded in the FlowerDocs Core JVM.

The script is stored as the content (attached .js file) of the OperationHandlerRegistration document used to configure the subscription.

Variables

The following variables are injected into the script depending on the type of operation:

VariableTypeAvailabilityDescription
contextOperationContextAlwaysOperation execution context (scope, user, phase, etc.)
componentComponentComponent operations (CREATE, UPDATE, DELETE, READ, ANSWER, ASSIGN)The component being operated on
requestSearchRequestSearch operationsThe search request being executed
responseSearchResponseSearch operations (AFTER phase only)The search response
loggerSLF4J LoggerAlwaysLogger for writing to the FlowerDocs Core log
utilContextUtilAlwaysUtility object for service access and component persistence

In BEFORE phase scripts, modifications to the component object are applied to the operation before it executes. In AFTER phase scripts, the component reflects its state after the operation completed.

Utilities

Two utility objects are available — they serve different purposes:

  • util is an instance of RuleContextUtil. Use it for any operation that calls FlowerDocs Core services: accessing services (util.getDocumentService(), util.getFolderService(), ...), persisting components (util.create(), util.update()), changing classes (util.changeClass()), and creating facts (util.createFact()).

  • RuleUtil is a static class reference. Use it for in-memory operations on the component object without making service calls: reading and writing tags (RuleUtil.getTagValue(), RuleUtil.setTagValue()), class identifiers (RuleUtil.getClassId(), RuleUtil.setClassId()), and statuses (RuleUtil.getStatus(), RuleUtil.setStatus()).

The full method reference for both objects is documented here.

info

Since util is an instance of a class that extends RuleUtil, calling static methods through util (e.g., util.setTagValue(...)) also works. However, the recommended convention is RuleUtil.method() for static helpers and util.method() for service calls.

Pre-imported classes

To simplify script development, the following classes are available by their short name without needing a full package reference:

ClassDescription
ComponentBuilderBuild Document, Folder, Task, or VirtualFolder instances
TagBuilderBuild Tag instances
CriterionBuilderBuild search criteria
FilterClauseBuilderBuild AND/OR filter clauses
SearchRequestBuilderBuild search requests
SearchBuilderBuild virtual folder searches
FactBuilderBuild audit facts
ReferenceBuilderBuild component references
ExceptionBuilderBuild functional or technical exceptions

Examples

Creating a folder when creating a document

AFTER / CREATE / DOCUMENT

var folder = ComponentBuilder.folder().classId('Folder').build();
folder.setName("Dossier " + component.getName());
util.getFolderService().create(Lists.newArrayList(folder));
util.getFolderService().addChildren(folder.getId(), Lists.newArrayList(ReferenceBuilder.from(component)), false);

Creating an audit fact

AFTER / CREATE / DOCUMENT — Record a business fact for traceability

logger.info("[CreateFactOnCreation] Build facts for " + component.getId());

var builder = FactBuilder.objectId(component.getId()).type(ObjectType.DOCUMENT);
builder.action("CREATE").description("Processing has been started.");
util.createFact(builder.build());

Workflow state machine (task class transitions)

BEFORE / ANSWER / TASK — Change task class based on the answer given

logger.info("[Workflow_OH] START");
var classId = RuleUtil.getClassId(component);
var answerId = component.getAnswer().getId().getValue();
logger.info("[Workflow_OH] classId=" + classId + ", answerId=" + answerId);

var start = "WKF_Step0_Creation";
var step1 = "WKF_Step1_Treatment";
var end = "WKF_Step2_End";

switch (classId) {
case start:
changeClassOnAnswer(answerId, "Initiate", step1);
break;
case step1:
if (!changeClassOnAnswer(answerId, "Validate", end)) {
changeClassOnAnswer(answerId, "Adjourn", start);
}
break;
default:
}
logger.info("[Workflow_OH] END");

function changeClassOnAnswer(appliedAnswer, expectedAnswer, classToApply) {
if (appliedAnswer === expectedAnswer) {
util.changeClass(component, classToApply);
logger.info("[Workflow_OH] Class changed to " + classToApply);
return true;
}
return false;
}

Search and conditionally create a virtual folder

AFTER / CREATE / DOCUMENT — Create a business folder if it does not already exist

let refValue = RuleUtil.getTagValue(component, "NumReference");
let clientValue = RuleUtil.getTagValue(component, "NomClient");

let refCriterion = CriterionBuilder.name("NumReference").operator(Operators.EQUALS_TO)
.type(Types.STRING).value(refValue).build();
let clientCriterion = CriterionBuilder.name("NomClient").operator(Operators.EQUALS_TO)
.type(Types.STRING).value(clientValue).build();
let request = SearchRequestBuilder.init()
.filter(FilterClauseBuilder.and().criterion(refCriterion).criterion(clientCriterion).build())
.build();

let response = util.getVirtualFolderService().search(request);
if (response.getFound() == 0) {
let vf = ComponentBuilder.virtualFolder().classId("BusinessFolder")
.name(refValue + " - " + clientValue).build();
vf.getTags().getTags().add(TagBuilder.name("NomClient").value(clientValue).build());
vf.getTags().getTags().add(TagBuilder.name("NumReference").value(refValue).build());
util.getVirtualFolderService().create([vf]);
}

Modifying tags and ACLs

BEFORE / ANSWER / TASK — Update tags and ACL on workflow step transition

var classId = RuleUtil.getClassId(component);
var answerId = component.getAnswer().getId().getValue();

if (classId == "Step0_Creation" && answerId === "Initiate") {
util.changeClass(component, "Step1_Treatment");
component.setAssignee("");
component.getData().setACL("acl-treatment");
util.setTagValue(component, "Status", "In Progress");
}

Throwing exceptions

A BEFORE-phase script can prevent the operation from executing by throwing an exception. This only works when StopOnException is set to true in the registration.

BEFORE — Reject the operation with a functional error

// Throw a functional exception (business rule violation)
throw ExceptionBuilder.createFunctionalException(FunctionalErrorCode.F00008);

// Throw a technical exception (system error)
throw ExceptionBuilder.createTechnicalException(TechnicalErrorCode.T00008);
info

To manually define this operation handler, the com.flower.docs.core.tsp.operation.script.ScriptOperationHandler identifier can be used as the value of the OperationHandler tag.

warning

The legacy identifier com.flower.docs.bpm.core.operation.ScriptOperationHandler is deprecated since version 2025.0. Use the identifier above instead. When migrating scripts from the legacy handler, replace equals() with == for string comparisons and Date.now() with String.valueOf(new Date().getTime()).