Library node
Overview
Section titled “Overview”The Library node is a special type of Action node used to define reusable code libraries that can be accessed by other nodes throughout the workflow. Library nodes promote code reuse, maintainability, and consistency by centralizing common functionality in one place rather than duplicating code across multiple nodes.
A Library node is created using an Action node and contains a script that defines a class or object with methods and properties. The library is then loaded in other nodes using ScriptUtils.evaluateFromNode("library_name"), making its functionality available throughout the workflow execution.
Node Properties
Section titled “Node Properties”| Property | Type | Description |
|---|---|---|
| Name | Text | The name that identifies this Library node. This name will be used by other nodes to load the library (e.g., “library_global_base”, “library_request_constants”, “library_utils”). Use descriptive names that clearly indicate the library’s purpose. |
| Id | Number | Unique identifier assigned to the node. This value is automatically generated by the system and uniquely identifies the node within the process definition. |
| Description | Textarea | Optional field for documenting the library’s purpose, the functions it provides, and usage examples. This is particularly important for libraries to help other developers understand what functionality is available. |
| Source position | Dropdown | Not applicable. Library nodes do not have source or target positions as they are not part of the workflow execution flow. They exist to provide functionality to other nodes. |
| Target position | Dropdown | Not applicable. Library nodes do not have source or target positions. |
| Script definition | Script Editor | Contains the library code that defines classes, methods, and properties. The script must return the class or object that represents the library. The editor can be accessed in two ways: - Textarea: Edit directly in the textarea field - Popup editor: Click the icon above the textarea to open a larger editing window The editor supports autocomplete functionality. Press Ctrl+Space while typing to display available options and code suggestions. |
Creating a Library
Section titled “Creating a Library”A library is defined by creating a class with static methods that provide reusable functionality. The script must end with a return statement that returns the class definition.
Example: Base Library
Section titled “Example: Base Library”The following example shows a comprehensive base library named library_global_base that provides common utility functions:
import com.openkm.sdk4j.impl.OKMWebservices;import com.openkm.sdk4j.bean.*;import com.openkm.okmflow.util.*;import com.openkm.okmflow.bean.*;import com.openkm.util.*;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.DeserializationFeature;import java.time.LocalDateTime;import java.time.Duration;
// Base library with common functions that can be used by all librariesclass BaseLibrary {
static void logInfo(String component, String message) { FileLogger.info(component, message); }
static void logError(String component, String message, Exception e = null) { FileLogger.error(component, message + "\n" + StackTraceUtils.toString(e)); }
static OKMWebservices getWebservices() { logInfo("BaseLibrary", "getWebservices"); return WebservicesHelper.getInstance(); }
static Object getContextValue(Map<String,Object> context, String key) { logInfo("BaseLibrary", "getContextValue"); return context.get(key); }
static void setContextValue(Map<String,Object> context, String key, Object value) { logInfo("BaseLibrary", "setContextValue"); context.put(key, value); }
// Get workflow initiator ID static String getInitiatorId(Map<String,Object> context) { logInfo("BaseLibrary", "getInitiatorId"); Actor actor = context.get("initiator"); return actor.getId(); }
// Get workflow initiator ID static long getProcessInstanceId(Map<String,Object> context) { logInfo("BaseLibrary", "getProcessInstanceId"); def procIns = context.get("processInstance"); return procIns.getId(); }
static String convertToJson(Object obj) { try { ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.writeValueAsString(obj); } catch (JsonProcessingException e) { logError("BaseLibrary", "Error converting object to JSON", e); throw e; } }
static <T> T convertFromJson(String jsonString, Class<T> targetClass) { try { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return objectMapper.readValue(jsonString, targetClass); } catch (JsonProcessingException e) { logError("BaseLibrary", "Error converting JSON to object of type: " + targetClass.getSimpleName(), e); throw e; } catch (IllegalArgumentException e) { logError("BaseLibrary", "Invalid JSON string or target class", e); throw e; } } static boolean hasDurationElapsed(String durationString, LocalDateTime startTime) { try { // Parse the ISO 8601 duration string Duration expectedDuration = Duration.parse(durationString);
// Get current time LocalDateTime currentTime = LocalDateTime.now();
// Calculate actual elapsed time since start time Duration elapsedDuration = Duration.between(startTime, currentTime);
// Check if elapsed time is greater than or equal to expected duration return elapsedDuration.compareTo(expectedDuration) >= 0;
} catch (Exception e) { logError("BaseLibrary", e.getMessage(), e); throw e; } }}
return BaseLibraryThis library provides several utility functions:
- Logging functions:
logInfo()andlogError()for consistent logging across the workflow - WebServices access:
getWebservices()to obtain the OpenKM API client - Context management:
getContextValue()andsetContextValue()for working with workflow variables - Workflow information:
getInitiatorId()andgetProcessInstanceId()to access workflow metadata - JSON utilities:
convertToJson()andconvertFromJson()for JSON serialization - Duration checking:
hasDurationElapsed()to check if a time period has elapsed
Loading and Using a Library
Section titled “Loading and Using a Library”To use a library in another node (Action, Decision, Task assignment, etc.), load it using ScriptUtils.evaluateFromNode("library_name") with the library node name:
import com.openkm.sdk4j.impl.OKMWebservices;import com.openkm.sdk4j.bean.*;import com.openkm.okmflow.util.*;import com.openkm.okmflow.bean.*;import com.openkm.util.*;import com.openkm.bean.form.*;
// Load libraryClass baseLibrary = ScriptUtils.evaluateFromNode("library_global_base");def initiatorId = baseLibrary.getInitiatorId(context);In this example:
- The library is loaded using
ScriptUtils.evaluateFromNode("library_global_base") - The returned class is stored in the variable
baseLibrary - Methods from the library can be called using
baseLibrary.methodName()
Common Library Patterns
Section titled “Common Library Patterns”Constants Library
Section titled “Constants Library”Libraries can be used to define constants that are used throughout the workflow:
class Constants { static final String CONTEXT_UUID = "uuid" static final String CONTEXT_INITIATOR = "initiator" static final String STATUS_PENDING = "PENDING" static final String STATUS_APPROVED = "APPROVED" static final String STATUS_REJECTED = "REJECTED"}
return ConstantsUtilities Library
Section titled “Utilities Library”Common utility functions for string manipulation, date handling, or business logic:
class Utilities { static String formatCurrency(double amount) { return String.format("$%.2f", amount) }
static boolean isValidEmail(String email) { return email.matches(/^[A-Za-z0-9+_.-]+@(.+)$/) }
static String generateReference() { return "REF-" + System.currentTimeMillis() }}
return UtilitiesBusiness Logic Library
Section titled “Business Logic Library”Complex business rules that are used in multiple decision points:
class BusinessRules { static boolean requiresManagerApproval(double amount) { return amount > 1000.0 }
static boolean requiresDirectorApproval(double amount) { return amount > 10000.0 }
static String determineApprovalLevel(double amount) { if (requiresDirectorApproval(amount)) { return "DIRECTOR" } else if (requiresManagerApproval(amount)) { return "MANAGER" } else { return "SUPERVISOR" } }}
return BusinessRulesBenefits of Using Libraries
Section titled “Benefits of Using Libraries”- Code reuse: Write common functionality once and use it in multiple nodes
- Maintainability: Update logic in one place rather than in multiple nodes
- Consistency: Ensure the same logic is applied consistently across the workflow
- Testability: Library functions can be tested independently
- Readability: Workflow node scripts become cleaner and more focused
- Modularity: Separate concerns into specialized libraries
Best Practices
Section titled “Best Practices”- Use descriptive library names that indicate their purpose (e.g.,
library_global_base,library_invoice_utils) - Prefix library names with
library_to distinguish them from regular action nodes - Document each library function with comments explaining parameters and return values
- Keep libraries focused - create separate libraries for different functional areas
- Use static methods for utility functions that don’t need instance state
- Include error handling and logging in library functions
- Test library functions thoroughly before using them in production workflows
- Version your libraries when making significant changes
- Document dependencies between libraries if one library uses another
- Keep library code efficient - these functions may be called many times during workflow execution