DEV Community

Explorer
Explorer

Posted on

Trigger Previous Activity in Joget (Controlled Rollback Script)

1. Why This Script Is Useful

Sometimes a running process reaches a wrong step because of data issues, user mistakes, or production fixes.

This script lets you rollback selected process instances by:

  • matching only specific running instances,
  • aborting from a known current activity,
  • resuming at a target activity,
  • restoring workflow variables when possible.

2. Where to Use in Joget

  • Workflow Builder context: execute as an admin/maintenance script.
  • Best use case: production correction for specific process instances.
  • Not a form validator: this operates on workflow engine state.

3. Critical Safety Rules Before Running

  • Run only with a strict target instance list.
  • Confirm appId, processDefId, and processVersion first.
  • Keep logs enabled and review summary counts after execution.
  • Take backup/snapshot before mass correction.

4. Configuration You Must Edit

Update only these values in the script:

  • appId
  • processDefId
  • processVersion
  • activityDefIdToAbort
  • activityDefIdToResume
  • targetProcessInstanceIds

5. Full Working Code (Unchanged)

import org.joget.workflow.model.service.WorkflowManager;
import org.joget.apps.app.service.AppUtil;
import org.joget.commons.util.LogUtil;
import org.joget.workflow.model.WorkflowActivity;
import org.joget.workflow.model.WorkflowProcess;
import org.joget.workflow.model.WorkflowVariable;

import java.util.Collection;
import java.util.Map;
import java.util.HashMap;

/*
=====================================================================
 PURPOSE
=====================================================================
 This script performs a controlled workflow rollback / jump in Joget.

 - Finds running workflow instances of ONE app + ONE process + ONE version
 - Checks if the instance is currently at a specific activity
 - Force-aborts that activity
 - Starts ANY activity you define (earlier, later, or never-run)
 - Restores workflow variables if the target activity ran before
 - Re-evaluates assignments after rollback

 IMPORTANT:
 - This affects ONLY workflow state, NOT form table data
 - Designed for admin / production-fix usage
=====================================================================
*/

/*
=====================================================================
 GET WORKFLOW MANAGER
=====================================================================
*/
WorkflowManager wm = (WorkflowManager) AppUtil
        .getApplicationContext()
        .getBean("workflowManager");

/*
=====================================================================
 HELPER METHOD:
 Get the latest (current) activity of a process instance
=====================================================================
*/
public WorkflowActivity getLatestActivity(String processInstanceId) {
    Collection activityList = wm.getActivityList(
        processInstanceId,
        0,
        1,
        "dateCreated",
        true
    );

    if (activityList != null && !activityList.isEmpty()) {
        return (WorkflowActivity) activityList.iterator().next();
    }
    return null;
}

/*
=====================================================================
 HELPER METHOD:
 Get the most recent execution of ANY specific activity definition
 (Used to restore workflow variables if activity ran before)
=====================================================================
*/
public WorkflowActivity getLastRecentSpecificActivity(
        String processInstanceId,
        String activityDefId
) {
    Collection activityList = wm.getActivityList(
        processInstanceId,
        null,
        null,
        "dateCreated",
        true
    );

    if (activityList != null && !activityList.isEmpty()) {
        for (Object obj : activityList) {
            WorkflowActivity act = (WorkflowActivity) obj;
            if (activityDefId.equals(act.getActivityDefId())) {
                return act; // Return the latest matching execution
            }
        }
    }
    return null;
}

/*
=====================================================================
 HELPER METHOD:
 Extract all workflow variables from an activity instance
=====================================================================
*/
public Map extractWorkflowVariables(String activityInstanceId) {
    Map vars = new HashMap();

    Collection varList = wm.getActivityVariableList(activityInstanceId);
    for (Object obj : varList) {
        WorkflowVariable var = (WorkflowVariable) obj;
        String value = (var.getVal() != null) ? var.getVal().toString() : "";
        vars.put(var.getId(), value);
    }
    return vars;
}

/*
=====================================================================
 CONFIGURATION SECTION (MODIFY ONLY THIS PART)
=====================================================================
*/
String appId = "your_app_id";
String processDefId = "your_process_def_id";
String processVersion = "your_process_version";

/* Activity where process MUST currently be */
String activityDefIdToAbort = "your_current_activity_def_id";

/* Activity where process will be resumed (ANY activity you want) */
String activityDefIdToResume = "your_target_activity_def_id";

/* Restrict rollback to specific process instances */
String[] targetProcessInstanceIds = {
    "your_process_instance_id"
};

/*
=====================================================================
 SAFETY METHOD:
 Check if current instance is in target list
=====================================================================
*/
boolean isTargetInstance(String processInstanceId, String[] targets) {
    for (int i = 0; i < targets.length; i++) {
        if (processInstanceId.equals(targets[i])) {
            return true;
        }
    }
    return false;
}

/*
=====================================================================
 MAIN EXECUTION LOGIC
=====================================================================
*/
int totalMatched = 0;
int successCount = 0;
int failureCount = 0;

/* Fetch all running workflow instances */
Collection runningProcesses = wm.getRunningProcessList(
    appId,
    processDefId,
    null,
    processVersion,
    null,
    null,
    null,
    null
);

for (Object obj : runningProcesses) {

    WorkflowProcess process = (WorkflowProcess) obj;
    String processInstanceId = process.getInstanceId();

    /* Get current activity */
    WorkflowActivity currentActivity = getLatestActivity(processInstanceId);

    /* Skip if not at expected activity */
    if (currentActivity == null ||
        !activityDefIdToAbort.equals(currentActivity.getActivityDefId())) {
        continue;
    }

    totalMatched++;
    LogUtil.info("Auto-Rollback", "Matched instance --> " + processInstanceId);

    /* Safety check: only allowed instances */
    if (!isTargetInstance(processInstanceId, targetProcessInstanceIds)) {
        LogUtil.info("Auto-Rollback", "Skipped (not in target list) --> " + processInstanceId);
        continue;
    }

    /*
    ===============================================================
     Attempt to restore workflow variables from previous execution
     of the resume activity (if it exists)
    ===============================================================
    */
    Map wfVars = new HashMap();
    WorkflowActivity lastRunOfResumeActivity =
        getLastRecentSpecificActivity(processInstanceId, activityDefIdToResume);

    if (lastRunOfResumeActivity != null) {
        wfVars = extractWorkflowVariables(lastRunOfResumeActivity.getId());
        LogUtil.info("Auto-Rollback",
            "Workflow variables restored from previous execution of --> "
            + activityDefIdToResume);
    } else {
        LogUtil.info("Auto-Rollback",
            "No previous execution found for --> "
            + activityDefIdToResume
            + ", starting fresh");
    }

    /*
    ===============================================================
     Force start the chosen activity
    ===============================================================
    */
    boolean started = wm.activityStart(
        processInstanceId,
        activityDefIdToResume,
        true
    );

    if (started) {

        WorkflowActivity resumedActivity =
            getLatestActivity(processInstanceId);

        /* Restore variables if available */
        if (!wfVars.isEmpty()) {
            wm.activityVariables(resumedActivity.getId(), wfVars);
        }

        /* Re-evaluate assignments */
        wm.reevaluateAssignmentsForActivity(resumedActivity.getId());

        successCount++;
        LogUtil.info("Auto-Rollback",
            "SUCCESS rollback --> " + processInstanceId
            + " resumed at --> " + activityDefIdToResume);

    } else {
        failureCount++;
        LogUtil.info("Auto-Rollback",
            "FAILED rollback --> " + processInstanceId);
    }
}

/*
=====================================================================
 FINAL SUMMARY LOGS
=====================================================================
*/
LogUtil.info("Auto-Rollback", "TOTAL MATCHED --> " + totalMatched);
LogUtil.info("Auto-Rollback", "SUCCESS COUNT --> " + successCount);
LogUtil.info("Auto-Rollback", "FAILURE COUNT --> " + failureCount);
Enter fullscreen mode Exit fullscreen mode

6. Quick Test Checklist

  • Test on one non-critical instance first.
  • Verify target instance is currently at activityDefIdToAbort.
  • Confirm resumed activity is activityDefIdToResume.
  • Check assignment list after reevaluateAssignmentsForActivity.
  • Review logs for matched/success/failure counts.

7. Security and Publishing Note

  • Replace real app IDs, process IDs, version numbers, and instance IDs with generic placeholders before publishing.
  • Do not expose internal process naming conventions in public posts.

8. Final Note

Use this script as a controlled recovery tool, not as a routine workflow path. With strict targeting and logging, it becomes a reliable production-fix pattern.

Follow-up topic: Bulk rollback strategy with dry-run mode and audit report output.

Top comments (0)