Library Node

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

PropertyTypeDescription
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.

Library nodes are not executed as part of the workflow flow. They serve as code repositories that other nodes can access when needed. Think of them as shared utility modules.

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

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 libraries
class 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 BaseLibrary

This library provides several utility functions:

  • Logging functions: logInfo() and logError() for consistent logging across the workflow
  • WebServices access: getWebservices() to obtain the OpenKM API client
  • Context management: getContextValue() and setContextValue() for working with workflow variables
  • Workflow information: getInitiatorId() and getProcessInstanceId() to access workflow metadata
  • JSON utilities: convertToJson() and convertFromJson() for JSON serialization
  • Duration checking: hasDurationElapsed() to check if a time period has elapsed

Important: The library script must end with a return statement that returns the class. Without this, other nodes will not be able to access the library's functionality.

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 library
Class baseLibrary = ScriptUtils.evaluateFromNode("library_global_base");
def initiatorId = baseLibrary.getInitiatorId(context);

In this example:

  1. The library is loaded using ScriptUtils.evaluateFromNode("library_global_base")
  2. The returned class is stored in the variable baseLibrary
  3. Methods from the library can be called using baseLibrary.methodName()

Common Library Patterns

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 Constants

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 Utilities

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 BusinessRules

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

  • 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 

Library nodes are essential for building maintainable, scalable workflows. They enable you to create a robust framework of reusable functions that simplify workflow development and reduce errors.