---
version: 37
data:

- type: "TaskStageHook"
  variation: null
  lastChangedDate: "2025-05-08T10:04:25"
  lastChangedBy: "admin"
  replacePermissions: false
  replaceObject: true
  attributes:
    code: "import java.time.*\nimport groovy.json.JsonOutput\nimport groovy.json.JsonSlurper\n\
      /********************************************************************************************************************************\n\
      \ * Control settings\n *******************************************************************************************************************************/\n\
      \ndef retentionPeriod = \"P730D\"                       // The period how long\
      \ a workflow, task or execution should be kept\ndef retentionCount = 250   \
      \                         // How many workflows and tasks should be kept at\
      \ minimum per template\ndef implizitOnly = true                            \
      \ // Specifies if only implizit created tasks and workflows should be deleted\n\
      def dryRun = true                                   // Specifies if only a dry\
      \ run will be done generating a list of objects that should be deleted\n\n/*\n\
      \ * Switch settings based on the current XDM instance\n */\ndef environment\
      \ = System.getenv(\"xdm.core.environment-id\")\nswitch (environment) {\n   \
      \ case 'int': \n        retentionPeriod = \"P30D\"                        //\
      \ The period how long a workflow, task or execution should be kept\n       \
      \ retentionCount = 30                             // How many workflows and\
      \ tasks should be kept at minimum per template\n        //implizitOnly = true\
      \                             // Specifies if only implizit created tasks and\
      \ workflows should be deleted\n        //dryRun = true                     \
      \              // Specifies if only a dry run will be done generating a list\
      \ of objects that should be deleted\n        break\n        retentionPeriod\
      \ = \"P30D\"                        // The period how long a workflow, task\
      \ or execution should be kept\n        retentionCount = 30                 \
      \            // How many workflows and tasks should be kept at minimum per template\n\
      \        //implizitOnly = true                             // Specifies if only\
      \ implizit created tasks and workflows should be deleted\n        //dryRun =\
      \ true                                   // Specifies if only a dry run will\
      \ be done generating a list of objects that should be deleted\n        break\n\
      \    default:\n        retentionPeriod = \"P250D\"                       //\
      \ The period how long a workflow, task or execution should be kept\n       \
      \ retentionCount = 730                            // How many workflows and\
      \ tasks should be kept at minimum per template\n        //implizitOnly = true\
      \                             // Specifies if only implizit created tasks and\
      \ workflows should be deleted\n        //dryRun = true                     \
      \              // Specifies if only a dry run will be done generating a list\
      \ of objects that should be deleted\n        break\n}\n\nprintln(\"Settings\
      \ for environment: ${environment}\")\nprintln(\"RetentionPeriod: ${retentionPeriod}\"\
      )\nprintln(\"retentionCount:  ${retentionCount}\")\nprintln(\"implizitOnly:\
      \    ${implizitOnly}\")\nprintln(\"dryRun:          ${dryRun}\")\n\n\n/********************************************************************************************************************************\n\
      \ * Prepare settings\n *******************************************************************************************************************************/\n\
      def retentionDateTime = LocalDateTime.now().minus(Duration.parse(retentionPeriod));\n\
      \n/********************************************************************************************************************************\n\
      \ * Build a list of objects to be deleted and delete them afterwards\n *******************************************************************************************************************************/\n\
      def deletePending = [];\nprintln(\"Delete objects older than ${retentionDateTime}\
      \ but keep at leaset ${retentionCount}\")\n\ndef deleteTasksAndBackups = checkTasksAndBackups(retentionDateTime,\
      \ retentionCount, implizitOnly)\ndeletePending.addAll(deleteTasksAndBackups)\n\
      \ndef deleteWorkflows = checkWorkflows(retentionDateTime, retentionCount, implizitOnly)\n\
      deletePending.addAll(deleteWorkflows)\n\ndef deleteExecutions = checkExecutions(retentionDateTime,\
      \ retentionCount)\ndeletePending.addAll(deleteExecutions)\n\n\ndeletePendingObjects(deletePending,\
      \ dryRun)\n\nSystem.exit(0)\n\n\n/********************************************************************************************************************************\n\
      \ * Function to delete the objects in the list\n *******************************************************************************************************************************/\n\
      def deletePendingObjects(def deletePendingList, def dryRun) {\n    println \"\
      \\nDelete pending object count: ${deletePendingList.size()}\"\n    def successful\
      \ = 0\n    def simulated = 0\n    def errors = 0\n    \n    def prefix = \"\"\
      \n    if (dryRun) {\n        prefix = \"simulate \"\n    }\n    \n    for(o\
      \ in deletePendingList) {\n        println \"${prefix}delete: ${o.type}: ${o.contextPath}\
      \ -> ${o.name}: ${o.reason}\";\n        try {\n            if (dryRun) {\n \
      \               simulated++\n            } else {\n                restTemplate.delete(o.contextPath)\n\
      \                successful++\n            }\n        } catch (err) {\n    \
      \        println \"Could not delete object\";\n            println err;\n  \
      \          errors++\n        }\n    }\n    println(\"Deleted: successful ${successful}\
      \ simulated ${simulated} error ${errors}\")\n}\n\n\n\n/********************************************************************************************************************************\n\
      \ * Function to collect executions to be deleted\n *******************************************************************************************************************************/\n\
      def checkExecutions(def retentionDateTime, def retentionCount) {\n    println(\"\
      Start collecting Executions\")\n    def deletePendingList = []\n    \n    def\
      \ page = 1\n    def size = 50\n    def countExecutions = 0\n    def readMore\
      \ = true\n    \n    while (readMore) {\n        def path = \"/task-execution?page=${page}&size=${size}&sort=startTime:asc&\"\
      \n\n        String output = restTemplate.getForObject(path, String.class);\n\
      \        try {\n            def json = new JsonSlurper().parseText(output)\n\
      \t\t\t if (json._embedded == null) {\n                readMore = false\n   \
      \             break;\n\t\t\t }\n        \n            def executionList = json._embedded.executionResourceList\n\
      \            \n            for (execution in executionList) {\n            \
      \    ++countExecutions;\n                def ex = [:]\n                ex.contextPath\
      \ = execution._links.delete.href.replace('{?cascade}', '')\n               \
      \ ex.startTime = execution.startTime\n                ex.endTime = execution.endTime\n\
      \                ex.status = execution.status\n                \n          \
      \      println(\"Execution: ${ex}\")\n                \n                def\
      \ fields = execution._meta\n                def workFlowTemplate = null;\n \
      \               def taskTemplate = null;\n                def workflow = null;\n\
      \                def task = null;\n                def executeable = null;\n\
      \                \n                for (field in fields) {\n               \
      \     if (field.field == \"workflowTemplate\") {\n                        workflowTemplate\
      \ = field.displayName\n                    }\n                    if (field.field\
      \ == \"taskTemplate\") {\n                        taskTemplate = field.displayName\n\
      \                    }\n                    if (field.field == \"workflow\"\
      ) {\n                        workflow = field.displayName\n                \
      \    }\n                    if (field.field == \"task\") {\n               \
      \         task = field.displayName\n                    }\n                \
      \    if (field.field == \"executeable\") {\n                        executeable\
      \ = field.displayName\n                    }\n                }\n          \
      \      \n                if (workflowTemplate && workflow) {\n             \
      \       ex.displayName = \"${workflowTemplate}.${workflow}\"\n             \
      \   }\n                else if (taskTemplate && task) {\n                  \
      \  ex.displayName = \"${taskTemplate}.${task}\"\n                }\n       \
      \         \n                if (execution.status == \"COMPLETE\" || execution.status\
      \ == \"ERROR\") {\n                    if (retentionDateTime.isAfter(LocalDateTime.parse(execution.endTime))\n\
      \                        || (retentionCount > 0 && countExecutions >= retentionCount))\
      \ {\n                        deletePendingList.push([\"type\": \"Execution\"\
      , \"object\": ex, \"name\": \"no name\", \"contextPath\": \"${ex.contextPath}\"\
      , \"\"), \"reason\": \"${LocalDateTime.parse(execution.endTime)}\"])\n     \
      \               }\n                }\n                \n                if (LocalDateTime.parse(execution.startTime).isAfter(retentionDateTime))\
      \ {\n                    println(\"Execution found which is after the retention\
      \ time, so no more executions need to be read\")\n                    readMore\
      \ = false\n                    break\n                }\n            }\n\t\t\
      } catch (Exception e) {\n            println(\"Read URL: ${path}\")\n\t\t\t\
      throw new RuntimeException(\"Error parsing JSON: ${e}\")\n\t\t}\n        \n\
      \        page += 1\n    }\n    \n    println(\"Read ${countExecutions} executions\"\
      )\n    return deletePendingList\n}\n\n\n\n/********************************************************************************************************************************\n\
      \ * Function to collect tasks and icebox generations to be deleted\n *******************************************************************************************************************************/\n\
      def checkTasksAndBackups(def retentionDateTime, def retentionCount, def implicitOnly)\
      \ {\n    println(\"Start collecting Tasks and Backups\")\n    def deletePendingList\
      \ = []\n    \n    def taskTemplates = api.taskTemplates\n    for (tt in taskTemplates){\n\
      \        println(\"TaskTemplateType: ${tt.taskTemplateType} TaskTemplate: ${tt.displayName}\
      \ \")\n\n        // If we found a backup/icebox task iterate through the list\
      \ of icebox generations\n        // Check the expire date of an icebox generation\n\
      \      \n        if (tt.taskTemplateType.endsWith(\"-backup-task-template\"\
      )) {\n            for (b in tt.backup) {\n                println(\"...Icebox(${b.generation}}\
      \ CreatedAt: ${b.createdOn} ExpiresOn: \" + (b.expiresOn ? (b.expiresOn != \"\
      \" ? b.expiresOn : \"\") : \"NULL\"))\n                if (b.expiresOn && b.expiresOn\
      \ != \"\") {\n                    // Convert the timestamp to date to ignore\
      \ the time part\n                    def expireDate = LocalDateTime.parse(b.expiresOn).toLocalDate()\n\
      \                    def today = LocalDate.now()\n                  \n     \
      \               if (expireDate.isBefore(today)) {\n                        deletePendingList.push([\"\
      type\": \"Icebox\", \"object\": b, \"name\": \"${tt.displayName}/Icebox(${b.generation})\"\
      , \"contextPath\": \"${b.contextPath}\", \"reason\": \"${expireDate}\"])\n \
      \                   }\n                }\n            }\n        }\n       \
      \ \n        def taskList = tt.taskList\n            .findAll { it.lastExecutionTime\
      \ != null }\n            .sort { t1, t2 -> LocalDateTime.parse(t2.lastExecutionTime).compareTo(LocalDateTime.parse(t1.lastExecutionTime))\
      \ }\n        \n        // Iterate through the list of tasks and check which\
      \ one must be deleted,\n        // because it either expired the retention period\
      \ or the retention count.\n        \n        def idx = 0\n        for(t in taskList)\
      \ {\n            println(\"...Task: ${t.displayName} -> ${t.contextPath} Last\
      \ execution: ${t.lastExecutionTime} ImplicitCreated: ${t.implicitCreated}\"\
      )\n            \n            // If the task is not implicitly created we skip\
      \ it if the user don't wants to process the implicit workflows.\n          \
      \  if (t.implicitCreated == false && implicitOnly == true) {\n             \
      \   continue;\n            }\n            \n            if (retentionDateTime.isAfter(LocalDateTime.parse(t.lastExecutionTime))\
      \ \n               && (retentionCount == 0 || idx >= retentionCount)) {\n  \
      \              deletePendingList.push([\"type\": \"Task\", \"object\": t, \"\
      name\": \"${tt.displayName}/${t.displayName}\", \"contextPath\": \"${t.contextPath}\"\
      , \"reason\": \"${LocalDateTime.parse(t.lastExecutionTime)}\"])\n          \
      \  }\n            idx++;\n        }\n    }\n    return deletePendingList\n}\n\
      \n\n/********************************************************************************************************************************\n\
      \ * Function to collect the workflows to be deleted\n *******************************************************************************************************************************/\n\
      def checkWorkflows(def retentionDateTime, def retentionCount, def implicitOnly)\
      \ {\n    println(\"Start collecting Workflows\")\n    deletePendingList = []\n\
      \    def workflowTemplates = api.workflowTemplates\n    for (wt in workflowTemplates){\n\
      \        println(\"WorkflowTemplate: ${wt.displayName}\")\n                \n\
      \        def workflowList = wt.workflowList\n            .findAll { it.lastExecutionTime\
      \ != null }\n            .sort { w1, w2 -> LocalDateTime.parse(w2.lastExecutionTime).compareTo(LocalDateTime.parse(w1.lastExecutionTime))\
      \ }\n        \n        // Iterate through the list of workflows and check which\
      \ one must be deleted,\n        // because it either expired the retention period\
      \ or the retention count.        \n        def idx = 0\n        for(w in workflowList)\
      \ {\n            println(\"...Workflow: ${w.displayName} -> ${w.contextPath}\
      \ Last execution: ${w.lastExecutionTime} ImplicitCreated: ${w.implicitCreated}\"\
      )\n            \n            // If the task is not implicitly created we skip\
      \ it if the user don't wants to process the implicit workflows.\n          \
      \  if (w.implicitCreated == false && implicitOnly == true) {\n             \
      \   continue;\n            }\n            \n            if (retentionDateTime.isAfter(LocalDateTime.parse(w.lastExecutionTime))\
      \ \n               && (retentionCount == 0 || idx >= retentionCount)) {\n  \
      \              deletePendingList.push([\"type\": \"Workflow\", \"object\": w,\
      \ \"name\": \"${wt.displayName}/${w.displayName}\", \"contextPath\": \"${w.contextPath}\"\
      , \"reason\": \"${LocalDateTime.parse(w.lastExecutionTime)}\"])\n          \
      \  }\n            idx++;\n        }\n    }\n    return deletePendingList\n}"
    codeType: "GROOVY"
    description: null
    displayName: "Delete all expired objects"
    libraries: []
    mappingTableAccessMode: "READ"
    mappingTableContainers: []
    parameters: []
    tags: []