[
  {
    "path": ".dockerignore",
    "content": "# General\n\n# Backend \nserver/build/libs\n\n# UI\n**/node_modules\nui/build"
  },
  {
    "path": ".gitattributes",
    "content": "gradlew eol=lf\n*.gradle eol=lf\n*.java eol=lf\n*.groovy eol=lf\nspring.factories eol=lf\n*.sh eol=lf\n\ndocs/* linguist-documentation\nserver/src/main/resources/swagger-ui/* linguist-vendored\n\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: 'type: bug'\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**Details**\nConductor version:\nPersistence implementation: Cassandra, Postgres, MySQL, Dynomite etc\nQueue implementation: Postgres, MySQL, Dynoqueues etc\nLock: Redis or Zookeeper?\nWorkflow definition:\nTask definition:\nEvent handler definition:\n\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.md",
    "content": "---\nname: Documentation\nabout: Something in the documentation that needs improvement\ntitle: \"[DOC]: \"\nlabels: 'type: docs'\nassignees: ''\n\n---\n\n## What are you missing in the docs\n\n## Proposed text\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Propose a new feature\ntitle: \"[FEATURE]: \"\nlabels: 'type: feature'\nassignees: ''\n\n---\n\nPlease read our [contributor guide](https://github.com/Netflix/conductor/blob/main/CONTRIBUTING.md) before creating an issue.   \nAlso consider discussing your idea on the [discussion forum](https://github.com/Netflix/conductor/discussions) first.\n\n## Describe the Feature Request\n_A clear and concise description of what the feature request is._\n\n## Describe Preferred Solution\n_A clear and concise description of what you want to happen._\n\n## Describe Alternatives\n_A clear and concise description of any alternative solutions or features you've considered._\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gradle\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    reviewers:\n      - \"aravindanr\"\n      - \"jxu-nflx\"\n      - \"apanicker-nflx\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "Pull Request type\n----\n- [ ] Bugfix\n- [ ] Feature\n- [ ] Refactoring (no functional changes, no api changes)\n- [ ] Build related changes (Please run `./gradlew generateLock saveLock` to refresh dependencies)\n- [ ] WHOSUSING.md\n- [ ] Other (please describe):\n\n**NOTE**: Please remember to run `./gradlew spotlessApply` to fix any format violations.\n\nChanges in this PR\n----\n\n_Describe the new behavior from this PR, and why it's needed_\nIssue #\n\nAlternatives considered\n----\n\n_Describe alternative implementation you have considered_\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "template: |\n  ## What’s Changed\n\n  $CHANGES\n\nname-template: 'v$RESOLVED_VERSION'\ntag-template: 'v$RESOLVED_VERSION'\n\ncategories:\n  - title: 'IMPORTANT'\n    label: 'type: important'\n  - title: 'New'\n    label: 'type: feature'\n  - title: 'Bug Fixes'\n    label: 'type: bug'\n  - title: 'Refactor'\n    label: 'type: maintenance'\n  - title: 'Documentation'\n    label: 'type: docs'\n  - title: 'Dependency Updates'\n    label: 'type: dependencies'\n\nversion-resolver:\n  minor:\n    labels:\n      - 'type: important'\n\n  patch:\n    labels:\n      - 'type: bug'\n      - 'type: maintenance'\n      - 'type: docs'\n      - 'type: dependencies'\n      - 'type: feature'\n\nexclude-labels:\n  - 'skip-changelog'\n  - 'gradle-wrapper'\n  - 'github_actions'\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non: [ push, pull_request ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n          fetch-depth: 0\n      - name: Gradle wrapper validation\n        uses: gradle/wrapper-validation-action@v1\n      - name: Set up Zulu JDK 17\n        uses: actions/setup-java@v3\n        with:\n          distribution: 'zulu'\n          java-version: '17'\n      - name: Cache SonarCloud packages\n        uses: actions/cache@v3\n        with:\n          path: ~/.sonar/cache\n          key: ${{ runner.os }}-sonar\n          restore-keys: ${{ runner.os }}-sonar\n      - name: Cache Gradle packages\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.gradle/caches\n            ~/.gradle/wrapper\n          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}\n          restore-keys: ${{ runner.os }}-gradle-\n      - name: Build with Gradle\n        if: github.ref != 'refs/heads/main'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n        run: |\n          ./gradlew build --scan\n      - name: Build and Publish snapshot\n        if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'\n        run: |\n          echo \"Running build for commit ${{ github.sha }}\"\n          ./gradlew build snapshot --scan\n        env:\n          NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}\n          NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}\n          NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}\n          NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}\n      - name: Publish Test Report\n        uses: mikepenz/action-junit-report@v3\n        if: always()\n        with:\n          report_paths: '**/build/test-results/test/TEST-*.xml'\n      - name: Upload build artifacts\n        uses: actions/upload-artifact@v3\n        with:\n          name: build-artifacts\n          path: '**/build/reports'\n      - name: Store Buildscan URL\n        uses: actions/upload-artifact@v3\n        with:\n          name: build-scan\n          path: 'buildscan.log'\n  build-ui:\n    runs-on: ubuntu-latest\n    container: cypress/browsers:node14.17.6-chrome100-ff98\n    defaults:\n      run:\n        working-directory: ui\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Install Dependencies\n        run: yarn install\n\n      - name: Build UI\n        run: yarn run build\n\n      - name: Run E2E Tests\n        uses: cypress-io/github-action@v4\n        with: \n          working-directory: ui\n          install: false\n          start: yarn run serve-build\n          wait-on: 'http://localhost:5000'\n      \n      - name: Run Component Tests\n        uses: cypress-io/github-action@v4\n        with: \n          working-directory: ui\n          install: false\n          component: true\n\n      - name: Archive test screenshots\n        uses: actions/upload-artifact@v2\n        if: failure()\n        with:\n          name: cypress-screenshots\n          path: ui/cypress/screenshots\n      \n      - name: Archive test videos\n        uses: actions/upload-artifact@v2\n        if: always()\n        with:\n          name: cypress-videos\n          path: ui/cypress/videos\n\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to NetflixOSS and Maven Central\non:\n  release:\n    types:\n      - released\n      - prereleased\n\npermissions:\n  contents: read\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    name: Gradle Build and Publish\n    steps:\n      - uses: actions/checkout@v3\n      - name: Set up Zulu JDK 17\n        uses: actions/setup-java@v3\n        with:\n          distribution: 'zulu'\n          java-version: '17'\n      - name: Cache Gradle packages\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.gradle/caches\n            ~/.gradle/wrapper\n          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}\n          restore-keys: |\n            ${{ runner.os }}-gradle-\n      - name: Publish candidate\n        if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc.')\n        run: ./gradlew -Prelease.useLastTag=true candidate --scan\n        env:\n          NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }}\n          NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }}\n          NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}\n          NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}\n          NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}\n          NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}\n      - name: Publish release\n        if: startsWith(github.ref, 'refs/tags/v') && (!contains(github.ref, '-rc.'))\n        run: ./gradlew -Prelease.useLastTag=true final --scan\n        env:\n          NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }}\n          NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }}\n          NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}\n          NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}\n          NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}\n          NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}\n      - name: Publish tag to community repo\n        if: startsWith(github.ref, 'refs/tags/v')\n        run: |\n          export TAG=$(git describe --tags --abbrev=0)\n          echo \"Current release version is $TAG\"\n          echo \"Triggering community build\"\n          curl \\\n            -H \"Accept: application/vnd.github.v3+json\" \\\n            -H \"Authorization: Bearer ${{ secrets.COMMUNITY_REPO_TRIGGER }}\" \\\n            -X POST https://api.github.com/repos/Netflix/conductor-community/dispatches \\\n            -d '{\"event_type\": \"publish_build\",\"client_payload\": {\"tag\":\"'\"$TAG\"'\"}}'\n      - name: Publish Test Report\n        uses: mikepenz/action-junit-report@v3\n        if: always() # always run even if the previous step fails\n        with:\n          report_paths: '**/build/test-results/test/TEST-*.xml'\n"
  },
  {
    "path": ".github/workflows/release_draft.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    branches:\n      - main\n\npermissions:\n  contents: read\n\njobs:\n  update_release_draft:\n    permissions:\n      contents: write  # for release-drafter/release-drafter to create a github release\n      pull-requests: write  # for release-drafter/release-drafter to add label to PR\n    runs-on: ubuntu-latest\n    steps:\n      - uses: release-drafter/release-drafter@v5\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close stale issues and pull requests\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n\npermissions:\n  contents: read\n\njobs:\n  stale:\n    permissions:\n      issues: write  # for actions/stale to close stale issues\n      pull-requests: write  # for actions/stale to close stale PRs\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v6\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          stale-issue-message: 'This issue is stale, because it has been open for 45 days with no activity. Remove the stale label or comment, or this will be closed in 7 days.'\n          close-issue-message: 'This issue was closed, because it has been stalled for 7 days with no activity.'\n          stale-pr-message: 'This PR is stale, because it has been open for 45 days with no activity. Remove the stale label or comment, or this will be closed in 7 days.'\n          close-pr-message: 'This PR was closed, because it has been stalled for 7 days with no activity.'\n          days-before-issue-stale: 45\n          days-before-issue-close: 7\n          days-before-pr-stale: 45\n          days-before-pr-close: 7\n          exempt-issue-labels: 'type: bug,enhancement,work_in_progress,help_wanted'\n"
  },
  {
    "path": ".github/workflows/update-gradle-wrapper.yml",
    "content": "name: Update Gradle Wrapper\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n  workflow_dispatch:\n\njobs:\n  update-gradle-wrapper:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Set up Zulu JDK 17\n        uses: actions/setup-java@v3\n        with:\n          distribution: 'zulu'\n          java-version: '17'\n      - name: Update Gradle Wrapper\n        uses: gradle-update/update-gradle-wrapper-action@v1\n"
  },
  {
    "path": ".gitignore",
    "content": "# Java Build\n.gradle\n.classpath\ndump.rdb\nout\nbin\ntarget\nbuildscan.log\n/docs/site\n\n# Python\n/polyglot-clients/python/conductor.egg-info\n*.pyc\n\n# OS & IDE\n.DS_Store\n.settings\n.vscode\n.idea\n.project\n*.iml\n\n# JS & UI Related\nnode_modules\n/ui/build\n/ui/public/monaco-editor\n\n# publishing secrets\nsecrets/signing-key\n\n# local builds\nlib/\nbuild/\n*/build/\n\n# asdf version file\n.tool-versions\n\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Conductor has been upgraded to use the SpringBoot framework and requires Java11 or above.  \n#### NOTE: The java clients (conductor-client, conductor-client-spring, conductor-grpc-client) are still compiled using Java8 to ensure backward compatibility and smoother migration.\n\n## Removals/Deprecations\n- Removed support for EmbeddedElasticSearch\n- Removed deprecated constructors in DynoQueueDAO\n- Removed deprecated methods in the Worker interface\n- Removed OAuth Support in HTTP task (Looking for contributions for OAuth/OAuth2.0)\n- Removed deprecated fields and methods in the Workflow object\n- Removed deprecated fields and methods in the Task object\n- Removed deprecated fields and methods in the WorkflowTask object\n\nRemoved unused methods from QueueDAO:\n- List pop(String, int, int, long)\n- List pollMessages(String, int, int, long)\n\nRemoved APIs:\n- GET /tasks/in_progress/{tasktype}\n- GET /tasks/in_progress/{workflowId}/{taskRefName}\n- POST /tasks/{taskId}/ack\n- POST /tasks/queue/requeue\n- DELETE /queue/{taskType}/{taskId}\n\n\n- GET /event/queues\n- GET /event/queues/providers\n\n\n- void restart(String) in workflow client\n- List getPendingTasksByType(String, String, Integer) in task client\n- Task getPendingTaskForWorkflow(String, String) in task client\n- boolean preAck(Task) in Worker\n- int getPollCount() in Worker\n\n## What's changed\nChanges to configurations:\n\n### `azureblob-storage` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| workflow.external.payload.storage.azure_blob.connection_string | conductor.external-payload-storage.azureblob.connectionString | null |\n| workflow.external.payload.storage.azure_blob.container_name | conductor.external-payload-storage.azureblob.containerName | conductor-payloads |\n| workflow.external.payload.storage.azure_blob.endpoint | conductor.external-payload-storage.azureblob.endpoint | null |\n| workflow.external.payload.storage.azure_blob.sas_token | conductor.external-payload-storage.azureblob.sasToken | null |\n| workflow.external.payload.storage.azure_blob.signedurlexpirationseconds | conductor.external-payload-storage.azureblob.signedUrlExpirationDuration | 5s |\n| workflow.external.payload.storage.azure_blob.workflow_input_path | conductor.external-payload-storage.azureblob.workflowInputPath | workflow/input/ | \n| workflow.external.payload.storage.azure_blob.workflow_output_path | conductor.external-payload-storage.azureblob.workflowOutputPath | workflow/output/ |\n| workflow.external.payload.storage.azure_blob.task_input_path | conductor.external-payload-storage.azureblob.taskInputPath | task/input/ |\n| workflow.external.payload.storage.azure_blob.task_output_path | conductor.external-payload-storage.azureblob.taskOutputPath | task/output/ |\n\n### `cassandra-persistence` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| workflow.cassandra.host | conductor.cassandra.hostAddress | 127.0.0.1 |\n| workflow.cassandra.port | conductor.cassandra.port | 9142 |\n| workflow.cassandra.cluster | conductor.cassandra.cluster | \"\" |\n| workflow.cassandra.keyspace | conductor.cassandra.keyspace | conductor |\n| workflow.cassandra.shard.size | conductor.cassandra.shardSize | 100 |\n| workflow.cassandra.replication.strategy | conductor.cassandra.replicationStrategy | SimpleStrategy |\n| workflow.cassandra.replication.factor.key | conductor.cassandra.replicationFactorKey | replication_factor |\n| workflow.cassandra.replication.factor.value | conductor.cassandra.replicationFactorValue | 3 |\n| workflow.cassandra.read.consistency.level | conductor.cassandra.readConsistencyLevel | LOCAL_QUORUM |\n| workflow.cassandra.write.consistency.level | conductor.cassandra.writeConsistencyLevel | LOCAL_QUORUM |\n| conductor.taskdef.cache.refresh.time.seconds | conductor.cassandra.taskDefCacheRefreshInterval | 60s |\n| conductor.eventhandler.cache.refresh.time.seconds | conductor.cassandra.eventHandlerCacheRefreshInterval | 60s |\n| workflow.event.execution.persistence.ttl.seconds | conductor.cassandra.eventExecutionPersistenceTTL | 0s |\n\n### `contribs` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| workflow.archival.ttl.seconds | conductor.workflow-status-listener.archival.ttlDuration | 0s |\n| workflow.archival.delay.queue.worker.thread.count | conductor.workflow-status-listener.archival.delayQueueWorkerThreadCount | 5 |\n| workflow.archival.delay.seconds | conductor.workflow-status-listener.archival.delaySeconds | 60 |\n|  |  |\n| workflowstatuslistener.publisher.success.queue | conductor.workflow-status-listener.queue-publisher.successQueue | _callbackSuccessQueue |\n| workflowstatuslistener.publisher.failure.queue | conductor.workflow-status-listener.queue-publisher.failureQueue | _callbackFailureQueue |\n|  |  |  |\n| com.netflix.conductor.contribs.metrics.LoggingMetricsModule.reportPeriodSeconds | conductor.metrics-logger.reportInterval | 30s |\n|  |  |  |\n| workflow.event.queues.amqp.batchSize | conductor.event-queues.amqp.batchSize | 1 |\n| workflow.event.queues.amqp.pollTimeInMs | conductor.event-queues.amqp.pollTimeDuration | 100ms |\n| workflow.event.queues.amqp.hosts | conductor.event-queues.amqp.hosts | localhost |\n| workflow.event.queues.amqp.username | conductor.event-queues.amqp.username | guest |\n| workflow.event.queues.amqp.password | conductor.event-queues.amqp.password | guest |\n| workflow.event.queues.amqp.virtualHost | conductor.event-queues.amqp.virtualHost | / |\n| workflow.event.queues.amqp.port | conductor.event-queues.amqp.port.port | 5672 |\n| workflow.event.queues.amqp.connectionTimeout | conductor.event-queues.amqp.connectionTimeout | 60000ms |\n| workflow.event.queues.amqp.useNio | conductor.event-queues.amqp.useNio | false |\n| workflow.event.queues.amqp.durable | conductor.event-queues.amqp.durable | true |\n| workflow.event.queues.amqp.exclusive | conductor.event-queues.amqp.exclusive | false |\n| workflow.event.queues.amqp.autoDelete | conductor.event-queues.amqp.autoDelete | false |\n| workflow.event.queues.amqp.contentType | conductor.event-queues.amqp.contentType | application/json |\n| workflow.event.queues.amqp.contentEncoding | conductor.event-queues.amqp.contentEncoding | UTF-8 |\n| workflow.event.queues.amqp.amqp_exchange | conductor.event-queues.amqp.exchangeType | topic |\n| workflow.event.queues.amqp.deliveryMode | conductor.event-queues.amqp.deliveryMode | 2 |\n| workflow.listener.queue.useExchange | conductor.event-queues.amqp.useExchange | true |\n| workflow.listener.queue.prefix | conductor.event-queues.amqp.listenerQueuePrefix | \"\" |\n|  |  |  |\n| io.nats.streaming.clusterId | conductor.event-queues.nats-stream.clusterId | test-cluster |\n| io.nats.streaming.durableName | conductor.event-queues.nats-stream.durableName | null |\n| io.nats.streaming.url | conductor.event-queues.nats-stream.url | nats://localhost:4222 |\n|  |  |  |\n| workflow.event.queues.sqs.batchSize | conductor.event-queues.sqs.batchSize | 1 |\n| workflow.event.queues.sqs.pollTimeInMS | conductor.event-queues.sqs.pollTimeDuration | 100ms |\n| workflow.event.queues.sqs.visibilityTimeoutInSeconds | conductor.event-queues.sqs.visibilityTimeout | 60s |\n| workflow.listener.queue.prefix | conductor.event-queues.sqs.listenerQueuePrefix | \"\" |\n| workflow.listener.queue.authorizedAccounts | conductor.event-queues.sqs.authorizedAccounts | \"\" |\n|  |  |  |\n| workflow.external.payload.storage.s3.bucket | conductor.external-payload-storage.s3.bucketName | conductor_payloads |\n| workflow.external.payload.storage.s3.signedurlexpirationseconds | conductor.external-payload-storage.s3.signedUrlExpirationDuration | 5s |\n| workflow.external.payload.storage.s3.region | conductor.external-payload-storage.s3.region | us-east-1 |\n|  |  |  |\n| http.task.read.timeout | conductor.tasks.http.readTimeout | 150ms |\n| http.task.connect.timeout | conductor.tasks.http.connectTimeout | 100ms |\n|  |  |  |\n| kafka.publish.request.timeout.ms | conductor.tasks.kafka-publish.requestTimeout | 100ms |\n| kafka.publish.max.block.ms | conductor.tasks.kafka-publish.maxBlock | 500ms |\n| kafka.publish.producer.cache.size | conductor.tasks.kafka-publish.cacheSize | 10 |\n| kafka.publish.producer.cache.time.ms | conductor.tasks.kafka-publish.cacheTime | 120000ms |\n\n### `core` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| environment | _removed_ |  |\n| STACK | conductor.app.stack | test |\n| APP_ID | conductor.app.appId | conductor |\n| workflow.executor.service.max.threads | conductor.app.executorServiceMaxThreadCount | 50 |\n| decider.sweep.frequency.seconds | conductor.app.sweepFrequency | 30s |\n| workflow.sweeper.thread.count | conductor.app.sweeperThreadCount | 5 |\n| - | conductor.app.sweeperWorkflowPollTimeout | 2000ms |\n| workflow.event.processor.thread.count | conductor.app.eventProcessorThreadCount | 2 |\n| workflow.event.message.indexing.enabled | conductor.app.eventMessageIndexingEnabled | true |\n| workflow.event.execution.indexing.enabled | conductor.app.eventExecutionIndexingEnabled | true |\n| workflow.decider.locking.enabled | conductor.app.workflowExecutionLockEnabled | false |\n| workflow.locking.lease.time.ms | conductor.app.lockLeaseTime | 60000ms |\n| workflow.locking.time.to.try.ms | conductor.app.lockTimeToTry | 500ms |\n| tasks.active.worker.lastpoll | conductor.app.activeWorkerLastPollTimeout | 10s |\n| task.queue.message.postponeSeconds | conductor.app.taskExecutionPostponeDuration | 60s |\n| workflow.taskExecLog.indexing.enabled | conductor.app.taskExecLogIndexingEnabled | true |\n| async.indexing.enabled | conductor.app.asyncIndexingEnabled | false |\n| workflow.system.task.worker.thread.count | conductor.app.systemTaskWorkerThreadCount | # available processors * 2 |\n| workflow.system.task.worker.callback.seconds | conductor.app.systemTaskWorkerCallbackDuration | 30s |\n| workflow.system.task.worker.poll.interval | conductor.app.systemTaskWorkerPollInterval | 50s |\n| workflow.system.task.worker.executionNameSpace | conductor.app.systemTaskWorkerExecutionNamespace | \"\" |\n| workflow.isolated.system.task.worker.thread.count | conductor.app.isolatedSystemTaskWorkerThreadCount | 1 |\n| workflow.system.task.queue.pollCount | conductor.app.systemTaskMaxPollCount | 1 |\n| async.update.short.workflow.duration.seconds | conductor.app.asyncUpdateShortRunningWorkflowDuration | 30s |\n| async.update.delay.seconds | conductor.app.asyncUpdateDelay | 60s |\n| summary.input.output.json.serialization.enabled | conductor.app.summary-input-output-json-serialization.enabled | false |\n| workflow.owner.email.mandatory | conductor.app.ownerEmailMandatory | true |\n| workflow.repairservice.enabled | conductor.app.workflowRepairServiceEnabled | false |\n| workflow.event.queue.scheduler.poll.thread.count | conductor.app.eventSchedulerPollThreadCount | # CPU cores |\n| workflow.dyno.queues.pollingInterval | conductor.app.eventQueuePollInterval | 100ms |\n| workflow.dyno.queues.pollCount | conductor.app.eventQueuePollCount | 10 |\n| workflow.dyno.queues.longPollTimeout | conductor.app.eventQueueLongPollTimeout | 1000ms |\n| conductor.workflow.input.payload.threshold.kb | conductor.app.workflowInputPayloadSizeThreshold | 5120KB |\n| conductor.max.workflow.input.payload.threshold.kb | conductor.app.maxWorkflowInputPayloadSizeThreshold | 10240KB |\n| conductor.workflow.output.payload.threshold.kb | conductor.app.workflowOutputPayloadSizeThreshold | 5120KB |\n| conductor.max.workflow.output.payload.threshold.kb | conductor.app.maxWorkflowOutputPayloadSizeThreshold | 10240KB |\n| conductor.task.input.payload.threshold.kb | conductor.app.taskInputPayloadSizeThreshold | 3072KB |\n| conductor.max.task.input.payload.threshold.kb | conductor.app.maxTaskInputPayloadSizeThreshold | 10240KB |\n| conductor.task.output.payload.threshold.kb | conductor.app.taskOutputPayloadSizeThreshold | 3072KB |\n| conductor.max.task.output.payload.threshold.kb | conductor.app.maxTaskOutputPayloadSizeThreshold | 10240KB |\n| conductor.max.workflow.variables.payload.threshold.kb | conductor.app.maxWorkflowVariablesPayloadSizeThreshold | 256KB |\n|  |  |  |\n| workflow.isolated.system.task.enable | conductor.app.isolatedSystemTaskEnabled | false |\n| workflow.isolated.system.task.poll.time.secs | conductor.app.isolatedSystemTaskQueuePollInterval | 10s |\n|  |  |  |\n| workflow.task.pending.time.threshold.minutes | conductor.app.taskPendingTimeThreshold |  60m |\n|  |  |  |\n| workflow.monitor.metadata.refresh.counter | conductor.workflow-monitor.metadataRefreshInterval | 10 |\n| workflow.monitor.stats.freq.seconds | conductor.workflow-monitor.statsFrequency | 60s |\n\n### `es6-persistence` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| workflow.elasticsearch.version | conductor.elasticsearch.version | 6 |\n| workflow.elasticsearch.url | conductor.elasticsearch.url | localhost:9300 |\n| workflow.elasticsearch.index.name | conductor.elasticsearch.indexPrefix | conductor |\n| workflow.elasticsearch.tasklog.index.name | _removed_ |  |\n| workflow.elasticsearch.cluster.health.color | conductor.elasticsearch.clusterHealthColor | green |\n| workflow.elasticsearch.archive.search.batchSize | _removed_ |  |\n| workflow.elasticsearch.index.batchSize | conductor.elasticsearch.indexBatchSize | 1 |\n| workflow.elasticsearch.async.dao.worker.queue.size | conductor.elasticsearch.asyncWorkerQueueSize | 100 |\n| workflow.elasticsearch.async.dao.max.pool.size | conductor.elasticsearch.asyncMaxPoolSize | 12 |\n| workflow.elasticsearch.async.buffer.flush.timeout.seconds | conductor.elasticsearch.asyncBufferFlushTimeout | 10s |\n| workflow.elasticsearch.index.shard.count | conductor.elasticsearch.indexShardCount | 5 |\n| workflow.elasticsearch.index.replicas.count | conductor.elasticsearch.indexReplicasCount | 1 |\n| tasklog.elasticsearch.query.size | conductor.elasticsearch.taskLogResultLimit | 10 |\n| workflow.elasticsearch.rest.client.connectionRequestTimeout.milliseconds | conductor.elasticsearch.restClientConnectionRequestTimeout | -1 |\n| workflow.elasticsearch.auto.index.management.enabled | conductor.elasticsearch.autoIndexManagementEnabled | true |\n| workflow.elasticsearch.document.type.override | conductor.elasticsearch.documentTypeOverride | \"\" |\n\n### `es7-persistence` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| workflow.elasticsearch.version | conductor.elasticsearch.version | 7 |\n| workflow.elasticsearch.url | conductor.elasticsearch.url | localhost:9300 |\n| workflow.elasticsearch.index.name | conductor.elasticsearch.indexPrefix | conductor |\n| workflow.elasticsearch.tasklog.index.name | _removed_ |  |\n| workflow.elasticsearch.cluster.health.color | conductor.elasticsearch.clusterHealthColor | green |\n| workflow.elasticsearch.archive.search.batchSize | _removed_ |  |\n| workflow.elasticsearch.index.batchSize | conductor.elasticsearch.indexBatchSize | 1 |\n| workflow.elasticsearch.async.dao.worker.queue.size | conductor.elasticsearch.asyncWorkerQueueSize | 100 |\n| workflow.elasticsearch.async.dao.max.pool.size | conductor.elasticsearch.asyncMaxPoolSize | 12 |\n| workflow.elasticsearch.async.buffer.flush.timeout.seconds | conductor.elasticsearch.asyncBufferFlushTimeout | 10s |\n| workflow.elasticsearch.index.shard.count | conductor.elasticsearch.indexShardCount | 5 |\n| workflow.elasticsearch.index.replicas.count | conductor.elasticsearch.indexReplicasCount | 1 |\n| tasklog.elasticsearch.query.size | conductor.elasticsearch.taskLogResultLimit | 10 |\n| workflow.elasticsearch.rest.client.connectionRequestTimeout.milliseconds | conductor.elasticsearch.restClientConnectionRequestTimeout | -1 |\n| workflow.elasticsearch.auto.index.management.enabled | conductor.elasticsearch.autoIndexManagementEnabled | true |\n| workflow.elasticsearch.document.type.override | conductor.elasticsearch.documentTypeOverride | \"\" |\n| workflow.elasticsearch.basic.auth.username | conductor.elasticsearch.username | \"\" |\n| workflow.elasticsearch.basic.auth.password | conductor.elasticsearch.password | \"\" |\n\n### `grpc-server` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| conductor.grpc.server.port | conductor.grpc-server.port | 8090 |\n| conductor.grpc.server.reflectionEnabled | conductor.grpc-server.reflectionEnabled | true |\n\n### `mysql-persistence` module (v3.0.0 - v3.0.5):\n\n| Old | New | Default |\n| --- | --- | --- |\n| jdbc.url | conductor.mysql.jdbcUrl | jdbc:mysql://localhost:3306/conductor |\n| jdbc.username | conductor.mysql.jdbcUsername | conductor |\n| jdbc.password | conductor.mysql.jdbcPassword | password |\n| flyway.enabled | conductor.mysql.flywayEnabled | true |\n| flyway.table | conductor.mysql.flywayTable | null |\n| conductor.mysql.connection.pool.size.max | conductor.mysql.connectionPoolMaxSize | -1 |\n| conductor.mysql.connection.pool.idle.min | conductor.mysql.connectionPoolMinIdle | -1 |\n| conductor.mysql.connection.lifetime.max | conductor.mysql.connectionMaxLifetime | 30m |\n| conductor.mysql.connection.idle.timeout | conductor.mysql.connectionIdleTimeout | 10m |\n| conductor.mysql.connection.timeout | conductor.mysql.connectionTimeout | 30s | \n| conductor.mysql.transaction.isolation.level | conductor.mysql.transactionIsolationLevel | \"\" |\n| conductor.mysql.autocommit | conductor.mysql.autoCommit | false |\n| conductor.taskdef.cache.refresh.time.seconds | conductor.mysql.taskDefCacheRefreshInterval | 60s |\n\n### `mysql-persistence` module (v3.0.5+):\n\n| Old | New |\n| --- | --- |\n| jdbc.url | spring.datasource.url |\n| jdbc.username | spring.datasource.username |\n| jdbc.password | spring.datasource.password |\n| flyway.enabled | spring.flyway.enabled |\n| flyway.table | spring.flyway.table |\n| conductor.mysql.connection.pool.size.max | spring.datasource.hikari.maximum-pool-size |\n| conductor.mysql.connection.pool.idle.min | spring.datasource.hikari.minimum-idle |\n| conductor.mysql.connection.lifetime.max | spring.datasource.hikari.max-lifetime |\n| conductor.mysql.connection.idle.timeout | spring.datasource.hikari.idle-timeout |\n| conductor.mysql.connection.timeout | spring.datasource.hikari.connection-timeout |\n| conductor.mysql.transaction.isolation.level | spring.datasource.hikari.transaction-isolation |\n| conductor.mysql.autocommit | spring.datasource.hikari.auto-commit |\n| conductor.taskdef.cache.refresh.time.seconds | conductor.mysql.taskDefCacheRefreshInterval |\n\n* for more properties and default values: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#application-properties.data.spring.datasource.hikari\n\n### `postgres-persistence` module (v3.0.0 - v3.0.5):\n\n| Old | New | Default |\n| --- | --- | --- |\n| jdbc.url | conductor.postgres.jdbcUrl | jdbc:postgresql://localhost:5432/conductor |\n| jdbc.username | conductor.postgres.jdbcUsername | conductor |\n| jdbc.password | conductor.postgres.jdbcPassword | password |\n| flyway.enabled | conductor.postgres.flywayEnabled | true |\n| flyway.table | conductor.postgres.flywayTable | null |\n| conductor.postgres.connection.pool.size.max | conductor.postgres.connectionPoolMaxSize | -1 |\n| conductor.postgres.connection.pool.idle.min | conductor.postgres.connectionPoolMinIdle | -1 |\n| conductor.postgres.connection.lifetime.max | conductor.postgres.connectionMaxLifetime | 30m |\n| conductor.postgres.connection.idle.timeout | conductor.postgres.connectionIdleTimeout | 10m |\n| conductor.postgres.connection.timeout | conductor.postgres.connectionTimeout | 30s |\n| conductor.postgres.transaction.isolation.level | conductor.postgres.transactionIsolationLevel | \"\" |\n| conductor.postgres.autocommit | conductor.postgres.autoCommit | false |\n| conductor.taskdef.cache.refresh.time.seconds | conductor.postgres.taskDefCacheRefreshInterval | 60s |\n\n### `postgres-persistence` module (v3.0.5+):\n\n| Old | New |\n| --- | --- |\n| jdbc.url | spring.datasource.url | \n| jdbc.username | spring.datasource.username |\n| jdbc.password | spring.datasource.password |\n| flyway.enabled | spring.flyway.enabled |\n| flyway.table | spring.flyway.table |\n| conductor.postgres.connection.pool.size.max | spring.datasource.hikari.maximum-pool-size |\n| conductor.postgres.connection.pool.idle.min | spring.datasource.hikari.minimum-idle |\n| conductor.postgres.connection.lifetime.max | spring.datasource.hikari.max-lifetime |\n| conductor.postgres.connection.idle.timeout | spring.datasource.hikari.idle-timeout |\n| conductor.postgres.connection.timeout | spring.datasource.hikari.connection-timeout |\n| conductor.postgres.transaction.isolation.level | spring.datasource.hikari.transaction-isolation |\n| conductor.postgres.autocommit | spring.datasource.hikari.auto-commit |\n| conductor.taskdef.cache.refresh.time.seconds | conductor.postgres.taskDefCacheRefreshInterval |\n\n* for more properties and default values: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#application-properties.data.spring.datasource.hikari\n\n### `redis-lock` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| workflow.redis.locking.server.type | conductor.redis-lock.serverType | single |\n| workflow.redis.locking.server.address | conductor.redis-lock.serverAddress | redis://127.0.0.1:6379 |\n| workflow.redis.locking.server.password | conductor.redis-lock.serverPassword | null |\n| workflow.redis.locking.server.master.name | conductor.redis-lock.serverMasterName | master |\n| workflow.decider.locking.namespace | conductor.redis-lock.namespace | \"\" |\n| workflow.decider.locking.exceptions.ignore | conductor.redis-lock.ignoreLockingExceptions | false |\n\n### `redis-persistence` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| EC2_REGION | conductor.redis.dataCenterRegion | us-east-1 |\n| EC2_AVAILABILITY_ZONE | conductor.redis.availabilityZone | us-east-1c |\n| workflow.dynomite.cluster | _removed_ |\n| workflow.dynomite.cluster.name | conductor.redis.clusterName | \"\" |\n| workflow.dynomite.cluster.hosts | conductor.redis.hosts | null |\n| workflow.namespace.prefix | conductor.redis.workflowNamespacePrefix | null |\n| workflow.namespace.queue.prefix | conductor.redis.queueNamespacePrefix | null |\n| workflow.dyno.keyspace.domain | conductor.redis.keyspaceDomain | null |\n| workflow.dynomite.connection.maxConnsPerHost | conductor.redis.maxConnectionsPerHost | 10 |\n| workflow.dynomite.connection.max.retry.attempt | conductor.redis.maxRetryAttempts | 0 |\n| workflow.dynomite.connection.max.timeout.exhausted.ms | conductor.redis.maxTimeoutWhenExhausted | 800ms |\n| queues.dynomite.nonQuorum.port | conductor.redis.queuesNonQuorumPort | 22122 |\n| workflow.dyno.queue.sharding.strategy | conductor.redis.queueShardingStrategy | roundRobin |\n| conductor.taskdef.cache.refresh.time.seconds | conductor.redis.taskDefCacheRefreshInterval | 60s |\n| workflow.event.execution.persistence.ttl.seconds | conductor.redis.eventExecutionPersistenceTTL | 60s |\n\n### `zookeeper-lock` module:\n\n| Old | New | Default |\n| --- | --- | --- |\n| workflow.zookeeper.lock.connection | conductor.zookeeper-lock.connectionString | localhost:2181 |\n| workflow.zookeeper.lock.sessionTimeoutMs | conductor.zookeeper-lock.sessionTimeout | 60000ms |\n| workflow.zookeeper.lock.connectionTimeoutMs | conductor.zookeeper-lock.connectionTimeout | 15000ms |\n| workflow.decider.locking.namespace | conductor.zookeeper-lock.namespace | \"\" |\n\n### Component configuration:\n\n| Old | New | Default |\n| --- | --- | --- |\n| db | conductor.db.type | \"\" | \n| workflow.indexing.enabled | conductor.indexing.enabled | true |\n| conductor.disable.async.workers | conductor.system-task-workers.enabled | true |\n| decider.sweep.disable | conductor.workflow-reconciler.enabled | true |\n| conductor.grpc.server.enabled | conductor.grpc-server.enabled | false |\n| workflow.external.payload.storage | conductor.external-payload-storage.type | dummy |\n| workflow.default.event.processor.enabled | conductor.default-event-processor.enabled | true |\n| workflow.events.default.queue.type | conductor.default-event-queue.type | sqs |\n| workflow.status.listener.type | conductor.workflow-status-listener.type | stub |\n| - | conductor.task-status-listener.type | stub |\n| workflow.decider.locking.server | conductor.workflow-execution-lock.type | noop_lock |\n|  |  |  |\n| workflow.default.event.queue.enabled | conductor.event-queues.default.enabled | true |\n| workflow.sqs.event.queue.enabled | conductor.event-queues.sqs.enabled | false |\n| workflow.amqp.event.queue.enabled | conductor.event-queues.amqp.enabled | false |\n| workflow.nats.event.queue.enabled | conductor.event-queues.nats.enabled | false |\n| workflow.nats_stream.event.queue.enabled | conductor.event-queues.nats-stream.enabled | false |\n|  |  |  |\n| - | conductor.metrics-logger.enabled | false |\n| - | conductor.metrics-prometheus.enabled | false |\n| - | conductor.metrics-datadog.enable | false |\n| - | conductor.metrics-datadog.api-key | |\n\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "[Code of Conduct](docs/docs/resources/code-of-conduct.md)"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "[Contributing](docs/docs/resources/contributing.md)"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} Netflix, Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "OSSMETADATA",
    "content": "osslifecycle=active\n"
  },
  {
    "path": "README.md",
    "content": "![Conductor](docs/docs/img/logo.png)\n\n## Announcement\n\n> Effective **December 13, 2023**, Netflix will discontinue maintenance of Conductor OSS on GitHub. This strategic decision, while difficult, is essential for realigning our resources to better serve our business objectives with our internal Conductor fork.\n> \n> We are *deeply grateful* for your support and contributions over the years. While Netflix will no longer be maintaining this repo, members of the Conductor community have been active in promoting alternative forks of this project, we’ll leave the code as is and trust that the health of the community will remain strong and continue to develop moving forward.\n\n\n# Conductor\n[![NetflixOSS Lifecycle](https://img.shields.io/osslifecycle/Netflix/conductor.svg)]()\n[![Github release](https://img.shields.io/github/v/release/Netflix/conductor.svg)](https://GitHub.com/Netflix/conductor/releases)\n[![License](https://img.shields.io/github/license/Netflix/conductor.svg)](http://www.apache.org/licenses/LICENSE-2.0)\n\n[![GitHub stars](https://img.shields.io/github/stars/Netflix/conductor.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/Netflix/conductor/stargazers/)\n[![GitHub forks](https://img.shields.io/github/forks/Netflix/conductor.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/Netflix/conductor/network/)\n\nConductor is a platform created by Netflix to orchestrate workflows that span across microservices.\n\n## Releases\nThe final release is [![Github release](https://img.shields.io/github/v/release/Netflix/conductor.svg)](https://GitHub.com/Netflix/conductor/releases)\n\n## Workflow Creation in Code\nConductor supports creating workflows using JSON and Code.  \nSDK support for creating workflows using code is available in multiple languages and can be found at https://github.com/conductor-sdk\n\n## Community Contributions\nThe modules contributed by the community are housed at [conductor-community](https://github.com/Netflix/conductor-community). Compatible versions of the community modules are released simultaneously with releases of the main modules.\n\n[Discussion Forum](https://github.com/Netflix/conductor/discussions): Please use the forum for questions and discussing ideas and join the community.\n\n[List of Conductor community projects](/docs/docs/resources/related.md): Backup tool, Cron like workflow starter, Docker containers and more.\n\n## Getting Started - Building & Running Conductor\n###  Using Docker:\nThe easiest way to get started is with Docker containers. Please follow the instructions [here](https://conductor.netflix.com/devguide/running/docker.html). \n\n###  From Source:\nConductor Server is a [Spring Boot](https://spring.io/projects/spring-boot) project and follows all applicable conventions. See instructions [here](https://conductor.netflix.com/devguide/running/source.html).\n\n## Published Artifacts\nBinaries are available from the [Maven Central Repository](https://search.maven.org/search?q=g:com.netflix.conductor).\n\n| Artifact                        | Description                                                                                     |\n|---------------------------------|-------------------------------------------------------------------------------------------------|\n| conductor-common                | Common models used by various conductor modules                                                 |\n| conductor-core                  | Core Conductor module                                                                           |\n| conductor-redis-persistence     | Persistence and queue using Redis/Dynomite                                                      |\n| conductor-cassandra-persistence | Persistence using Cassandra                                                                     |\n| conductor-es6-persistence       | Indexing using Elasticsearch 6.X                                                                |\n| conductor-rest                  | Spring MVC resources for the core services                                                      |\n| conductor-ui                    | node.js based UI for Conductor                                                                  |\n| conductor-client                | Java client for Conductor that includes helpers for running worker tasks                        |\n| conductor-client-spring         | Client starter kit for Spring                                                                   |\n| conductor-java-sdk              | SDK for writing workflows in code                                                               |\n| conductor-server                | Spring Boot Web Application                                                                     |\n| conductor-redis-lock            | Workflow execution lock implementation using Redis                                              |\n| conductor-awss3-storage         | External payload storage implementation using AWS S3                                            |\n| conductor-awssqs-event-queue    | Event queue implementation using AWS SQS                                                        |\n| conductor-http-task             | Workflow system task implementation to send make requests                                       |\n| conductor-json-jq-task          | Workflow system task implementation to evaluate JSON using [jq](https://stedolan.github.io/jq/) |\n| conductor-grpc                  | Protobuf models used by the server and client                                                   |\n| conductor-grpc-client           | gRPC client to interact with the gRPC server                                                    |\n| conductor-grpc-server           | gRPC server Application                                                                         |\n| conductor-test-harness          | Integration and regression tests                                                                |\n\n## Database Requirements\n\n* The default persistence used is Redis\n* The indexing backend is [Elasticsearch](https://www.elastic.co/) (6.x)\n\n## Other Requirements\n* JDK 17+\n* UI requires Node 14 to build. Earlier Node versions may work but is untested.\n\n## Get Support\nThere are several ways to get in touch with us:\n* [Slack Community](https://join.slack.com/t/orkes-conductor/shared_invite/zt-xyxqyseb-YZ3hwwAgHJH97bsrYRnSZg)\n* [GitHub Discussion Forum](https://github.com/Netflix/conductor/discussions)\n\n## Contributions\nWhether it is a small documentation correction, bug fix or a new feature, contributions are highly appreciated. We just ask you to follow standard OSS guidelines. The [Discussion Forum](https://github.com/Netflix/conductor/discussions) is a good place to ask questions, discuss new features and explore ideas. Please check with us before spending too much time, only to find out later that someone else is already working on a similar feature.\n\n`main` branch is the current working branch. Please send your PR's to `main` branch, making sure that it builds on your local system successfully. Also, please make sure all the conflicts are resolved.\n\n## License\nCopyright 2022 Netflix, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "RELATED.md",
    "content": "[Related Projects](docs/docs/resources/related.md)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 3.x.x   | :white_check_mark: |\n| 2.x.x   | :x:                |\n| 1.x.x   | :x:                |\n\n## Reporting a Vulnerability\n\nPlease email conductor@netflix.com to report vulnerabilities.\n"
  },
  {
    "path": "USERS.md",
    "content": "\n## Who uses Conductor?\n\nWe would like to keep track of whose using Conductor. Please send a pull request with your company name and Github handle.\n\n* [Netflix](https://www.netflix.com/) [[@aravindanr](https://github.com/aravindanr)]\n* [Florida Blue](http://bcbsfl.com/) [[@rickfish](https://github.com/rickfish)]\n* [UWM](https://www.uwm.com/) [[@zergrushjoe](https://github.com/ZergRushJoe)]\n* [Deutsche Telekom Digital Labs](https://dtdl.in) [[@jas34](https://github.com/jas34)] [[@deoramanas](https://github.com/deoramanas)]\n* [VMware](https://www.vmware.com/) [[@taojwmware](https://github.com/taojwmware)] [[@venkag](https://github.com/venkag)]\n* [JP Morgan Chase](https://www.chase.com/) [[@maheshyaddanapudi](https://github.com/maheshyaddanapudi)]\n* [Orkes](https://orkes.io/) [[@CherishSantoshi](https://github.com/CherishSantoshi)]\n* [313X](https://313x.com.br) [[@dalmoveras](https://github.com/dalmoveras)]\n* [Supercharge](https://supercharge.io) [[@team-supercharge](https://github.com/team-supercharge)]\n* [GE Healthcare](https://www.gehealthcare.com/) [[@flavioschuindt](https://github.com/flavioschuindt)]\n* [ReliaQuest](https://www.reliaquest.com/) [[@rq-dbrady](https://github.com/rq-dbrady)] [[@alexmay48](https://github.com/alexmay48)]\n* [Clari](https://www.clari.com/) [[@TeamJOF](https://github.com/clari)]\n* [Atlassian](https://www.atlassian.com/) [[@LuisLainez](https://github.com/LuisLainez)] [[@aradu](https://github.com/aradu-atlassian)]\n"
  },
  {
    "path": "annotations/README.md",
    "content": "# Annotations \n\n- `protogen` Annotations\n  - Original Author: Vicent Martí - https://github.com/vmg\n  - Original Repo: https://github.com/vmg/protogen\n\n\n"
  },
  {
    "path": "annotations/build.gradle",
    "content": "\n\ndependencies {\n\n}"
  },
  {
    "path": "annotations/src/main/java/com/netflix/conductor/annotations/protogen/ProtoEnum.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotations.protogen;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * ProtoEnum annotates an enum type that will be exposed via the GRPC API as a native Protocol\n * Buffers enum.\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface ProtoEnum {}\n"
  },
  {
    "path": "annotations/src/main/java/com/netflix/conductor/annotations/protogen/ProtoField.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotations.protogen;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * ProtoField annotates a field inside an struct with metadata on how to expose it on its\n * corresponding Protocol Buffers struct. For a field to be exposed in a ProtoBuf struct, the\n * containing struct must also be annotated with a {@link ProtoMessage} or {@link ProtoEnum} tag.\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.FIELD)\npublic @interface ProtoField {\n    /**\n     * Mandatory. Sets the Protocol Buffer ID for this specific field. Once a field has been\n     * annotated with a given ID, the ID can never change to a different value or the resulting\n     * Protocol Buffer struct will not be backwards compatible.\n     *\n     * @return the numeric ID for the field\n     */\n    int id();\n}\n"
  },
  {
    "path": "annotations/src/main/java/com/netflix/conductor/annotations/protogen/ProtoMessage.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotations.protogen;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * ProtoMessage annotates a given Java class so it becomes exposed via the GRPC API as a native\n * Protocol Buffers struct. The annotated class must be a POJO.\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface ProtoMessage {\n    /**\n     * Sets whether the generated mapping code will contain a helper to translate the POJO for this\n     * class into the equivalent ProtoBuf object.\n     *\n     * @return whether this class will generate a mapper to ProtoBuf objects\n     */\n    boolean toProto() default true;\n\n    /**\n     * Sets whether the generated mapping code will contain a helper to translate the ProtoBuf\n     * object for this class into the equivalent POJO.\n     *\n     * @return whether this class will generate a mapper from ProtoBuf objects\n     */\n    boolean fromProto() default true;\n\n    /**\n     * Sets whether this is a wrapper class that will be used to encapsulate complex nested type\n     * interfaces. Wrapper classes are not directly exposed by the ProtoBuf API and must be mapped\n     * manually.\n     *\n     * @return whether this is a wrapper class\n     */\n    boolean wrapper() default false;\n}\n"
  },
  {
    "path": "annotations-processor/README.md",
    "content": "[Annotations Processor](docs/docs/reference-docs/annotations-processor.md)"
  },
  {
    "path": "annotations-processor/build.gradle",
    "content": "\nsourceSets {\n    example\n}\n\ndependencies {\n    implementation project(':conductor-annotations')\n    api 'com.google.guava:guava:32.1.2-jre'\n    api 'com.squareup:javapoet:1.13.+'\n    api 'com.github.jknack:handlebars:4.3.+'\n    api 'com.google.protobuf:protobuf-java:3.21.12'\n    api 'javax.annotation:javax.annotation-api:1.3.2'\n    api gradleApi()\n\n    exampleImplementation sourceSets.main.output\n    exampleImplementation project(':conductor-annotations')\n}\n\ntask exampleJar(type: Jar) {\n    archiveFileName = 'example.jar'\n    from sourceSets.example.output.classesDirs\n}\n\ntestClasses.finalizedBy(exampleJar)\n"
  },
  {
    "path": "annotations-processor/src/example/java/com/example/Example.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.example;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\n@ProtoMessage\npublic class Example {\n    @ProtoField(id = 1)\n    public String name;\n\n    @ProtoField(id = 2)\n    public Long count;\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/AbstractMessage.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;\nimport com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;\n\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeSpec;\n\npublic abstract class AbstractMessage {\n    protected Class<?> clazz;\n    protected MessageType type;\n    protected List<Field> fields = new ArrayList<Field>();\n    protected List<AbstractMessage> nested = new ArrayList<>();\n\n    public AbstractMessage(Class<?> cls, MessageType parentType) {\n        assert cls.isAnnotationPresent(ProtoMessage.class)\n                || cls.isAnnotationPresent(ProtoEnum.class);\n\n        this.clazz = cls;\n        this.type = TypeMapper.INSTANCE.declare(cls, parentType);\n\n        for (Class<?> nested : clazz.getDeclaredClasses()) {\n            if (nested.isEnum()) addNestedEnum(nested);\n            else addNestedClass(nested);\n        }\n    }\n\n    private void addNestedEnum(Class<?> cls) {\n        ProtoEnum ann = (ProtoEnum) cls.getAnnotation(ProtoEnum.class);\n        if (ann != null) {\n            nested.add(new Enum(cls, this.type));\n        }\n    }\n\n    private void addNestedClass(Class<?> cls) {\n        ProtoMessage ann = (ProtoMessage) cls.getAnnotation(ProtoMessage.class);\n        if (ann != null) {\n            nested.add(new Message(cls, this.type));\n        }\n    }\n\n    public abstract String getProtoClass();\n\n    protected abstract void javaMapToProto(TypeSpec.Builder builder);\n\n    protected abstract void javaMapFromProto(TypeSpec.Builder builder);\n\n    public void generateJavaMapper(TypeSpec.Builder builder) {\n        javaMapToProto(builder);\n        javaMapFromProto(builder);\n\n        for (AbstractMessage abstractMessage : this.nested) {\n            abstractMessage.generateJavaMapper(builder);\n        }\n    }\n\n    public void generateAbstractMethods(Set<MethodSpec> specs) {\n        for (Field field : fields) {\n            field.generateAbstractMethods(specs);\n        }\n\n        for (AbstractMessage elem : nested) {\n            elem.generateAbstractMethods(specs);\n        }\n    }\n\n    public void findDependencies(Set<String> dependencies) {\n        for (Field field : fields) {\n            field.getDependencies(dependencies);\n        }\n\n        for (AbstractMessage elem : nested) {\n            elem.findDependencies(dependencies);\n        }\n    }\n\n    public List<AbstractMessage> getNested() {\n        return nested;\n    }\n\n    public List<Field> getFields() {\n        return fields;\n    }\n\n    public String getName() {\n        return clazz.getSimpleName();\n    }\n\n    public abstract static class Field {\n        protected int protoIndex;\n        protected java.lang.reflect.Field field;\n\n        protected Field(int index, java.lang.reflect.Field field) {\n            this.protoIndex = index;\n            this.field = field;\n        }\n\n        public abstract String getProtoTypeDeclaration();\n\n        public int getProtoIndex() {\n            return protoIndex;\n        }\n\n        public String getName() {\n            return field.getName();\n        }\n\n        public String getProtoName() {\n            return field.getName().toUpperCase();\n        }\n\n        public void getDependencies(Set<String> deps) {}\n\n        public void generateAbstractMethods(Set<MethodSpec> specs) {}\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/Enum.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen;\n\nimport javax.lang.model.element.Modifier;\n\nimport com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;\n\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeName;\nimport com.squareup.javapoet.TypeSpec;\n\npublic class Enum extends AbstractMessage {\n    public enum MapType {\n        FROM_PROTO(\"fromProto\"),\n        TO_PROTO(\"toProto\");\n\n        private final String methodName;\n\n        MapType(String m) {\n            methodName = m;\n        }\n\n        public String getMethodName() {\n            return methodName;\n        }\n    }\n\n    public Enum(Class cls, MessageType parent) {\n        super(cls, parent);\n\n        int protoIndex = 0;\n        for (java.lang.reflect.Field field : cls.getDeclaredFields()) {\n            if (field.isEnumConstant()) fields.add(new EnumField(protoIndex++, field));\n        }\n    }\n\n    @Override\n    public String getProtoClass() {\n        return \"enum\";\n    }\n\n    private MethodSpec javaMap(MapType mt, TypeName from, TypeName to) {\n        MethodSpec.Builder method = MethodSpec.methodBuilder(mt.getMethodName());\n        method.addModifiers(Modifier.PUBLIC);\n        method.returns(to);\n        method.addParameter(from, \"from\");\n\n        method.addStatement(\"$T to\", to);\n        method.beginControlFlow(\"switch (from)\");\n\n        for (Field field : fields) {\n            String fromName = (mt == MapType.TO_PROTO) ? field.getName() : field.getProtoName();\n            String toName = (mt == MapType.TO_PROTO) ? field.getProtoName() : field.getName();\n            method.addStatement(\"case $L: to = $T.$L; break\", fromName, to, toName);\n        }\n\n        method.addStatement(\n                \"default: throw new $T(\\\"Unexpected enum constant: \\\" + from)\",\n                IllegalArgumentException.class);\n        method.endControlFlow();\n        method.addStatement(\"return to\");\n        return method.build();\n    }\n\n    @Override\n    protected void javaMapFromProto(TypeSpec.Builder type) {\n        type.addMethod(\n                javaMap(\n                        MapType.FROM_PROTO,\n                        this.type.getJavaProtoType(),\n                        TypeName.get(this.clazz)));\n    }\n\n    @Override\n    protected void javaMapToProto(TypeSpec.Builder type) {\n        type.addMethod(\n                javaMap(MapType.TO_PROTO, TypeName.get(this.clazz), this.type.getJavaProtoType()));\n    }\n\n    public class EnumField extends Field {\n        protected EnumField(int index, java.lang.reflect.Field field) {\n            super(index, field);\n        }\n\n        @Override\n        public String getProtoTypeDeclaration() {\n            return String.format(\"%s = %d\", getProtoName(), getProtoIndex());\n        }\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/Message.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen;\n\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport javax.lang.model.element.Modifier;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.annotationsprocessor.protogen.types.AbstractType;\nimport com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;\nimport com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;\n\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeSpec;\n\npublic class Message extends AbstractMessage {\n    public Message(Class<?> cls, MessageType parent) {\n        super(cls, parent);\n\n        for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {\n            ProtoField ann = field.getAnnotation(ProtoField.class);\n            if (ann == null) continue;\n\n            fields.add(new MessageField(ann.id(), field));\n        }\n    }\n\n    protected ProtoMessage getAnnotation() {\n        return (ProtoMessage) this.clazz.getAnnotation(ProtoMessage.class);\n    }\n\n    @Override\n    public String getProtoClass() {\n        return \"message\";\n    }\n\n    @Override\n    protected void javaMapToProto(TypeSpec.Builder type) {\n        if (!getAnnotation().toProto() || getAnnotation().wrapper()) return;\n\n        ClassName javaProtoType = (ClassName) this.type.getJavaProtoType();\n        MethodSpec.Builder method = MethodSpec.methodBuilder(\"toProto\");\n        method.addModifiers(Modifier.PUBLIC);\n        method.returns(javaProtoType);\n        method.addParameter(this.clazz, \"from\");\n\n        method.addStatement(\n                \"$T to = $T.newBuilder()\", javaProtoType.nestedClass(\"Builder\"), javaProtoType);\n\n        for (Field field : this.fields) {\n            if (field instanceof MessageField) {\n                AbstractType fieldType = ((MessageField) field).getAbstractType();\n                fieldType.mapToProto(field.getName(), method);\n            }\n        }\n\n        method.addStatement(\"return to.build()\");\n        type.addMethod(method.build());\n    }\n\n    @Override\n    protected void javaMapFromProto(TypeSpec.Builder type) {\n        if (!getAnnotation().fromProto() || getAnnotation().wrapper()) return;\n\n        MethodSpec.Builder method = MethodSpec.methodBuilder(\"fromProto\");\n        method.addModifiers(Modifier.PUBLIC);\n        method.returns(this.clazz);\n        method.addParameter(this.type.getJavaProtoType(), \"from\");\n\n        method.addStatement(\"$T to = new $T()\", this.clazz, this.clazz);\n\n        for (Field field : this.fields) {\n            if (field instanceof MessageField) {\n                AbstractType fieldType = ((MessageField) field).getAbstractType();\n                fieldType.mapFromProto(field.getName(), method);\n            }\n        }\n\n        method.addStatement(\"return to\");\n        type.addMethod(method.build());\n    }\n\n    public static class MessageField extends Field {\n        protected AbstractType type;\n\n        protected MessageField(int index, java.lang.reflect.Field field) {\n            super(index, field);\n        }\n\n        public AbstractType getAbstractType() {\n            if (type == null) {\n                type = TypeMapper.INSTANCE.get(field.getGenericType());\n            }\n            return type;\n        }\n\n        private static Pattern CAMEL_CASE_RE = Pattern.compile(\"(?<=[a-z])[A-Z]\");\n\n        private static String toUnderscoreCase(String input) {\n            Matcher m = CAMEL_CASE_RE.matcher(input);\n            StringBuilder sb = new StringBuilder();\n            while (m.find()) {\n                m.appendReplacement(sb, \"_\" + m.group());\n            }\n            m.appendTail(sb);\n            return sb.toString().toLowerCase();\n        }\n\n        @Override\n        public String getProtoTypeDeclaration() {\n            return String.format(\n                    \"%s %s = %d\",\n                    getAbstractType().getProtoType(), toUnderscoreCase(getName()), getProtoIndex());\n        }\n\n        @Override\n        public void getDependencies(Set<String> deps) {\n            getAbstractType().getDependencies(deps);\n        }\n\n        @Override\n        public void generateAbstractMethods(Set<MethodSpec> specs) {\n            getAbstractType().generateAbstractMethods(specs);\n        }\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/ProtoFile.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;\n\nimport com.squareup.javapoet.ClassName;\n\npublic class ProtoFile {\n    public static String PROTO_SUFFIX = \"Pb\";\n\n    private ClassName baseClass;\n    private AbstractMessage message;\n    private String filePath;\n\n    private String protoPackageName;\n    private String javaPackageName;\n    private String goPackageName;\n\n    public ProtoFile(\n            Class<?> object,\n            String protoPackageName,\n            String javaPackageName,\n            String goPackageName) {\n        this.protoPackageName = protoPackageName;\n        this.javaPackageName = javaPackageName;\n        this.goPackageName = goPackageName;\n\n        String className = object.getSimpleName() + PROTO_SUFFIX;\n        this.filePath = \"model/\" + object.getSimpleName().toLowerCase() + \".proto\";\n        this.baseClass = ClassName.get(this.javaPackageName, className);\n        this.message = new Message(object, TypeMapper.INSTANCE.baseClass(baseClass, filePath));\n    }\n\n    public String getJavaClassName() {\n        return baseClass.simpleName();\n    }\n\n    public String getFilePath() {\n        return filePath;\n    }\n\n    public String getProtoPackageName() {\n        return protoPackageName;\n    }\n\n    public String getJavaPackageName() {\n        return javaPackageName;\n    }\n\n    public String getGoPackageName() {\n        return goPackageName;\n    }\n\n    public AbstractMessage getMessage() {\n        return message;\n    }\n\n    public Set<String> getIncludes() {\n        Set<String> includes = new HashSet<>();\n        message.findDependencies(includes);\n        includes.remove(this.getFilePath());\n        return includes;\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/ProtoGen.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen;\n\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.util.*;\n\nimport javax.annotation.Generated;\nimport javax.lang.model.element.Modifier;\n\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\nimport com.github.jknack.handlebars.EscapingStrategy;\nimport com.github.jknack.handlebars.Handlebars;\nimport com.github.jknack.handlebars.Template;\nimport com.github.jknack.handlebars.io.ClassPathTemplateLoader;\nimport com.github.jknack.handlebars.io.TemplateLoader;\nimport com.google.common.reflect.ClassPath;\nimport com.squareup.javapoet.AnnotationSpec;\nimport com.squareup.javapoet.JavaFile;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeSpec;\n\npublic class ProtoGen {\n    private static final String GENERATOR_NAME =\n            \"com.netflix.conductor.annotationsprocessor.protogen\";\n\n    private String protoPackageName;\n    private String javaPackageName;\n    private String goPackageName;\n    private List<ProtoFile> protoFiles = new ArrayList<>();\n\n    public ProtoGen(String protoPackageName, String javaPackageName, String goPackageName) {\n        this.protoPackageName = protoPackageName;\n        this.javaPackageName = javaPackageName;\n        this.goPackageName = goPackageName;\n    }\n\n    public void writeMapper(File root, String mapperPackageName) throws IOException {\n        TypeSpec.Builder protoMapper =\n                TypeSpec.classBuilder(\"AbstractProtoMapper\")\n                        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n                        .addAnnotation(\n                                AnnotationSpec.builder(Generated.class)\n                                        .addMember(\"value\", \"$S\", GENERATOR_NAME)\n                                        .build());\n\n        Set<MethodSpec> abstractMethods = new HashSet<>();\n\n        protoFiles.sort(\n                new Comparator<ProtoFile>() {\n                    public int compare(ProtoFile p1, ProtoFile p2) {\n                        String n1 = p1.getMessage().getName();\n                        String n2 = p2.getMessage().getName();\n                        return n1.compareTo(n2);\n                    }\n                });\n\n        for (ProtoFile protoFile : protoFiles) {\n            AbstractMessage elem = protoFile.getMessage();\n            elem.generateJavaMapper(protoMapper);\n            elem.generateAbstractMethods(abstractMethods);\n        }\n\n        protoMapper.addMethods(abstractMethods);\n\n        JavaFile javaFile =\n                JavaFile.builder(mapperPackageName, protoMapper.build()).indent(\"    \").build();\n        File filename = new File(root, \"AbstractProtoMapper.java\");\n        try (Writer writer = new FileWriter(filename.toString())) {\n            System.out.printf(\"protogen: writing '%s'...\\n\", filename);\n            javaFile.writeTo(writer);\n        }\n    }\n\n    public void writeProtos(File root) throws IOException {\n        TemplateLoader loader = new ClassPathTemplateLoader(\"/templates\", \".proto\");\n        Handlebars handlebars =\n                new Handlebars(loader)\n                        .infiniteLoops(true)\n                        .prettyPrint(true)\n                        .with(EscapingStrategy.NOOP);\n\n        Template protoFile = handlebars.compile(\"file\");\n\n        for (ProtoFile file : protoFiles) {\n            File filename = new File(root, file.getFilePath());\n            try (Writer writer = new FileWriter(filename)) {\n                System.out.printf(\"protogen: writing '%s'...\\n\", filename);\n                protoFile.apply(file, writer);\n            }\n        }\n    }\n\n    public void processPackage(File jarFile, String packageName) throws IOException {\n        if (!jarFile.isFile()) throw new IOException(\"missing Jar file \" + jarFile);\n\n        URL[] urls = new URL[] {jarFile.toURI().toURL()};\n        ClassLoader loader =\n                new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());\n        ClassPath cp = ClassPath.from(loader);\n\n        System.out.printf(\"protogen: processing Jar '%s'\\n\", jarFile);\n        for (ClassPath.ClassInfo info : cp.getTopLevelClassesRecursive(packageName)) {\n            try {\n                processClass(info.load());\n            } catch (NoClassDefFoundError ignored) {\n            }\n        }\n    }\n\n    public void processClass(Class<?> obj) {\n        if (obj.isAnnotationPresent(ProtoMessage.class)) {\n            System.out.printf(\"protogen: found %s\\n\", obj.getCanonicalName());\n            protoFiles.add(new ProtoFile(obj, protoPackageName, javaPackageName, goPackageName));\n        }\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/ProtoGenTask.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen;\n\nimport java.io.File;\nimport java.io.IOException;\n\npublic class ProtoGenTask {\n    private String protoPackage;\n    private String javaPackage;\n    private String goPackage;\n\n    private File protosDir;\n    private File mapperDir;\n    private String mapperPackage;\n\n    private File sourceJar;\n    private String sourcePackage;\n\n    public String getProtoPackage() {\n        return protoPackage;\n    }\n\n    public void setProtoPackage(String protoPackage) {\n        this.protoPackage = protoPackage;\n    }\n\n    public String getJavaPackage() {\n        return javaPackage;\n    }\n\n    public void setJavaPackage(String javaPackage) {\n        this.javaPackage = javaPackage;\n    }\n\n    public String getGoPackage() {\n        return goPackage;\n    }\n\n    public void setGoPackage(String goPackage) {\n        this.goPackage = goPackage;\n    }\n\n    public File getProtosDir() {\n        return protosDir;\n    }\n\n    public void setProtosDir(File protosDir) {\n        this.protosDir = protosDir;\n    }\n\n    public File getMapperDir() {\n        return mapperDir;\n    }\n\n    public void setMapperDir(File mapperDir) {\n        this.mapperDir = mapperDir;\n    }\n\n    public String getMapperPackage() {\n        return mapperPackage;\n    }\n\n    public void setMapperPackage(String mapperPackage) {\n        this.mapperPackage = mapperPackage;\n    }\n\n    public File getSourceJar() {\n        return sourceJar;\n    }\n\n    public void setSourceJar(File sourceJar) {\n        this.sourceJar = sourceJar;\n    }\n\n    public String getSourcePackage() {\n        return sourcePackage;\n    }\n\n    public void setSourcePackage(String sourcePackage) {\n        this.sourcePackage = sourcePackage;\n    }\n\n    public void generate() {\n        ProtoGen generator = new ProtoGen(protoPackage, javaPackage, goPackage);\n        try {\n            generator.processPackage(sourceJar, sourcePackage);\n            generator.writeMapper(mapperDir, mapperPackage);\n            generator.writeProtos(protosDir);\n        } catch (IOException e) {\n            System.err.printf(\"protogen: failed with %s\\n\", e);\n        }\n    }\n\n    public static void main(String[] args) {\n        if (args == null || args.length < 8) {\n            throw new RuntimeException(\n                    \"protogen configuration incomplete, please provide all required (8) inputs\");\n        }\n        ProtoGenTask task = new ProtoGenTask();\n        int argsId = 0;\n        task.setProtoPackage(args[argsId++]);\n        task.setJavaPackage(args[argsId++]);\n        task.setGoPackage(args[argsId++]);\n        task.setProtosDir(new File(args[argsId++]));\n        task.setMapperDir(new File(args[argsId++]));\n        task.setMapperPackage(args[argsId++]);\n        task.setSourceJar(new File(args[argsId++]));\n        task.setSourcePackage(args[argsId]);\n        System.out.println(\"Running protogen with arguments: \" + task);\n        task.generate();\n        System.out.println(\"protogen completed.\");\n    }\n\n    @Override\n    public String toString() {\n        return \"ProtoGenTask{\"\n                + \"protoPackage='\"\n                + protoPackage\n                + '\\''\n                + \", javaPackage='\"\n                + javaPackage\n                + '\\''\n                + \", goPackage='\"\n                + goPackage\n                + '\\''\n                + \", protosDir=\"\n                + protosDir\n                + \", mapperDir=\"\n                + mapperDir\n                + \", mapperPackage='\"\n                + mapperPackage\n                + '\\''\n                + \", sourceJar=\"\n                + sourceJar\n                + \", sourcePackage='\"\n                + sourcePackage\n                + '\\''\n                + '}';\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/AbstractType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.Type;\nimport java.util.Set;\n\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeName;\n\npublic abstract class AbstractType {\n    Type javaType;\n    TypeName javaProtoType;\n\n    AbstractType(Type javaType, TypeName javaProtoType) {\n        this.javaType = javaType;\n        this.javaProtoType = javaProtoType;\n    }\n\n    public Type getJavaType() {\n        return javaType;\n    }\n\n    public TypeName getJavaProtoType() {\n        return javaProtoType;\n    }\n\n    public abstract String getProtoType();\n\n    public abstract TypeName getRawJavaType();\n\n    public abstract void mapToProto(String field, MethodSpec.Builder method);\n\n    public abstract void mapFromProto(String field, MethodSpec.Builder method);\n\n    public abstract void getDependencies(Set<String> deps);\n\n    public abstract void generateAbstractMethods(Set<MethodSpec> specs);\n\n    protected String javaMethodName(String m, String field) {\n        String fieldName = field.substring(0, 1).toUpperCase() + field.substring(1);\n        return m + fieldName;\n    }\n\n    private static class ProtoCase {\n        static String convert(String s) {\n            StringBuilder out = new StringBuilder(s.length());\n            final int len = s.length();\n            int i = 0;\n            int j = -1;\n            while ((j = findWordBoundary(s, ++j)) != -1) {\n                out.append(normalizeWord(s.substring(i, j)));\n                if (j < len && s.charAt(j) == '_') j++;\n                i = j;\n            }\n            if (i == 0) return normalizeWord(s);\n            if (i < len) out.append(normalizeWord(s.substring(i)));\n            return out.toString();\n        }\n\n        private static boolean isWordBoundary(char c) {\n            return (c >= 'A' && c <= 'Z');\n        }\n\n        private static int findWordBoundary(CharSequence sequence, int start) {\n            int length = sequence.length();\n            if (start >= length) return -1;\n\n            if (isWordBoundary(sequence.charAt(start))) {\n                int i = start;\n                while (i < length && isWordBoundary(sequence.charAt(i))) i++;\n                return i;\n            } else {\n                for (int i = start; i < length; i++) {\n                    final char c = sequence.charAt(i);\n                    if (c == '_' || isWordBoundary(c)) return i;\n                }\n                return -1;\n            }\n        }\n\n        private static String normalizeWord(String word) {\n            if (word.length() < 2) return word.toUpperCase();\n            return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();\n        }\n    }\n\n    protected String protoMethodName(String m, String field) {\n        return m + ProtoCase.convert(field);\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/ExternMessageType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.Type;\nimport java.util.Set;\n\nimport javax.lang.model.element.Modifier;\n\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\n\npublic class ExternMessageType extends MessageType {\n    private String externProtoType;\n\n    public ExternMessageType(\n            Type javaType, ClassName javaProtoType, String externProtoType, String protoFilePath) {\n        super(javaType, javaProtoType, protoFilePath);\n        this.externProtoType = externProtoType;\n    }\n\n    @Override\n    public String getProtoType() {\n        return externProtoType;\n    }\n\n    @Override\n    public void generateAbstractMethods(Set<MethodSpec> specs) {\n        MethodSpec fromProto =\n                MethodSpec.methodBuilder(\"fromProto\")\n                        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n                        .returns(this.getJavaType())\n                        .addParameter(this.getJavaProtoType(), \"in\")\n                        .build();\n\n        MethodSpec toProto =\n                MethodSpec.methodBuilder(\"toProto\")\n                        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n                        .returns(this.getJavaProtoType())\n                        .addParameter(this.getJavaType(), \"in\")\n                        .build();\n\n        specs.add(fromProto);\n        specs.add(toProto);\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/GenericType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.Set;\n\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeName;\n\nabstract class GenericType extends AbstractType {\n    public GenericType(Type type) {\n        super(type, null);\n    }\n\n    protected Class getRawType() {\n        ParameterizedType tt = (ParameterizedType) this.getJavaType();\n        return (Class) tt.getRawType();\n    }\n\n    protected AbstractType resolveGenericParam(int idx) {\n        ParameterizedType tt = (ParameterizedType) this.getJavaType();\n        Type[] types = tt.getActualTypeArguments();\n\n        AbstractType abstractType = TypeMapper.INSTANCE.get(types[idx]);\n        if (abstractType instanceof GenericType) {\n            return WrappedType.wrap((GenericType) abstractType);\n        }\n        return abstractType;\n    }\n\n    public abstract String getWrapperSuffix();\n\n    public abstract AbstractType getValueType();\n\n    public abstract TypeName resolveJavaProtoType();\n\n    @Override\n    public TypeName getRawJavaType() {\n        return ClassName.get(getRawType());\n    }\n\n    @Override\n    public void getDependencies(Set<String> deps) {\n        getValueType().getDependencies(deps);\n    }\n\n    @Override\n    public void generateAbstractMethods(Set<MethodSpec> specs) {\n        getValueType().generateAbstractMethods(specs);\n    }\n\n    @Override\n    public TypeName getJavaProtoType() {\n        if (javaProtoType == null) {\n            javaProtoType = resolveJavaProtoType();\n        }\n        return javaProtoType;\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/ListType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.Type;\nimport java.util.stream.Collectors;\n\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterizedTypeName;\nimport com.squareup.javapoet.TypeName;\n\npublic class ListType extends GenericType {\n    private AbstractType valueType;\n\n    public ListType(Type type) {\n        super(type);\n    }\n\n    @Override\n    public String getWrapperSuffix() {\n        return \"List\";\n    }\n\n    @Override\n    public AbstractType getValueType() {\n        if (valueType == null) {\n            valueType = resolveGenericParam(0);\n        }\n        return valueType;\n    }\n\n    @Override\n    public void mapToProto(String field, MethodSpec.Builder method) {\n        AbstractType subtype = getValueType();\n        if (subtype instanceof ScalarType) {\n            method.addStatement(\n                    \"to.$L( from.$L() )\",\n                    protoMethodName(\"addAll\", field),\n                    javaMethodName(\"get\", field));\n        } else {\n            method.beginControlFlow(\n                    \"for ($T elem : from.$L())\",\n                    subtype.getJavaType(),\n                    javaMethodName(\"get\", field));\n            method.addStatement(\"to.$L( toProto(elem) )\", protoMethodName(\"add\", field));\n            method.endControlFlow();\n        }\n    }\n\n    @Override\n    public void mapFromProto(String field, MethodSpec.Builder method) {\n        AbstractType subtype = getValueType();\n        Type entryType = subtype.getJavaType();\n        Class collector = TypeMapper.PROTO_LIST_TYPES.get(getRawType());\n\n        if (subtype instanceof ScalarType) {\n            if (entryType.equals(String.class)) {\n                method.addStatement(\n                        \"to.$L( from.$L().stream().collect($T.toCollection($T::new)) )\",\n                        javaMethodName(\"set\", field),\n                        protoMethodName(\"get\", field) + \"List\",\n                        Collectors.class,\n                        collector);\n            } else {\n                method.addStatement(\n                        \"to.$L( from.$L() )\",\n                        javaMethodName(\"set\", field),\n                        protoMethodName(\"get\", field) + \"List\");\n            }\n        } else {\n            method.addStatement(\n                    \"to.$L( from.$L().stream().map(this::fromProto).collect($T.toCollection($T::new)) )\",\n                    javaMethodName(\"set\", field),\n                    protoMethodName(\"get\", field) + \"List\",\n                    Collectors.class,\n                    collector);\n        }\n    }\n\n    @Override\n    public TypeName resolveJavaProtoType() {\n        return ParameterizedTypeName.get(\n                (ClassName) getRawJavaType(), getValueType().getJavaProtoType());\n    }\n\n    @Override\n    public String getProtoType() {\n        return \"repeated \" + getValueType().getProtoType();\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/MapType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.Type;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.ParameterizedTypeName;\nimport com.squareup.javapoet.TypeName;\n\npublic class MapType extends GenericType {\n    private AbstractType keyType;\n    private AbstractType valueType;\n\n    public MapType(Type type) {\n        super(type);\n    }\n\n    @Override\n    public String getWrapperSuffix() {\n        return \"Map\";\n    }\n\n    @Override\n    public AbstractType getValueType() {\n        if (valueType == null) {\n            valueType = resolveGenericParam(1);\n        }\n        return valueType;\n    }\n\n    public AbstractType getKeyType() {\n        if (keyType == null) {\n            keyType = resolveGenericParam(0);\n        }\n        return keyType;\n    }\n\n    @Override\n    public void mapToProto(String field, MethodSpec.Builder method) {\n        AbstractType valueType = getValueType();\n        if (valueType instanceof ScalarType) {\n            method.addStatement(\n                    \"to.$L( from.$L() )\",\n                    protoMethodName(\"putAll\", field),\n                    javaMethodName(\"get\", field));\n        } else {\n            TypeName typeName =\n                    ParameterizedTypeName.get(\n                            Map.Entry.class,\n                            getKeyType().getJavaType(),\n                            getValueType().getJavaType());\n            method.beginControlFlow(\n                    \"for ($T pair : from.$L().entrySet())\", typeName, javaMethodName(\"get\", field));\n            method.addStatement(\n                    \"to.$L( pair.getKey(), toProto( pair.getValue() ) )\",\n                    protoMethodName(\"put\", field));\n            method.endControlFlow();\n        }\n    }\n\n    @Override\n    public void mapFromProto(String field, MethodSpec.Builder method) {\n        AbstractType valueType = getValueType();\n        if (valueType instanceof ScalarType) {\n            method.addStatement(\n                    \"to.$L( from.$L() )\",\n                    javaMethodName(\"set\", field),\n                    protoMethodName(\"get\", field) + \"Map\");\n        } else {\n            Type keyType = getKeyType().getJavaType();\n            Type valueTypeJava = getValueType().getJavaType();\n            TypeName valueTypePb = getValueType().getJavaProtoType();\n\n            ParameterizedTypeName entryType =\n                    ParameterizedTypeName.get(\n                            ClassName.get(Map.Entry.class), TypeName.get(keyType), valueTypePb);\n            ParameterizedTypeName mapType =\n                    ParameterizedTypeName.get(Map.class, keyType, valueTypeJava);\n            ParameterizedTypeName hashMapType =\n                    ParameterizedTypeName.get(HashMap.class, keyType, valueTypeJava);\n            String mapName = field + \"Map\";\n\n            method.addStatement(\"$T $L = new $T()\", mapType, mapName, hashMapType);\n            method.beginControlFlow(\n                    \"for ($T pair : from.$L().entrySet())\",\n                    entryType,\n                    protoMethodName(\"get\", field) + \"Map\");\n            method.addStatement(\"$L.put( pair.getKey(), fromProto( pair.getValue() ) )\", mapName);\n            method.endControlFlow();\n            method.addStatement(\"to.$L($L)\", javaMethodName(\"set\", field), mapName);\n        }\n    }\n\n    @Override\n    public TypeName resolveJavaProtoType() {\n        return ParameterizedTypeName.get(\n                (ClassName) getRawJavaType(),\n                getKeyType().getJavaProtoType(),\n                getValueType().getJavaProtoType());\n    }\n\n    @Override\n    public String getProtoType() {\n        AbstractType keyType = getKeyType();\n        AbstractType valueType = getValueType();\n        if (!(keyType instanceof ScalarType)) {\n            throw new IllegalArgumentException(\n                    \"cannot map non-scalar map key: \" + this.getJavaType());\n        }\n        return String.format(\"map<%s, %s>\", keyType.getProtoType(), valueType.getProtoType());\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/MessageType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.Set;\n\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeName;\n\npublic class MessageType extends AbstractType {\n    private String protoFilePath;\n\n    public MessageType(Type javaType, ClassName javaProtoType, String protoFilePath) {\n        super(javaType, javaProtoType);\n        this.protoFilePath = protoFilePath;\n    }\n\n    @Override\n    public String getProtoType() {\n        List<String> classes = ((ClassName) getJavaProtoType()).simpleNames();\n        return String.join(\".\", classes.subList(1, classes.size()));\n    }\n\n    public String getProtoFilePath() {\n        return protoFilePath;\n    }\n\n    @Override\n    public TypeName getRawJavaType() {\n        return getJavaProtoType();\n    }\n\n    @Override\n    public void mapToProto(String field, MethodSpec.Builder method) {\n        final String getter = javaMethodName(\"get\", field);\n        method.beginControlFlow(\"if (from.$L() != null)\", getter);\n        method.addStatement(\"to.$L( toProto( from.$L() ) )\", protoMethodName(\"set\", field), getter);\n        method.endControlFlow();\n    }\n\n    private boolean isEnum() {\n        Type clazz = getJavaType();\n        return (clazz instanceof Class<?>) && ((Class) clazz).isEnum();\n    }\n\n    @Override\n    public void mapFromProto(String field, MethodSpec.Builder method) {\n        if (!isEnum()) method.beginControlFlow(\"if (from.$L())\", protoMethodName(\"has\", field));\n\n        method.addStatement(\n                \"to.$L( fromProto( from.$L() ) )\",\n                javaMethodName(\"set\", field),\n                protoMethodName(\"get\", field));\n\n        if (!isEnum()) method.endControlFlow();\n    }\n\n    @Override\n    public void getDependencies(Set<String> deps) {\n        deps.add(protoFilePath);\n    }\n\n    @Override\n    public void generateAbstractMethods(Set<MethodSpec> specs) {}\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/ScalarType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.Type;\nimport java.util.Set;\n\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeName;\n\npublic class ScalarType extends AbstractType {\n    private String protoType;\n\n    public ScalarType(Type javaType, TypeName javaProtoType, String protoType) {\n        super(javaType, javaProtoType);\n        this.protoType = protoType;\n    }\n\n    @Override\n    public String getProtoType() {\n        return protoType;\n    }\n\n    @Override\n    public TypeName getRawJavaType() {\n        return getJavaProtoType();\n    }\n\n    @Override\n    public void mapFromProto(String field, MethodSpec.Builder method) {\n        method.addStatement(\n                \"to.$L( from.$L() )\", javaMethodName(\"set\", field), protoMethodName(\"get\", field));\n    }\n\n    private boolean isNullableType() {\n        final Type jt = getJavaType();\n        return jt.equals(Boolean.class)\n                || jt.equals(Byte.class)\n                || jt.equals(Character.class)\n                || jt.equals(Short.class)\n                || jt.equals(Integer.class)\n                || jt.equals(Long.class)\n                || jt.equals(Double.class)\n                || jt.equals(Float.class)\n                || jt.equals(String.class);\n    }\n\n    @Override\n    public void mapToProto(String field, MethodSpec.Builder method) {\n        final boolean nullable = isNullableType();\n        String getter =\n                (getJavaType().equals(boolean.class) || getJavaType().equals(Boolean.class))\n                        ? javaMethodName(\"is\", field)\n                        : javaMethodName(\"get\", field);\n\n        if (nullable) method.beginControlFlow(\"if (from.$L() != null)\", getter);\n\n        method.addStatement(\"to.$L( from.$L() )\", protoMethodName(\"set\", field), getter);\n\n        if (nullable) method.endControlFlow();\n    }\n\n    @Override\n    public void getDependencies(Set<String> deps) {}\n\n    @Override\n    public void generateAbstractMethods(Set<MethodSpec> specs) {}\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/TypeMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.*;\n\nimport com.google.protobuf.Any;\nimport com.squareup.javapoet.ClassName;\nimport com.squareup.javapoet.TypeName;\n\npublic class TypeMapper {\n    static Map<Type, Class> PROTO_LIST_TYPES = new HashMap<>();\n\n    static {\n        PROTO_LIST_TYPES.put(List.class, ArrayList.class);\n        PROTO_LIST_TYPES.put(Set.class, HashSet.class);\n        PROTO_LIST_TYPES.put(LinkedList.class, LinkedList.class);\n    }\n\n    public static TypeMapper INSTANCE = new TypeMapper();\n\n    private Map<Type, AbstractType> types = new HashMap<>();\n\n    public void addScalarType(Type t, String protoType) {\n        types.put(t, new ScalarType(t, TypeName.get(t), protoType));\n    }\n\n    public void addMessageType(Class<?> t, MessageType message) {\n        types.put(t, message);\n    }\n\n    public TypeMapper() {\n        addScalarType(int.class, \"int32\");\n        addScalarType(Integer.class, \"int32\");\n        addScalarType(long.class, \"int64\");\n        addScalarType(Long.class, \"int64\");\n        addScalarType(String.class, \"string\");\n        addScalarType(boolean.class, \"bool\");\n        addScalarType(Boolean.class, \"bool\");\n\n        addMessageType(\n                Object.class,\n                new ExternMessageType(\n                        Object.class,\n                        ClassName.get(\"com.google.protobuf\", \"Value\"),\n                        \"google.protobuf.Value\",\n                        \"google/protobuf/struct.proto\"));\n\n        addMessageType(\n                Any.class,\n                new ExternMessageType(\n                        Any.class,\n                        ClassName.get(Any.class),\n                        \"google.protobuf.Any\",\n                        \"google/protobuf/any.proto\"));\n    }\n\n    public AbstractType get(Type t) {\n        if (!types.containsKey(t)) {\n            if (t instanceof ParameterizedType) {\n                Type raw = ((ParameterizedType) t).getRawType();\n                if (PROTO_LIST_TYPES.containsKey(raw)) {\n                    types.put(t, new ListType(t));\n                } else if (raw.equals(Map.class)) {\n                    types.put(t, new MapType(t));\n                }\n            }\n        }\n        if (!types.containsKey(t)) {\n            throw new IllegalArgumentException(\"Cannot map type: \" + t);\n        }\n        return types.get(t);\n    }\n\n    public MessageType get(String className) {\n        for (Map.Entry<Type, AbstractType> pair : types.entrySet()) {\n            AbstractType t = pair.getValue();\n            if (t instanceof MessageType) {\n                if (((Class) t.getJavaType()).getSimpleName().equals(className))\n                    return (MessageType) t;\n            }\n        }\n        return null;\n    }\n\n    public MessageType declare(Class type, MessageType parent) {\n        return declare(type, (ClassName) parent.getJavaProtoType(), parent.getProtoFilePath());\n    }\n\n    public MessageType declare(Class type, ClassName parentType, String protoFilePath) {\n        String simpleName = type.getSimpleName();\n        MessageType t = new MessageType(type, parentType.nestedClass(simpleName), protoFilePath);\n        if (types.containsKey(type)) {\n            throw new IllegalArgumentException(\"duplicate type declaration: \" + type);\n        }\n        types.put(type, t);\n        return t;\n    }\n\n    public MessageType baseClass(ClassName className, String protoFilePath) {\n        return new MessageType(Object.class, className, protoFilePath);\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/WrappedType.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen.types;\n\nimport java.lang.reflect.Type;\nimport java.util.Set;\n\nimport javax.lang.model.element.Modifier;\n\nimport com.squareup.javapoet.MethodSpec;\nimport com.squareup.javapoet.TypeName;\n\npublic class WrappedType extends AbstractType {\n    private AbstractType realType;\n    private MessageType wrappedType;\n\n    public static WrappedType wrap(GenericType realType) {\n        Type valueType = realType.getValueType().getJavaType();\n        if (!(valueType instanceof Class))\n            throw new IllegalArgumentException(\"cannot wrap primitive type: \" + valueType);\n\n        String className = ((Class) valueType).getSimpleName() + realType.getWrapperSuffix();\n        MessageType wrappedType = TypeMapper.INSTANCE.get(className);\n        if (wrappedType == null)\n            throw new IllegalArgumentException(\"missing wrapper class: \" + className);\n        return new WrappedType(realType, wrappedType);\n    }\n\n    public WrappedType(AbstractType realType, MessageType wrappedType) {\n        super(realType.getJavaType(), wrappedType.getJavaProtoType());\n        this.realType = realType;\n        this.wrappedType = wrappedType;\n    }\n\n    @Override\n    public String getProtoType() {\n        return wrappedType.getProtoType();\n    }\n\n    @Override\n    public TypeName getRawJavaType() {\n        return realType.getRawJavaType();\n    }\n\n    @Override\n    public void mapToProto(String field, MethodSpec.Builder method) {\n        wrappedType.mapToProto(field, method);\n    }\n\n    @Override\n    public void mapFromProto(String field, MethodSpec.Builder method) {\n        wrappedType.mapFromProto(field, method);\n    }\n\n    @Override\n    public void getDependencies(Set<String> deps) {\n        this.realType.getDependencies(deps);\n        this.wrappedType.getDependencies(deps);\n    }\n\n    @Override\n    public void generateAbstractMethods(Set<MethodSpec> specs) {\n        MethodSpec fromProto =\n                MethodSpec.methodBuilder(\"fromProto\")\n                        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n                        .returns(this.realType.getJavaType())\n                        .addParameter(this.wrappedType.getJavaProtoType(), \"in\")\n                        .build();\n\n        MethodSpec toProto =\n                MethodSpec.methodBuilder(\"toProto\")\n                        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n                        .returns(this.wrappedType.getJavaProtoType())\n                        .addParameter(this.realType.getJavaType(), \"in\")\n                        .build();\n\n        specs.add(fromProto);\n        specs.add(toProto);\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/main/resources/templates/file.proto",
    "content": "syntax = \"proto3\";\npackage {{protoPackageName}};\n\n{{#includes}}\nimport \"{{this}}\";\n{{/includes}}\n\noption java_package = \"{{javaPackageName}}\";\noption java_outer_classname = \"{{javaClassName}}\";\noption go_package = \"{{goPackageName}}\";\n\n{{#message}}\n{{>message}}\n{{/message}}\n"
  },
  {
    "path": "annotations-processor/src/main/resources/templates/message.proto",
    "content": "{{protoClass}} {{name}} {\n{{#nested}}\n    {{>message}}\n{{/nested}}\n{{#fields}}\n    {{protoTypeDeclaration}};\n{{/fields}}\n}\n"
  },
  {
    "path": "annotations-processor/src/test/java/com/netflix/conductor/annotationsprocessor/protogen/ProtoGenTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotationsprocessor.protogen;\n\nimport java.io.File;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\nimport com.google.common.collect.Lists;\nimport com.google.common.io.Files;\nimport com.google.common.io.Resources;\n\nimport static org.junit.Assert.*;\n\npublic class ProtoGenTest {\n    private static final Charset charset = StandardCharsets.UTF_8;\n\n    @Rule public TemporaryFolder folder = new TemporaryFolder();\n\n    @Test\n    public void happyPath() throws Exception {\n        File rootDir = folder.getRoot();\n        String protoPackage = \"protoPackage\";\n        String javaPackage = \"abc.protogen.example\";\n        String goPackage = \"goPackage\";\n        String sourcePackage = \"com.example\";\n        String mapperPackage = \"mapperPackage\";\n\n        File jarFile = new File(\"./build/libs/example.jar\");\n        assertTrue(jarFile.exists());\n\n        File mapperDir = new File(rootDir, \"mapperDir\");\n        mapperDir.mkdirs();\n\n        File protosDir = new File(rootDir, \"protosDir\");\n        protosDir.mkdirs();\n\n        File modelDir = new File(protosDir, \"model\");\n        modelDir.mkdirs();\n\n        ProtoGen generator = new ProtoGen(protoPackage, javaPackage, goPackage);\n        generator.processPackage(jarFile, sourcePackage);\n        generator.writeMapper(mapperDir, mapperPackage);\n        generator.writeProtos(protosDir);\n\n        List<File> models = Lists.newArrayList(modelDir.listFiles());\n        assertEquals(1, models.size());\n        File exampleProtoFile =\n                models.stream().filter(f -> f.getName().equals(\"example.proto\")).findFirst().get();\n        assertTrue(exampleProtoFile.length() > 0);\n        assertEquals(\n                Resources.asCharSource(Resources.getResource(\"example.proto.txt\"), charset).read(),\n                Files.asCharSource(exampleProtoFile, charset).read());\n    }\n}\n"
  },
  {
    "path": "annotations-processor/src/test/resources/example.proto.txt",
    "content": "syntax = \"proto3\";\npackage protoPackage;\n\n\noption java_package = \"abc.protogen.example\";\noption java_outer_classname = \"ExamplePb\";\noption go_package = \"goPackage\";\n\nmessage Example {\n    string name = 1;\n    int64 count = 2;\n}\n"
  },
  {
    "path": "awss3-storage/README.md",
    "content": ""
  },
  {
    "path": "awss3-storage/build.gradle",
    "content": "/*\n *  Copyright 2022 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\ndependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n\n    implementation \"com.amazonaws:aws-java-sdk-s3:${revAwsSdk}\"\n    implementation \"org.apache.commons:commons-lang3\"\n}\n"
  },
  {
    "path": "awss3-storage/src/main/java/com/netflix/conductor/s3/config/S3Configuration.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.s3.config;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.s3.storage.S3PayloadStorage;\n\nimport com.amazonaws.services.s3.AmazonS3;\nimport com.amazonaws.services.s3.AmazonS3ClientBuilder;\n\n@Configuration\n@EnableConfigurationProperties(S3Properties.class)\n@ConditionalOnProperty(name = \"conductor.external-payload-storage.type\", havingValue = \"s3\")\npublic class S3Configuration {\n\n    @Bean\n    public ExternalPayloadStorage s3ExternalPayloadStorage(\n            IDGenerator idGenerator, S3Properties properties, AmazonS3 s3Client) {\n        return new S3PayloadStorage(idGenerator, properties, s3Client);\n    }\n\n    @ConditionalOnProperty(\n            name = \"conductor.external-payload-storage.s3.use_default_client\",\n            havingValue = \"true\",\n            matchIfMissing = true)\n    @Bean\n    public AmazonS3 amazonS3(S3Properties properties) {\n        return AmazonS3ClientBuilder.standard().withRegion(properties.getRegion()).build();\n    }\n}\n"
  },
  {
    "path": "awss3-storage/src/main/java/com/netflix/conductor/s3/config/S3Properties.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.s3.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\n\n@ConfigurationProperties(\"conductor.external-payload-storage.s3\")\npublic class S3Properties {\n\n    /** The s3 bucket name where the payloads will be stored */\n    private String bucketName = \"conductor_payloads\";\n\n    /** The time (in seconds) for which the signed url will be valid */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration signedUrlExpirationDuration = Duration.ofSeconds(5);\n\n    /** The AWS region of the s3 bucket */\n    private String region = \"us-east-1\";\n\n    public String getBucketName() {\n        return bucketName;\n    }\n\n    public void setBucketName(String bucketName) {\n        this.bucketName = bucketName;\n    }\n\n    public Duration getSignedUrlExpirationDuration() {\n        return signedUrlExpirationDuration;\n    }\n\n    public void setSignedUrlExpirationDuration(Duration signedUrlExpirationDuration) {\n        this.signedUrlExpirationDuration = signedUrlExpirationDuration;\n    }\n\n    public String getRegion() {\n        return region;\n    }\n\n    public void setRegion(String region) {\n        this.region = region;\n    }\n}\n"
  },
  {
    "path": "awss3-storage/src/main/java/com/netflix/conductor/s3/storage/S3PayloadStorage.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.s3.storage;\n\nimport java.io.InputStream;\nimport java.net.URISyntaxException;\nimport java.util.Date;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.s3.config.S3Properties;\n\nimport com.amazonaws.HttpMethod;\nimport com.amazonaws.SdkClientException;\nimport com.amazonaws.services.s3.AmazonS3;\nimport com.amazonaws.services.s3.model.*;\n\n/**\n * An implementation of {@link ExternalPayloadStorage} using AWS S3 for storing large JSON payload\n * data.\n *\n * <p><em>NOTE: The S3 client assumes that access to S3 is configured on the instance.</em>\n *\n * @see <a\n *     href=\"https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/index.html?com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html\">DefaultAWSCredentialsProviderChain</a>\n */\npublic class S3PayloadStorage implements ExternalPayloadStorage {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(S3PayloadStorage.class);\n    private static final String CONTENT_TYPE = \"application/json\";\n\n    private final IDGenerator idGenerator;\n    private final AmazonS3 s3Client;\n    private final String bucketName;\n    private final long expirationSec;\n\n    public S3PayloadStorage(IDGenerator idGenerator, S3Properties properties, AmazonS3 s3Client) {\n        this.idGenerator = idGenerator;\n        this.s3Client = s3Client;\n        bucketName = properties.getBucketName();\n        expirationSec = properties.getSignedUrlExpirationDuration().getSeconds();\n    }\n\n    /**\n     * @param operation the type of {@link Operation} to be performed\n     * @param payloadType the {@link PayloadType} that is being accessed\n     * @return a {@link ExternalStorageLocation} object which contains the pre-signed URL and the s3\n     *     object key for the json payload\n     */\n    @Override\n    public ExternalStorageLocation getLocation(\n            Operation operation, PayloadType payloadType, String path) {\n        try {\n            ExternalStorageLocation externalStorageLocation = new ExternalStorageLocation();\n\n            Date expiration = new Date();\n            long expTimeMillis = expiration.getTime() + 1000 * expirationSec;\n            expiration.setTime(expTimeMillis);\n\n            HttpMethod httpMethod = HttpMethod.GET;\n            if (operation == Operation.WRITE) {\n                httpMethod = HttpMethod.PUT;\n            }\n\n            String objectKey;\n            if (StringUtils.isNotBlank(path)) {\n                objectKey = path;\n            } else {\n                objectKey = getObjectKey(payloadType);\n            }\n            externalStorageLocation.setPath(objectKey);\n\n            GeneratePresignedUrlRequest generatePresignedUrlRequest =\n                    new GeneratePresignedUrlRequest(bucketName, objectKey)\n                            .withMethod(httpMethod)\n                            .withExpiration(expiration);\n\n            externalStorageLocation.setUri(\n                    s3Client.generatePresignedUrl(generatePresignedUrlRequest)\n                            .toURI()\n                            .toASCIIString());\n            return externalStorageLocation;\n        } catch (SdkClientException e) {\n            String msg =\n                    String.format(\n                            \"Error communicating with S3 - operation:%s, payloadType: %s, path: %s\",\n                            operation, payloadType, path);\n            LOGGER.error(msg, e);\n            throw new TransientException(msg, e);\n        } catch (URISyntaxException e) {\n            String msg = \"Invalid URI Syntax\";\n            LOGGER.error(msg, e);\n            throw new NonTransientException(msg, e);\n        }\n    }\n\n    /**\n     * Uploads the payload to the given s3 object key. It is expected that the caller retrieves the\n     * object key using {@link #getLocation(Operation, PayloadType, String)} before making this\n     * call.\n     *\n     * @param path the s3 key of the object to be uploaded\n     * @param payload an {@link InputStream} containing the json payload which is to be uploaded\n     * @param payloadSize the size of the json payload in bytes\n     */\n    @Override\n    public void upload(String path, InputStream payload, long payloadSize) {\n        try {\n            ObjectMetadata objectMetadata = new ObjectMetadata();\n            objectMetadata.setContentType(CONTENT_TYPE);\n            objectMetadata.setContentLength(payloadSize);\n            PutObjectRequest request =\n                    new PutObjectRequest(bucketName, path, payload, objectMetadata);\n            s3Client.putObject(request);\n        } catch (SdkClientException e) {\n            String msg =\n                    String.format(\n                            \"Error uploading to S3 - path:%s, payloadSize: %d\", path, payloadSize);\n            LOGGER.error(msg, e);\n            throw new TransientException(msg, e);\n        }\n    }\n\n    /**\n     * Downloads the payload stored in the s3 object.\n     *\n     * @param path the S3 key of the object\n     * @return an input stream containing the contents of the object Caller is expected to close the\n     *     input stream.\n     */\n    @Override\n    public InputStream download(String path) {\n        try {\n            S3Object s3Object = s3Client.getObject(new GetObjectRequest(bucketName, path));\n            return s3Object.getObjectContent();\n        } catch (SdkClientException e) {\n            String msg = String.format(\"Error downloading from S3 - path:%s\", path);\n            LOGGER.error(msg, e);\n            throw new TransientException(msg, e);\n        }\n    }\n\n    private String getObjectKey(PayloadType payloadType) {\n        StringBuilder stringBuilder = new StringBuilder();\n        switch (payloadType) {\n            case WORKFLOW_INPUT:\n                stringBuilder.append(\"workflow/input/\");\n                break;\n            case WORKFLOW_OUTPUT:\n                stringBuilder.append(\"workflow/output/\");\n                break;\n            case TASK_INPUT:\n                stringBuilder.append(\"task/input/\");\n                break;\n            case TASK_OUTPUT:\n                stringBuilder.append(\"task/output/\");\n                break;\n        }\n        stringBuilder.append(idGenerator.generate()).append(\".json\");\n        return stringBuilder.toString();\n    }\n}\n"
  },
  {
    "path": "awss3-storage/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"hints\": [\n    {\n      \"name\": \"conductor.external-payload-storage.type\",\n      \"values\": [\n        {\n          \"value\": \"s3\",\n          \"description\": \"Use AWS S3 as the external payload storage.\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "awssqs-event-queue/README.md",
    "content": ""
  },
  {
    "path": "awssqs-event-queue/build.gradle",
    "content": "dependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n\n    implementation \"org.apache.commons:commons-lang3\"\n    // SBMTODO: remove guava dep\n    implementation \"com.google.guava:guava:${revGuava}\"\n\n    implementation \"com.amazonaws:aws-java-sdk-sqs:${revAwsSdk}\"\n\n    implementation \"io.reactivex:rxjava:${revRxJava}\"\n\n    testImplementation 'org.springframework.boot:spring-boot-starter'\n    testImplementation project(':conductor-common').sourceSets.test.output\n}"
  },
  {
    "path": "awssqs-event-queue/src/main/java/com/netflix/conductor/sqs/config/SQSEventQueueConfiguration.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sqs.config;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.events.EventQueueProvider;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.model.TaskModel.Status;\nimport com.netflix.conductor.sqs.eventqueue.SQSObservableQueue.Builder;\n\nimport com.amazonaws.auth.AWSCredentialsProvider;\nimport com.amazonaws.auth.DefaultAWSCredentialsProviderChain;\nimport com.amazonaws.client.builder.AwsClientBuilder;\nimport com.amazonaws.services.sqs.AmazonSQS;\nimport com.amazonaws.services.sqs.AmazonSQSClientBuilder;\nimport rx.Scheduler;\n\n@Configuration\n@EnableConfigurationProperties(SQSEventQueueProperties.class)\n@ConditionalOnProperty(name = \"conductor.event-queues.sqs.enabled\", havingValue = \"true\")\npublic class SQSEventQueueConfiguration {\n\n    @Autowired private SQSEventQueueProperties sqsProperties;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SQSEventQueueConfiguration.class);\n\n    @Bean\n    AWSCredentialsProvider createAWSCredentialsProvider() {\n        return new DefaultAWSCredentialsProviderChain();\n    }\n\n    @ConditionalOnMissingBean\n    @Bean\n    public AmazonSQS getSQSClient(AWSCredentialsProvider credentialsProvider) {\n        AmazonSQSClientBuilder builder =\n                AmazonSQSClientBuilder.standard().withCredentials(credentialsProvider);\n        if (!sqsProperties.getEndpoint().isEmpty()) {\n            LOGGER.info(\"Setting custom SQS endpoint to {}\", sqsProperties.getEndpoint());\n            builder.withEndpointConfiguration(\n                    new AwsClientBuilder.EndpointConfiguration(\n                            sqsProperties.getEndpoint(), System.getenv(\"AWS_REGION\")));\n        }\n        return builder.build();\n    }\n\n    @Bean\n    public EventQueueProvider sqsEventQueueProvider(\n            AmazonSQS sqsClient, SQSEventQueueProperties properties, Scheduler scheduler) {\n        return new SQSEventQueueProvider(sqsClient, properties, scheduler);\n    }\n\n    @ConditionalOnProperty(\n            name = \"conductor.default-event-queue.type\",\n            havingValue = \"sqs\",\n            matchIfMissing = true)\n    @Bean\n    public Map<Status, ObservableQueue> getQueues(\n            ConductorProperties conductorProperties,\n            SQSEventQueueProperties properties,\n            AmazonSQS sqsClient) {\n        String stack = \"\";\n        if (conductorProperties.getStack() != null && conductorProperties.getStack().length() > 0) {\n            stack = conductorProperties.getStack() + \"_\";\n        }\n        Status[] statuses = new Status[] {Status.COMPLETED, Status.FAILED};\n        Map<Status, ObservableQueue> queues = new HashMap<>();\n        for (Status status : statuses) {\n            String queuePrefix =\n                    StringUtils.isBlank(properties.getListenerQueuePrefix())\n                            ? conductorProperties.getAppId() + \"_sqs_notify_\" + stack\n                            : properties.getListenerQueuePrefix();\n\n            String queueName = queuePrefix + status.name();\n\n            Builder builder = new Builder().withClient(sqsClient).withQueueName(queueName);\n\n            String auth = properties.getAuthorizedAccounts();\n            String[] accounts = auth.split(\",\");\n            for (String accountToAuthorize : accounts) {\n                accountToAuthorize = accountToAuthorize.trim();\n                if (accountToAuthorize.length() > 0) {\n                    builder.addAccountToAuthorize(accountToAuthorize.trim());\n                }\n            }\n            ObservableQueue queue = builder.build();\n            queues.put(status, queue);\n        }\n\n        return queues;\n    }\n}\n"
  },
  {
    "path": "awssqs-event-queue/src/main/java/com/netflix/conductor/sqs/config/SQSEventQueueProperties.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sqs.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\n\n@ConfigurationProperties(\"conductor.event-queues.sqs\")\npublic class SQSEventQueueProperties {\n\n    /** The maximum number of messages to be fetched from the queue in a single request */\n    private int batchSize = 1;\n\n    /** The polling interval (in milliseconds) */\n    private Duration pollTimeDuration = Duration.ofMillis(100);\n\n    /** The visibility timeout (in seconds) for the message on the queue */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration visibilityTimeout = Duration.ofSeconds(60);\n\n    /** The prefix to be used for the default listener queues */\n    private String listenerQueuePrefix = \"\";\n\n    /** The AWS account Ids authorized to send messages to the queues */\n    private String authorizedAccounts = \"\";\n\n    /** The endpoint to use to connect to a local SQS server for testing */\n    private String endpoint = \"\";\n\n    public int getBatchSize() {\n        return batchSize;\n    }\n\n    public void setBatchSize(int batchSize) {\n        this.batchSize = batchSize;\n    }\n\n    public Duration getPollTimeDuration() {\n        return pollTimeDuration;\n    }\n\n    public void setPollTimeDuration(Duration pollTimeDuration) {\n        this.pollTimeDuration = pollTimeDuration;\n    }\n\n    public Duration getVisibilityTimeout() {\n        return visibilityTimeout;\n    }\n\n    public void setVisibilityTimeout(Duration visibilityTimeout) {\n        this.visibilityTimeout = visibilityTimeout;\n    }\n\n    public String getListenerQueuePrefix() {\n        return listenerQueuePrefix;\n    }\n\n    public void setListenerQueuePrefix(String listenerQueuePrefix) {\n        this.listenerQueuePrefix = listenerQueuePrefix;\n    }\n\n    public String getAuthorizedAccounts() {\n        return authorizedAccounts;\n    }\n\n    public void setAuthorizedAccounts(String authorizedAccounts) {\n        this.authorizedAccounts = authorizedAccounts;\n    }\n\n    public String getEndpoint() {\n        return endpoint;\n    }\n\n    public void setEndpoint(String endpoint) {\n        this.endpoint = endpoint;\n    }\n}\n"
  },
  {
    "path": "awssqs-event-queue/src/main/java/com/netflix/conductor/sqs/config/SQSEventQueueProvider.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sqs.config;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport org.springframework.lang.NonNull;\n\nimport com.netflix.conductor.core.events.EventQueueProvider;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.sqs.eventqueue.SQSObservableQueue;\n\nimport com.amazonaws.services.sqs.AmazonSQS;\nimport rx.Scheduler;\n\npublic class SQSEventQueueProvider implements EventQueueProvider {\n\n    private final Map<String, ObservableQueue> queues = new ConcurrentHashMap<>();\n    private final AmazonSQS client;\n    private final int batchSize;\n    private final long pollTimeInMS;\n    private final int visibilityTimeoutInSeconds;\n    private final Scheduler scheduler;\n\n    public SQSEventQueueProvider(\n            AmazonSQS client, SQSEventQueueProperties properties, Scheduler scheduler) {\n        this.client = client;\n        this.batchSize = properties.getBatchSize();\n        this.pollTimeInMS = properties.getPollTimeDuration().toMillis();\n        this.visibilityTimeoutInSeconds = (int) properties.getVisibilityTimeout().getSeconds();\n        this.scheduler = scheduler;\n    }\n\n    @Override\n    public String getQueueType() {\n        return \"sqs\";\n    }\n\n    @Override\n    @NonNull\n    public ObservableQueue getQueue(String queueURI) {\n        return queues.computeIfAbsent(\n                queueURI,\n                q ->\n                        new SQSObservableQueue.Builder()\n                                .withBatchSize(this.batchSize)\n                                .withClient(client)\n                                .withPollTimeInMS(this.pollTimeInMS)\n                                .withQueueName(queueURI)\n                                .withVisibilityTimeout(this.visibilityTimeoutInSeconds)\n                                .withScheduler(scheduler)\n                                .build());\n    }\n}\n"
  },
  {
    "path": "awssqs-event-queue/src/main/java/com/netflix/conductor/sqs/eventqueue/SQSObservableQueue.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sqs.eventqueue;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport com.amazonaws.auth.policy.Action;\nimport com.amazonaws.auth.policy.Policy;\nimport com.amazonaws.auth.policy.Principal;\nimport com.amazonaws.auth.policy.Resource;\nimport com.amazonaws.auth.policy.Statement;\nimport com.amazonaws.auth.policy.Statement.Effect;\nimport com.amazonaws.auth.policy.actions.SQSActions;\nimport com.amazonaws.services.sqs.AmazonSQS;\nimport com.amazonaws.services.sqs.model.BatchResultErrorEntry;\nimport com.amazonaws.services.sqs.model.ChangeMessageVisibilityRequest;\nimport com.amazonaws.services.sqs.model.CreateQueueRequest;\nimport com.amazonaws.services.sqs.model.CreateQueueResult;\nimport com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;\nimport com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry;\nimport com.amazonaws.services.sqs.model.DeleteMessageBatchResult;\nimport com.amazonaws.services.sqs.model.GetQueueAttributesResult;\nimport com.amazonaws.services.sqs.model.ListQueuesRequest;\nimport com.amazonaws.services.sqs.model.ListQueuesResult;\nimport com.amazonaws.services.sqs.model.ReceiveMessageRequest;\nimport com.amazonaws.services.sqs.model.ReceiveMessageResult;\nimport com.amazonaws.services.sqs.model.SendMessageBatchRequest;\nimport com.amazonaws.services.sqs.model.SendMessageBatchRequestEntry;\nimport com.amazonaws.services.sqs.model.SendMessageBatchResult;\nimport com.amazonaws.services.sqs.model.SetQueueAttributesResult;\nimport rx.Observable;\nimport rx.Observable.OnSubscribe;\nimport rx.Scheduler;\n\npublic class SQSObservableQueue implements ObservableQueue {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SQSObservableQueue.class);\n    private static final String QUEUE_TYPE = \"sqs\";\n\n    private final String queueName;\n    private final int visibilityTimeoutInSeconds;\n    private final int batchSize;\n    private final AmazonSQS client;\n    private final long pollTimeInMS;\n    private final String queueURL;\n    private final Scheduler scheduler;\n    private volatile boolean running;\n\n    private SQSObservableQueue(\n            String queueName,\n            AmazonSQS client,\n            int visibilityTimeoutInSeconds,\n            int batchSize,\n            long pollTimeInMS,\n            List<String> accountsToAuthorize,\n            Scheduler scheduler) {\n        this.queueName = queueName;\n        this.client = client;\n        this.visibilityTimeoutInSeconds = visibilityTimeoutInSeconds;\n        this.batchSize = batchSize;\n        this.pollTimeInMS = pollTimeInMS;\n        this.queueURL = getOrCreateQueue();\n        this.scheduler = scheduler;\n        addPolicy(accountsToAuthorize);\n    }\n\n    @Override\n    public Observable<Message> observe() {\n        OnSubscribe<Message> subscriber = getOnSubscribe();\n        return Observable.create(subscriber);\n    }\n\n    @Override\n    public List<String> ack(List<Message> messages) {\n        return delete(messages);\n    }\n\n    @Override\n    public void publish(List<Message> messages) {\n        publishMessages(messages);\n    }\n\n    @Override\n    public long size() {\n        GetQueueAttributesResult attributes =\n                client.getQueueAttributes(\n                        queueURL, Collections.singletonList(\"ApproximateNumberOfMessages\"));\n        String sizeAsStr = attributes.getAttributes().get(\"ApproximateNumberOfMessages\");\n        try {\n            return Long.parseLong(sizeAsStr);\n        } catch (Exception e) {\n            return -1;\n        }\n    }\n\n    @Override\n    public void setUnackTimeout(Message message, long unackTimeout) {\n        int unackTimeoutInSeconds = (int) (unackTimeout / 1000);\n        ChangeMessageVisibilityRequest request =\n                new ChangeMessageVisibilityRequest(\n                        queueURL, message.getReceipt(), unackTimeoutInSeconds);\n        client.changeMessageVisibility(request);\n    }\n\n    @Override\n    public String getType() {\n        return QUEUE_TYPE;\n    }\n\n    @Override\n    public String getName() {\n        return queueName;\n    }\n\n    @Override\n    public String getURI() {\n        return queueURL;\n    }\n\n    public long getPollTimeInMS() {\n        return pollTimeInMS;\n    }\n\n    public int getBatchSize() {\n        return batchSize;\n    }\n\n    public int getVisibilityTimeoutInSeconds() {\n        return visibilityTimeoutInSeconds;\n    }\n\n    @Override\n    public void start() {\n        LOGGER.info(\"Started listening to {}:{}\", getClass().getSimpleName(), queueName);\n        running = true;\n    }\n\n    @Override\n    public void stop() {\n        LOGGER.info(\"Stopped listening to {}:{}\", getClass().getSimpleName(), queueName);\n        running = false;\n    }\n\n    @Override\n    public boolean isRunning() {\n        return running;\n    }\n\n    public static class Builder {\n\n        private String queueName;\n        private int visibilityTimeout = 30; // seconds\n        private int batchSize = 5;\n        private long pollTimeInMS = 100;\n        private AmazonSQS client;\n        private List<String> accountsToAuthorize = new LinkedList<>();\n        private Scheduler scheduler;\n\n        public Builder withQueueName(String queueName) {\n            this.queueName = queueName;\n            return this;\n        }\n\n        /**\n         * @param visibilityTimeout Visibility timeout for the message in SECONDS\n         * @return builder instance\n         */\n        public Builder withVisibilityTimeout(int visibilityTimeout) {\n            this.visibilityTimeout = visibilityTimeout;\n            return this;\n        }\n\n        public Builder withBatchSize(int batchSize) {\n            this.batchSize = batchSize;\n            return this;\n        }\n\n        public Builder withClient(AmazonSQS client) {\n            this.client = client;\n            return this;\n        }\n\n        public Builder withPollTimeInMS(long pollTimeInMS) {\n            this.pollTimeInMS = pollTimeInMS;\n            return this;\n        }\n\n        public Builder withAccountsToAuthorize(List<String> accountsToAuthorize) {\n            this.accountsToAuthorize = accountsToAuthorize;\n            return this;\n        }\n\n        public Builder addAccountToAuthorize(String accountToAuthorize) {\n            this.accountsToAuthorize.add(accountToAuthorize);\n            return this;\n        }\n\n        public Builder withScheduler(Scheduler scheduler) {\n            this.scheduler = scheduler;\n            return this;\n        }\n\n        public SQSObservableQueue build() {\n            return new SQSObservableQueue(\n                    queueName,\n                    client,\n                    visibilityTimeout,\n                    batchSize,\n                    pollTimeInMS,\n                    accountsToAuthorize,\n                    scheduler);\n        }\n    }\n\n    // Private methods\n    String getOrCreateQueue() {\n        List<String> queueUrls = listQueues(queueName);\n        if (queueUrls == null || queueUrls.isEmpty()) {\n            CreateQueueRequest createQueueRequest =\n                    new CreateQueueRequest().withQueueName(queueName);\n            CreateQueueResult result = client.createQueue(createQueueRequest);\n            return result.getQueueUrl();\n        } else {\n            return queueUrls.get(0);\n        }\n    }\n\n    private String getQueueARN() {\n        GetQueueAttributesResult response =\n                client.getQueueAttributes(queueURL, Collections.singletonList(\"QueueArn\"));\n        return response.getAttributes().get(\"QueueArn\");\n    }\n\n    private void addPolicy(List<String> accountsToAuthorize) {\n        if (accountsToAuthorize == null || accountsToAuthorize.isEmpty()) {\n            LOGGER.info(\"No additional security policies attached for the queue \" + queueName);\n            return;\n        }\n        LOGGER.info(\"Authorizing \" + accountsToAuthorize + \" to the queue \" + queueName);\n        Map<String, String> attributes = new HashMap<>();\n        attributes.put(\"Policy\", getPolicy(accountsToAuthorize));\n        SetQueueAttributesResult result = client.setQueueAttributes(queueURL, attributes);\n        LOGGER.info(\"policy attachment result: \" + result);\n        LOGGER.info(\n                \"policy attachment result: status=\"\n                        + result.getSdkHttpMetadata().getHttpStatusCode());\n    }\n\n    private String getPolicy(List<String> accountIds) {\n        Policy policy = new Policy(\"AuthorizedWorkerAccessPolicy\");\n        Statement stmt = new Statement(Effect.Allow);\n        Action action = SQSActions.SendMessage;\n        stmt.getActions().add(action);\n        stmt.setResources(new LinkedList<>());\n        for (String accountId : accountIds) {\n            Principal principal = new Principal(accountId);\n            stmt.getPrincipals().add(principal);\n        }\n        stmt.getResources().add(new Resource(getQueueARN()));\n        policy.getStatements().add(stmt);\n        return policy.toJson();\n    }\n\n    private List<String> listQueues(String queueName) {\n        ListQueuesRequest listQueuesRequest =\n                new ListQueuesRequest().withQueueNamePrefix(queueName);\n        ListQueuesResult resultList = client.listQueues(listQueuesRequest);\n        return resultList.getQueueUrls().stream()\n                .filter(queueUrl -> queueUrl.contains(queueName))\n                .collect(Collectors.toList());\n    }\n\n    private void publishMessages(List<Message> messages) {\n        LOGGER.debug(\"Sending {} messages to the SQS queue: {}\", messages.size(), queueName);\n        SendMessageBatchRequest batch = new SendMessageBatchRequest(queueURL);\n        messages.forEach(\n                msg -> {\n                    SendMessageBatchRequestEntry sendr =\n                            new SendMessageBatchRequestEntry(msg.getId(), msg.getPayload());\n                    batch.getEntries().add(sendr);\n                });\n        LOGGER.debug(\"sending {} messages in batch\", batch.getEntries().size());\n        SendMessageBatchResult result = client.sendMessageBatch(batch);\n        LOGGER.debug(\"send result: {} for SQS queue: {}\", result.getFailed().toString(), queueName);\n    }\n\n    List<Message> receiveMessages() {\n        try {\n            ReceiveMessageRequest receiveMessageRequest =\n                    new ReceiveMessageRequest()\n                            .withQueueUrl(queueURL)\n                            .withVisibilityTimeout(visibilityTimeoutInSeconds)\n                            .withMaxNumberOfMessages(batchSize);\n\n            ReceiveMessageResult result = client.receiveMessage(receiveMessageRequest);\n\n            List<Message> messages =\n                    result.getMessages().stream()\n                            .map(\n                                    msg ->\n                                            new Message(\n                                                    msg.getMessageId(),\n                                                    msg.getBody(),\n                                                    msg.getReceiptHandle()))\n                            .collect(Collectors.toList());\n            Monitors.recordEventQueueMessagesProcessed(QUEUE_TYPE, this.queueName, messages.size());\n            return messages;\n        } catch (Exception e) {\n            LOGGER.error(\"Exception while getting messages from SQS\", e);\n            Monitors.recordObservableQMessageReceivedErrors(QUEUE_TYPE);\n        }\n        return new ArrayList<>();\n    }\n\n    OnSubscribe<Message> getOnSubscribe() {\n        return subscriber -> {\n            Observable<Long> interval = Observable.interval(pollTimeInMS, TimeUnit.MILLISECONDS);\n            interval.flatMap(\n                            (Long x) -> {\n                                if (!isRunning()) {\n                                    LOGGER.debug(\n                                            \"Component stopped, skip listening for messages from SQS\");\n                                    return Observable.from(Collections.emptyList());\n                                }\n                                List<Message> messages = receiveMessages();\n                                return Observable.from(messages);\n                            })\n                    .subscribe(subscriber::onNext, subscriber::onError);\n        };\n    }\n\n    private List<String> delete(List<Message> messages) {\n        if (messages == null || messages.isEmpty()) {\n            return null;\n        }\n\n        DeleteMessageBatchRequest batch = new DeleteMessageBatchRequest().withQueueUrl(queueURL);\n        List<DeleteMessageBatchRequestEntry> entries = batch.getEntries();\n\n        messages.forEach(\n                m ->\n                        entries.add(\n                                new DeleteMessageBatchRequestEntry()\n                                        .withId(m.getId())\n                                        .withReceiptHandle(m.getReceipt())));\n\n        DeleteMessageBatchResult result = client.deleteMessageBatch(batch);\n        List<String> failures =\n                result.getFailed().stream()\n                        .map(BatchResultErrorEntry::getId)\n                        .collect(Collectors.toList());\n        LOGGER.debug(\"Failed to delete messages from queue: {}: {}\", queueName, failures);\n        return failures;\n    }\n}\n"
  },
  {
    "path": "awssqs-event-queue/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"properties\": [\n    {\n      \"name\": \"conductor.event-queues.sqs.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enable the use of AWS SQS implementation to provide queues for consuming events.\",\n      \"sourceType\": \"com.netflix.conductor.sqs.config.SQSEventQueueConfiguration\"\n    },\n    {\n      \"name\": \"conductor.default-event-queue.type\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"The default event queue type to listen on for the WAIT task.\",\n      \"sourceType\": \"com.netflix.conductor.sqs.config.SQSEventQueueConfiguration\"\n    }\n  ],\n  \"hints\": [\n    {\n      \"name\": \"conductor.default-event-queue.type\",\n      \"values\": [\n        {\n          \"value\": \"sqs\",\n          \"description\": \"Use AWS SQS as the event queue to listen on for the WAIT task.\"\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "awssqs-event-queue/src/test/java/com/netflix/conductor/sqs/eventqueue/DefaultEventQueueProcessorTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sqs.eventqueue;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.stubbing.Answer;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.core.events.queue.DefaultEventQueueProcessor;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.TaskModel.Status;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.util.concurrent.Uninterruptibles;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_WAIT;\n\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@SuppressWarnings(\"unchecked\")\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class DefaultEventQueueProcessorTest {\n\n    private static SQSObservableQueue queue;\n    private static WorkflowExecutor workflowExecutor;\n    private DefaultEventQueueProcessor defaultEventQueueProcessor;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    private static final List<Message> messages = new LinkedList<>();\n    private static final List<TaskResult> updatedTasks = new LinkedList<>();\n    private static final List<Task> mappedTasks = new LinkedList<>();\n\n    @Before\n    public void init() {\n        Map<Status, ObservableQueue> queues = new HashMap<>();\n        queues.put(Status.COMPLETED, queue);\n        defaultEventQueueProcessor =\n                new DefaultEventQueueProcessor(queues, workflowExecutor, objectMapper);\n    }\n\n    @BeforeClass\n    public static void setup() {\n\n        queue = mock(SQSObservableQueue.class);\n        when(queue.getOrCreateQueue()).thenReturn(\"junit_queue_url\");\n        when(queue.isRunning()).thenReturn(true);\n        Answer<?> answer =\n                (Answer<List<Message>>)\n                        invocation -> {\n                            List<Message> copy = new LinkedList<>(messages);\n                            messages.clear();\n                            return copy;\n                        };\n\n        when(queue.receiveMessages()).thenAnswer(answer);\n        when(queue.getOnSubscribe()).thenCallRealMethod();\n        when(queue.observe()).thenCallRealMethod();\n        when(queue.getName()).thenReturn(Status.COMPLETED.name());\n\n        TaskModel task0 = new TaskModel();\n        task0.setStatus(Status.IN_PROGRESS);\n        task0.setTaskId(\"t0\");\n        task0.setReferenceTaskName(\"t0\");\n        task0.setTaskType(TASK_TYPE_WAIT);\n        WorkflowModel workflow0 = new WorkflowModel();\n        workflow0.setWorkflowId(\"v_0\");\n        workflow0.getTasks().add(task0);\n\n        TaskModel task2 = new TaskModel();\n        task2.setStatus(Status.IN_PROGRESS);\n        task2.setTaskId(\"t2\");\n        task2.setTaskType(TASK_TYPE_WAIT);\n        WorkflowModel workflow2 = new WorkflowModel();\n        workflow2.setWorkflowId(\"v_2\");\n        workflow2.getTasks().add(task2);\n\n        doAnswer(\n                        (Answer<Void>)\n                                invocation -> {\n                                    List<Message> msgs = invocation.getArgument(0, List.class);\n                                    messages.addAll(msgs);\n                                    return null;\n                                })\n                .when(queue)\n                .publish(any());\n\n        workflowExecutor = mock(WorkflowExecutor.class);\n        assertNotNull(workflowExecutor);\n\n        doReturn(workflow0).when(workflowExecutor).getWorkflow(eq(\"v_0\"), anyBoolean());\n\n        doReturn(workflow2).when(workflowExecutor).getWorkflow(eq(\"v_2\"), anyBoolean());\n\n        doAnswer(\n                        (Answer<Void>)\n                                invocation -> {\n                                    updatedTasks.add(invocation.getArgument(0, TaskResult.class));\n                                    return null;\n                                })\n                .when(workflowExecutor)\n                .updateTask(any(TaskResult.class));\n    }\n\n    @Test\n    public void test() throws Exception {\n        defaultEventQueueProcessor.updateByTaskRefName(\n                \"v_0\", \"t0\", new HashMap<>(), Status.COMPLETED);\n        Uninterruptibles.sleepUninterruptibly(1_000, TimeUnit.MILLISECONDS);\n\n        assertTrue(updatedTasks.stream().anyMatch(task -> task.getTaskId().equals(\"t0\")));\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testFailure() throws Exception {\n        defaultEventQueueProcessor.updateByTaskRefName(\n                \"v_1\", \"t1\", new HashMap<>(), Status.CANCELED);\n        Uninterruptibles.sleepUninterruptibly(1_000, TimeUnit.MILLISECONDS);\n    }\n\n    @Test\n    public void testWithTaskId() throws Exception {\n        defaultEventQueueProcessor.updateByTaskId(\"v_2\", \"t2\", new HashMap<>(), Status.COMPLETED);\n        Uninterruptibles.sleepUninterruptibly(1_000, TimeUnit.MILLISECONDS);\n        assertTrue(updatedTasks.stream().anyMatch(task -> task.getTaskId().equals(\"t2\")));\n    }\n}\n"
  },
  {
    "path": "awssqs-event-queue/src/test/java/com/netflix/conductor/sqs/eventqueue/SQSObservableQueueTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sqs.eventqueue;\n\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\nimport org.mockito.stubbing.Answer;\n\nimport com.netflix.conductor.core.events.queue.Message;\n\nimport com.amazonaws.services.sqs.AmazonSQS;\nimport com.amazonaws.services.sqs.model.ListQueuesRequest;\nimport com.amazonaws.services.sqs.model.ListQueuesResult;\nimport com.amazonaws.services.sqs.model.ReceiveMessageRequest;\nimport com.amazonaws.services.sqs.model.ReceiveMessageResult;\nimport com.google.common.util.concurrent.Uninterruptibles;\nimport rx.Observable;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class SQSObservableQueueTest {\n\n    @Test\n    public void test() {\n\n        List<Message> messages = new LinkedList<>();\n        Observable.range(0, 10)\n                .forEach((Integer x) -> messages.add(new Message(\"\" + x, \"payload: \" + x, null)));\n        assertEquals(10, messages.size());\n\n        SQSObservableQueue queue = mock(SQSObservableQueue.class);\n        when(queue.getOrCreateQueue()).thenReturn(\"junit_queue_url\");\n        Answer<?> answer = (Answer<List<Message>>) invocation -> Collections.emptyList();\n        when(queue.receiveMessages()).thenReturn(messages).thenAnswer(answer);\n        when(queue.isRunning()).thenReturn(true);\n        when(queue.getOnSubscribe()).thenCallRealMethod();\n        when(queue.observe()).thenCallRealMethod();\n\n        List<Message> found = new LinkedList<>();\n        Observable<Message> observable = queue.observe();\n        assertNotNull(observable);\n        observable.subscribe(found::add);\n\n        Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS);\n\n        assertEquals(messages.size(), found.size());\n        assertEquals(messages, found);\n    }\n\n    @Test\n    public void testException() {\n        com.amazonaws.services.sqs.model.Message message =\n                new com.amazonaws.services.sqs.model.Message()\n                        .withMessageId(\"test\")\n                        .withBody(\"\")\n                        .withReceiptHandle(\"receiptHandle\");\n        Answer<?> answer = (Answer<ReceiveMessageResult>) invocation -> new ReceiveMessageResult();\n\n        AmazonSQS client = mock(AmazonSQS.class);\n        when(client.listQueues(any(ListQueuesRequest.class)))\n                .thenReturn(new ListQueuesResult().withQueueUrls(\"junit_queue_url\"));\n        when(client.receiveMessage(any(ReceiveMessageRequest.class)))\n                .thenThrow(new RuntimeException(\"Error in SQS communication\"))\n                .thenReturn(new ReceiveMessageResult().withMessages(message))\n                .thenAnswer(answer);\n\n        SQSObservableQueue queue =\n                new SQSObservableQueue.Builder().withQueueName(\"junit\").withClient(client).build();\n        queue.start();\n\n        List<Message> found = new LinkedList<>();\n        Observable<Message> observable = queue.observe();\n        assertNotNull(observable);\n        observable.subscribe(found::add);\n\n        Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS);\n        assertEquals(1, found.size());\n    }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "import org.springframework.boot.gradle.plugin.SpringBootPlugin\n\nbuildscript {\n    repositories {\n        mavenCentral()\n        maven {\n            url \"https://plugins.gradle.org/m2/\"\n        }\n    }\n    dependencies {\n        classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:10.0.0'\n        classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.7.16'\n        classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.+'\n    }\n}\n\nplugins {\n    id 'io.spring.dependency-management' version '1.1.3'\n    id 'java'\n    id 'application'\n    id 'jacoco'\n    id 'com.netflix.nebula.netflixoss' version '11.3.2'\n    id 'org.sonarqube' version '3.4.0.2513'\n}\n\n/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\n// Establish version and status\next.githubProjectName = rootProject.name // Change if github project name is not the same as the root project's name\n\nsubprojects {\n    tasks.withType(Javadoc).all { enabled = false }\n}\n\napply from: \"$rootDir/dependencies.gradle\"\napply from: \"$rootDir/springboot-bom-overrides.gradle\"\n\nallprojects {\n    apply plugin: 'com.netflix.nebula.netflixoss'\n    apply plugin: 'io.spring.dependency-management'\n    apply plugin: 'java-library'\n    apply plugin: 'project-report'\n\n    java {\n        toolchain {\n            languageVersion = JavaLanguageVersion.of(17)\n        }\n    }\n\n    group = 'com.netflix.conductor'\n\n    configurations.all {\n        exclude group: 'ch.qos.logback', module: 'logback-classic'\n        exclude group: 'ch.qos.logback', module: 'logback-core'\n        exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j'\n        exclude group: 'org.slf4j', module: 'slf4j-log4j12'\n        resolutionStrategy {\n            force 'org.codehaus.jettison:jettison:1.5.4'\n            force \"org.apache.commons:commons-compress:${revCommonsCompress}\"\n        }\n    }\n\n    repositories {\n        mavenCentral()\n\n        // oss-candidate for -rc.* verions:\n        maven {\n            url \"https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates\"\n        }\n\n        /**\n         * This repository locates artifacts that don't exist in maven central but we had to backup from jcenter\n         * The exclusiveContent\n         */\n        exclusiveContent {\n            forRepository {\n                maven {\n                    url \"https://artifactory-oss.prod.netflix.net/artifactory/required-jcenter-modules-backup\"\n                }\n            }\n            filter {\n                includeGroupByRegex \"com\\\\.github\\\\.vmg.*\"\n            }\n        }\n    }\n\n    dependencyManagement {\n        imports {\n            // dependency versions for the BOM can be found at https://docs.spring.io/spring-boot/docs/2.7.3/reference/htmlsingle/#appendix.dependency-versions\n            mavenBom(SpringBootPlugin.BOM_COORDINATES)\n        }\n    }\n\n    dependencies {\n        implementation('org.apache.logging.log4j:log4j-core') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.17.2'\n                // the strict bounds, effectively allowing any 2.x version greater than 2.17.2\n                // could also remove the upper bound entirely if we wanted too\n                strictly '[2.17.2,3.0)'\n            }\n        }\n        implementation('org.apache.logging.log4j:log4j-api') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.17.2'\n                // the strict bounds, effectively allowing any 2.x version greater than 2.17.2\n                // could also remove the upper bound entirely if we wanted too\n                strictly '[2.17.2,3.0)'\n            }\n        }\n        implementation('org.apache.logging.log4j:log4j-slf4j-impl') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.17.2'\n                // the strict bounds, effectively allowing any 2.x version greater than 2.17.2\n                // could also remove the upper bound entirely if we wanted too\n                strictly '[2.17.2,3.0)'\n            }\n        }\n        implementation('org.apache.logging.log4j:log4j-jul') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.17.2'\n                // the strict bounds, effectively allowing any 2.x version greater than 2.17.2\n                // could also remove the upper bound entirely if we wanted too\n                strictly '[2.17.2,3.0)'\n            }\n        }\n        implementation('org.apache.logging.log4j:log4j-web') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.17.2'\n                // the strict bounds, effectively allowing any 2.x version greater than 2.17.2\n                // could also remove the upper bound entirely if we wanted too\n                strictly '[2.17.2,3.0)'\n            }\n        }\n        implementation('org.yaml:snakeyaml') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.0'\n                // the strict bounds, effectively allowing any 2.x version between 2.0 and 2.1\n                strictly '[2.0,2.1)'\n            }\n        }\n        implementation('com.fasterxml.jackson.core:jackson-core') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.core:jackson-databind') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.core:jackson-annotations') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-smile') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-cbor') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.datatype:jackson-datatype-jdk8') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.datatype:jackson-datatype-joda') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.datatype:jackson-datatype-jsr310') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('com.fasterxml.jackson.module:jackson-module-afterburner') {\n            version {\n                // this is the preferred version this library will use\n                prefer '2.15.0'\n                // the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2\n                strictly '[2.15.0,2.15.2)'\n            }\n        }\n        implementation('org.apache.logging.log4j:log4j-core')\n        implementation('org.apache.logging.log4j:log4j-api')\n        implementation('org.apache.logging.log4j:log4j-slf4j-impl')\n        implementation('org.apache.logging.log4j:log4j-jul')\n        implementation('org.apache.logging.log4j:log4j-web') \n        annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'\n\n        testImplementation('org.springframework.boot:spring-boot-starter-test') {\n            exclude group: 'org.yaml', module: 'snakeyaml'\n        }\n        testImplementation('org.springframework.boot:spring-boot-starter-log4j2')\n        testImplementation 'junit:junit'\n        testImplementation \"org.junit.vintage:junit-vintage-engine\"\n\n        // Needed for build to work on m1/m2 macs\n        testImplementation 'net.java.dev.jna:jna:5.13.0'\n    }\n\n    // processes additional configuration metadata json file as described here\n    // https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/appendix-configuration-metadata.html#configuration-metadata-additional-metadata\n    compileJava.inputs.files(processResources)\n\n    test {\n        useJUnitPlatform()\n        testLogging {\n            events = [\"SKIPPED\", \"FAILED\"]\n            exceptionFormat = \"full\"\n            displayGranularity = 1\n            showStandardStreams = false\n        }\n    }\n}\n\n// all client and their related modules are published with Java 17 compatibility\n[\"annotations\", \"common\", \"client\", \"client-spring\", \"grpc\", \"grpc-client\"].each {\n    project(\":conductor-$it\") {\n        compileJava {\n            options.release = 17\n        }\n    }\n}\n\njacocoTestReport {\n    reports {\n        html.required = true\n        xml.required = true\n        csv.required = false\n    }\n}\n\ntask server {\n    dependsOn ':conductor-server:bootRun'\n}\n\nsonarqube {\n    properties {\n        property \"sonar.projectKey\", \"com.netflix.conductor:conductor\"\n        property \"sonar.organization\", \"netflix\"\n        property \"sonar.host.url\", \"https://sonarcloud.io\"\n    }\n}\n\nconfigure(allprojects - project(':conductor-grpc')) {\n    apply plugin: 'com.diffplug.spotless'\n\n    spotless {\n        java {\n            googleJavaFormat().aosp()\n            removeUnusedImports()\n            importOrder('java', 'javax', 'org', 'com.netflix', '', '\\\\#com.netflix', '\\\\#')\n            licenseHeaderFile(\"$rootDir/licenseheader.txt\")\n        }\n    }\n}\n\n['cassandra-persistence', 'core', 'redis-concurrency-limit', 'test-harness', 'client'].each {\n    configure(project(\":conductor-$it\")) {\n        spotless {\n            groovy {\n                importOrder('java', 'javax', 'org', 'com.netflix', '', '\\\\#com.netflix', '\\\\#')\n                licenseHeaderFile(\"$rootDir/licenseheader.txt\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/build.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\napply plugin: 'groovy'\n\ndependencies {\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    implementation \"com.datastax.cassandra:cassandra-driver-core:${revCassandra}\"\n    implementation \"org.apache.commons:commons-lang3\"\n\n    testImplementation project(':conductor-core').sourceSets.test.output\n    testImplementation project(':conductor-common').sourceSets.test.output\n\n    testImplementation \"org.codehaus.groovy:groovy-all:${revGroovy}\"\n    testImplementation \"org.spockframework:spock-core:${revSpock}\"\n    testImplementation \"org.spockframework:spock-spring:${revSpock}\"\n    testImplementation \"org.testcontainers:spock:${revTestContainer}\"\n    testImplementation \"org.testcontainers:cassandra:${revTestContainer}\"\n    testImplementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/CassandraConfiguration.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.config;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.cassandra.config.cache.CacheableEventHandlerDAO;\nimport com.netflix.conductor.cassandra.config.cache.CacheableMetadataDAO;\nimport com.netflix.conductor.cassandra.dao.CassandraEventHandlerDAO;\nimport com.netflix.conductor.cassandra.dao.CassandraExecutionDAO;\nimport com.netflix.conductor.cassandra.dao.CassandraMetadataDAO;\nimport com.netflix.conductor.cassandra.dao.CassandraPollDataDAO;\nimport com.netflix.conductor.cassandra.util.Statements;\nimport com.netflix.conductor.dao.EventHandlerDAO;\nimport com.netflix.conductor.dao.ExecutionDAO;\nimport com.netflix.conductor.dao.MetadataDAO;\n\nimport com.datastax.driver.core.Cluster;\nimport com.datastax.driver.core.Metadata;\nimport com.datastax.driver.core.Session;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(CassandraProperties.class)\n@ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"cassandra\")\npublic class CassandraConfiguration {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CassandraConfiguration.class);\n\n    @Bean\n    public Cluster cluster(CassandraProperties properties) {\n        String host = properties.getHostAddress();\n        int port = properties.getPort();\n\n        LOGGER.info(\"Connecting to cassandra cluster with host:{}, port:{}\", host, port);\n\n        Cluster cluster = Cluster.builder().addContactPoint(host).withPort(port).build();\n\n        Metadata metadata = cluster.getMetadata();\n        LOGGER.info(\"Connected to cluster: {}\", metadata.getClusterName());\n        metadata.getAllHosts()\n                .forEach(\n                        h ->\n                                LOGGER.info(\n                                        \"Datacenter:{}, host:{}, rack: {}\",\n                                        h.getDatacenter(),\n                                        h.getEndPoint().resolve().getHostName(),\n                                        h.getRack()));\n        return cluster;\n    }\n\n    @Bean\n    public Session session(Cluster cluster) {\n        LOGGER.info(\"Initializing cassandra session\");\n        return cluster.connect();\n    }\n\n    @Bean\n    public MetadataDAO cassandraMetadataDAO(\n            Session session,\n            ObjectMapper objectMapper,\n            CassandraProperties properties,\n            Statements statements,\n            CacheManager cacheManager) {\n        CassandraMetadataDAO cassandraMetadataDAO =\n                new CassandraMetadataDAO(session, objectMapper, properties, statements);\n        return new CacheableMetadataDAO(cassandraMetadataDAO, properties, cacheManager);\n    }\n\n    @Bean\n    public ExecutionDAO cassandraExecutionDAO(\n            Session session,\n            ObjectMapper objectMapper,\n            CassandraProperties properties,\n            Statements statements) {\n        return new CassandraExecutionDAO(session, objectMapper, properties, statements);\n    }\n\n    @Bean\n    public EventHandlerDAO cassandraEventHandlerDAO(\n            Session session,\n            ObjectMapper objectMapper,\n            CassandraProperties properties,\n            Statements statements,\n            CacheManager cacheManager) {\n        CassandraEventHandlerDAO cassandraEventHandlerDAO =\n                new CassandraEventHandlerDAO(session, objectMapper, properties, statements);\n        return new CacheableEventHandlerDAO(cassandraEventHandlerDAO, properties, cacheManager);\n    }\n\n    @Bean\n    public CassandraPollDataDAO cassandraPollDataDAO() {\n        return new CassandraPollDataDAO();\n    }\n\n    @Bean\n    public Statements statements(CassandraProperties cassandraProperties) {\n        return new Statements(cassandraProperties.getKeyspace());\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/CassandraProperties.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\n\nimport com.datastax.driver.core.ConsistencyLevel;\n\n@ConfigurationProperties(\"conductor.cassandra\")\npublic class CassandraProperties {\n\n    /** The address for the cassandra database host */\n    private String hostAddress = \"127.0.0.1\";\n\n    /** The port to be used to connect to the cassandra database instance */\n    private int port = 9142;\n\n    /** The name of the cassandra cluster */\n    private String cluster = \"\";\n\n    /** The keyspace to be used in the cassandra datastore */\n    private String keyspace = \"conductor\";\n\n    /**\n     * The number of tasks to be stored in a single partition which will be used for sharding\n     * workflows in the datastore\n     */\n    private int shardSize = 100;\n\n    /** The replication strategy with which to configure the keyspace */\n    private String replicationStrategy = \"SimpleStrategy\";\n\n    /** The key to be used while configuring the replication factor */\n    private String replicationFactorKey = \"replication_factor\";\n\n    /** The replication factor value with which the keyspace is configured */\n    private int replicationFactorValue = 3;\n\n    /** The consistency level to be used for read operations */\n    private ConsistencyLevel readConsistencyLevel = ConsistencyLevel.LOCAL_QUORUM;\n\n    /** The consistency level to be used for write operations */\n    private ConsistencyLevel writeConsistencyLevel = ConsistencyLevel.LOCAL_QUORUM;\n\n    /** The time in seconds after which the in-memory task definitions cache will be refreshed */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration taskDefCacheRefreshInterval = Duration.ofSeconds(60);\n\n    /** The time in seconds after which the in-memory event handler cache will be refreshed */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration eventHandlerCacheRefreshInterval = Duration.ofSeconds(60);\n\n    /** The time to live in seconds for which the event execution will be persisted */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration eventExecutionPersistenceTtl = Duration.ZERO;\n\n    public String getHostAddress() {\n        return hostAddress;\n    }\n\n    public void setHostAddress(String hostAddress) {\n        this.hostAddress = hostAddress;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    public void setPort(int port) {\n        this.port = port;\n    }\n\n    public String getCluster() {\n        return cluster;\n    }\n\n    public void setCluster(String cluster) {\n        this.cluster = cluster;\n    }\n\n    public String getKeyspace() {\n        return keyspace;\n    }\n\n    public void setKeyspace(String keyspace) {\n        this.keyspace = keyspace;\n    }\n\n    public int getShardSize() {\n        return shardSize;\n    }\n\n    public void setShardSize(int shardSize) {\n        this.shardSize = shardSize;\n    }\n\n    public String getReplicationStrategy() {\n        return replicationStrategy;\n    }\n\n    public void setReplicationStrategy(String replicationStrategy) {\n        this.replicationStrategy = replicationStrategy;\n    }\n\n    public String getReplicationFactorKey() {\n        return replicationFactorKey;\n    }\n\n    public void setReplicationFactorKey(String replicationFactorKey) {\n        this.replicationFactorKey = replicationFactorKey;\n    }\n\n    public int getReplicationFactorValue() {\n        return replicationFactorValue;\n    }\n\n    public void setReplicationFactorValue(int replicationFactorValue) {\n        this.replicationFactorValue = replicationFactorValue;\n    }\n\n    public ConsistencyLevel getReadConsistencyLevel() {\n        return readConsistencyLevel;\n    }\n\n    public void setReadConsistencyLevel(ConsistencyLevel readConsistencyLevel) {\n        this.readConsistencyLevel = readConsistencyLevel;\n    }\n\n    public ConsistencyLevel getWriteConsistencyLevel() {\n        return writeConsistencyLevel;\n    }\n\n    public void setWriteConsistencyLevel(ConsistencyLevel writeConsistencyLevel) {\n        this.writeConsistencyLevel = writeConsistencyLevel;\n    }\n\n    public Duration getTaskDefCacheRefreshInterval() {\n        return taskDefCacheRefreshInterval;\n    }\n\n    public void setTaskDefCacheRefreshInterval(Duration taskDefCacheRefreshInterval) {\n        this.taskDefCacheRefreshInterval = taskDefCacheRefreshInterval;\n    }\n\n    public Duration getEventHandlerCacheRefreshInterval() {\n        return eventHandlerCacheRefreshInterval;\n    }\n\n    public void setEventHandlerCacheRefreshInterval(Duration eventHandlerCacheRefreshInterval) {\n        this.eventHandlerCacheRefreshInterval = eventHandlerCacheRefreshInterval;\n    }\n\n    public Duration getEventExecutionPersistenceTtl() {\n        return eventExecutionPersistenceTtl;\n    }\n\n    public void setEventExecutionPersistenceTtl(Duration eventExecutionPersistenceTtl) {\n        this.eventExecutionPersistenceTtl = eventExecutionPersistenceTtl;\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/cache/CacheableEventHandlerDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.config.cache;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport javax.annotation.PostConstruct;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.cache.Cache;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.CachePut;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.cassandra.config.CassandraProperties;\nimport com.netflix.conductor.cassandra.dao.CassandraEventHandlerDAO;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.dao.EventHandlerDAO;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport static com.netflix.conductor.cassandra.config.cache.CachingConfig.EVENT_HANDLER_CACHE;\n\n@Trace\npublic class CacheableEventHandlerDAO implements EventHandlerDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CacheableEventHandlerDAO.class);\n\n    private static final String CLASS_NAME = CacheableEventHandlerDAO.class.getSimpleName();\n\n    private final CassandraEventHandlerDAO cassandraEventHandlerDAO;\n    private final CassandraProperties properties;\n\n    private final CacheManager cacheManager;\n\n    public CacheableEventHandlerDAO(\n            CassandraEventHandlerDAO cassandraEventHandlerDAO,\n            CassandraProperties properties,\n            CacheManager cacheManager) {\n        this.cassandraEventHandlerDAO = cassandraEventHandlerDAO;\n        this.properties = properties;\n        this.cacheManager = cacheManager;\n    }\n\n    @PostConstruct\n    public void scheduleEventHandlerRefresh() {\n        long cacheRefreshTime = properties.getEventHandlerCacheRefreshInterval().getSeconds();\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleWithFixedDelay(\n                        this::refreshEventHandlersCache, 0, cacheRefreshTime, TimeUnit.SECONDS);\n    }\n\n    @Override\n    @CachePut(value = EVENT_HANDLER_CACHE, key = \"#eventHandler.name\")\n    public void addEventHandler(EventHandler eventHandler) {\n        cassandraEventHandlerDAO.addEventHandler(eventHandler);\n    }\n\n    @Override\n    @CachePut(value = EVENT_HANDLER_CACHE, key = \"#eventHandler.name\")\n    public void updateEventHandler(EventHandler eventHandler) {\n        cassandraEventHandlerDAO.updateEventHandler(eventHandler);\n    }\n\n    @Override\n    @CacheEvict(EVENT_HANDLER_CACHE)\n    public void removeEventHandler(String name) {\n        cassandraEventHandlerDAO.removeEventHandler(name);\n    }\n\n    @Override\n    public List<EventHandler> getAllEventHandlers() {\n        Object nativeCache = cacheManager.getCache(EVENT_HANDLER_CACHE).getNativeCache();\n        if (nativeCache != null && nativeCache instanceof ConcurrentHashMap) {\n            ConcurrentHashMap cacheMap = (ConcurrentHashMap) nativeCache;\n            if (!cacheMap.isEmpty()) {\n                List<EventHandler> eventHandlers = new ArrayList<>();\n                cacheMap.values().stream()\n                        .filter(element -> element != null && element instanceof EventHandler)\n                        .forEach(element -> eventHandlers.add((EventHandler) element));\n                return eventHandlers;\n            }\n        }\n\n        return refreshEventHandlersCache();\n    }\n\n    @Override\n    public List<EventHandler> getEventHandlersForEvent(String event, boolean activeOnly) {\n        if (activeOnly) {\n            return getAllEventHandlers().stream()\n                    .filter(eventHandler -> eventHandler.getEvent().equals(event))\n                    .filter(EventHandler::isActive)\n                    .collect(Collectors.toList());\n        } else {\n            return getAllEventHandlers().stream()\n                    .filter(eventHandler -> eventHandler.getEvent().equals(event))\n                    .collect(Collectors.toList());\n        }\n    }\n\n    private List<EventHandler> refreshEventHandlersCache() {\n        try {\n            Cache eventHandlersCache = cacheManager.getCache(EVENT_HANDLER_CACHE);\n            eventHandlersCache.clear();\n            List<EventHandler> eventHandlers = cassandraEventHandlerDAO.getAllEventHandlers();\n            eventHandlers.forEach(\n                    eventHandler -> eventHandlersCache.put(eventHandler.getName(), eventHandler));\n            LOGGER.debug(\"Refreshed event handlers, total num: \" + eventHandlers.size());\n            return eventHandlers;\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"refreshEventHandlersCache\");\n            LOGGER.error(\"refresh EventHandlers failed\", e);\n        }\n        return Collections.emptyList();\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/cache/CacheableMetadataDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.config.cache;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.annotation.PostConstruct;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.cache.Cache;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.CachePut;\nimport org.springframework.cache.annotation.Cacheable;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.cassandra.config.CassandraProperties;\nimport com.netflix.conductor.cassandra.dao.CassandraMetadataDAO;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport static com.netflix.conductor.cassandra.config.cache.CachingConfig.TASK_DEF_CACHE;\n\n@Trace\npublic class CacheableMetadataDAO implements MetadataDAO {\n\n    private static final String CLASS_NAME = CacheableMetadataDAO.class.getSimpleName();\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CacheableMetadataDAO.class);\n\n    private final CassandraMetadataDAO cassandraMetadataDAO;\n    private final CassandraProperties properties;\n\n    private final CacheManager cacheManager;\n\n    public CacheableMetadataDAO(\n            CassandraMetadataDAO cassandraMetadataDAO,\n            CassandraProperties properties,\n            CacheManager cacheManager) {\n        this.cassandraMetadataDAO = cassandraMetadataDAO;\n        this.properties = properties;\n        this.cacheManager = cacheManager;\n    }\n\n    @PostConstruct\n    public void scheduleCacheRefresh() {\n        long cacheRefreshTime = properties.getTaskDefCacheRefreshInterval().getSeconds();\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleWithFixedDelay(\n                        this::refreshTaskDefsCache, 0, cacheRefreshTime, TimeUnit.SECONDS);\n        LOGGER.info(\n                \"Scheduled cache refresh for Task Definitions, every {} seconds\", cacheRefreshTime);\n    }\n\n    @Override\n    @CachePut(value = TASK_DEF_CACHE, key = \"#taskDef.name\")\n    public TaskDef createTaskDef(TaskDef taskDef) {\n        cassandraMetadataDAO.createTaskDef(taskDef);\n        return taskDef;\n    }\n\n    @Override\n    @CachePut(value = TASK_DEF_CACHE, key = \"#taskDef.name\")\n    public TaskDef updateTaskDef(TaskDef taskDef) {\n        return cassandraMetadataDAO.updateTaskDef(taskDef);\n    }\n\n    @Override\n    @Cacheable(TASK_DEF_CACHE)\n    public TaskDef getTaskDef(String name) {\n        return cassandraMetadataDAO.getTaskDef(name);\n    }\n\n    @Override\n    public List<TaskDef> getAllTaskDefs() {\n        Object nativeCache = cacheManager.getCache(TASK_DEF_CACHE).getNativeCache();\n        if (nativeCache != null && nativeCache instanceof ConcurrentHashMap) {\n            ConcurrentHashMap cacheMap = (ConcurrentHashMap) nativeCache;\n            if (!cacheMap.isEmpty()) {\n                List<TaskDef> taskDefs = new ArrayList<>();\n                cacheMap.values().stream()\n                        .filter(element -> element != null && element instanceof TaskDef)\n                        .forEach(element -> taskDefs.add((TaskDef) element));\n                return taskDefs;\n            }\n        }\n\n        return refreshTaskDefsCache();\n    }\n\n    @Override\n    @CacheEvict(TASK_DEF_CACHE)\n    public void removeTaskDef(String name) {\n        cassandraMetadataDAO.removeTaskDef(name);\n    }\n\n    @Override\n    public void createWorkflowDef(WorkflowDef workflowDef) {\n        cassandraMetadataDAO.createWorkflowDef(workflowDef);\n    }\n\n    @Override\n    public void updateWorkflowDef(WorkflowDef workflowDef) {\n        cassandraMetadataDAO.updateWorkflowDef(workflowDef);\n    }\n\n    @Override\n    public Optional<WorkflowDef> getLatestWorkflowDef(String name) {\n        return cassandraMetadataDAO.getLatestWorkflowDef(name);\n    }\n\n    @Override\n    public Optional<WorkflowDef> getWorkflowDef(String name, int version) {\n        return cassandraMetadataDAO.getWorkflowDef(name, version);\n    }\n\n    @Override\n    public void removeWorkflowDef(String name, Integer version) {\n        cassandraMetadataDAO.removeWorkflowDef(name, version);\n    }\n\n    @Override\n    public List<WorkflowDef> getAllWorkflowDefs() {\n        return cassandraMetadataDAO.getAllWorkflowDefs();\n    }\n\n    @Override\n    public List<WorkflowDef> getAllWorkflowDefsLatestVersions() {\n        return cassandraMetadataDAO.getAllWorkflowDefsLatestVersions();\n    }\n\n    private List<TaskDef> refreshTaskDefsCache() {\n        try {\n            Cache taskDefsCache = cacheManager.getCache(TASK_DEF_CACHE);\n            taskDefsCache.clear();\n            List<TaskDef> taskDefs = cassandraMetadataDAO.getAllTaskDefs();\n            taskDefs.forEach(taskDef -> taskDefsCache.put(taskDef.getName(), taskDef));\n            LOGGER.debug(\"Refreshed task defs, total num: \" + taskDefs.size());\n            return taskDefs;\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"refreshTaskDefs\");\n            LOGGER.error(\"refresh TaskDefs failed \", e);\n        }\n        return Collections.emptyList();\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/cache/CachingConfig.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.config.cache;\n\nimport org.springframework.cache.CacheManager;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.cache.concurrent.ConcurrentMapCacheManager;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\n@EnableCaching\npublic class CachingConfig {\n    public static final String TASK_DEF_CACHE = \"taskDefCache\";\n    public static final String EVENT_HANDLER_CACHE = \"eventHandlerCache\";\n\n    @Bean\n    public CacheManager cacheManager() {\n        return new ConcurrentMapCacheManager(TASK_DEF_CACHE, EVENT_HANDLER_CACHE);\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/dao/CassandraBaseDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao;\n\nimport java.io.IOException;\nimport java.util.UUID;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.cassandra.config.CassandraProperties;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport com.datastax.driver.core.DataType;\nimport com.datastax.driver.core.Session;\nimport com.datastax.driver.core.schemabuilder.SchemaBuilder;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.collect.ImmutableMap;\n\nimport static com.netflix.conductor.cassandra.util.Constants.DAO_NAME;\nimport static com.netflix.conductor.cassandra.util.Constants.ENTITY_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.EVENT_EXECUTION_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_NAME_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.HANDLERS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.MESSAGE_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.PAYLOAD_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.SHARD_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_EXECUTIONS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_HANDLERS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEFS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEF_LIMIT;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_LOOKUP;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOWS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS_INDEX;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEFINITION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEFS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEF_NAME_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TOTAL_PARTITIONS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TOTAL_TASKS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEFINITION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_VALUE;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_VERSION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_VERSION_KEY;\n\n/**\n * Creates the keyspace and tables.\n *\n * <p>CREATE KEYSPACE IF NOT EXISTS conductor WITH replication = { 'class' :\n * 'NetworkTopologyStrategy', 'us-east': '3'};\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.workflows ( workflow_id uuid, shard_id int, task_id text,\n * entity text, payload text, total_tasks int STATIC, total_partitions int STATIC, PRIMARY\n * KEY((workflow_id, shard_id), entity, task_id) );\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.task_lookup( task_id uuid, workflow_id uuid, PRIMARY KEY\n * (task_id) );\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.task_def_limit( task_def_name text, task_id uuid,\n * workflow_id uuid, PRIMARY KEY ((task_def_name), task_id_key) );\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.workflow_definitions( workflow_def_name text, version\n * int, workflow_definition text, PRIMARY KEY ((workflow_def_name), version) );\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.workflow_defs_index( workflow_def_version_index text,\n * workflow_def_name_version text, workflow_def_index_value text,PRIMARY KEY\n * ((workflow_def_version_index), workflow_def_name_version) );\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.task_definitions( task_defs text, task_def_name text,\n * task_definition text, PRIMARY KEY ((task_defs), task_def_name) );\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.event_handlers( handlers text, event_handler_name text,\n * event_handler text, PRIMARY KEY ((handlers), event_handler_name) );\n *\n * <p>CREATE TABLE IF NOT EXISTS conductor.event_executions( message_id text, event_handler_name\n * text, event_execution_id text, payload text, PRIMARY KEY ((message_id, event_handler_name),\n * event_execution_id) );\n */\npublic abstract class CassandraBaseDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CassandraBaseDAO.class);\n\n    private final ObjectMapper objectMapper;\n    protected final Session session;\n    protected final CassandraProperties properties;\n\n    private boolean initialized = false;\n\n    public CassandraBaseDAO(\n            Session session, ObjectMapper objectMapper, CassandraProperties properties) {\n        this.session = session;\n        this.objectMapper = objectMapper;\n        this.properties = properties;\n\n        init();\n    }\n\n    protected static UUID toUUID(String uuidString, String message) {\n        try {\n            return UUID.fromString(uuidString);\n        } catch (IllegalArgumentException iae) {\n            throw new IllegalArgumentException(message + \" \" + uuidString, iae);\n        }\n    }\n\n    private void init() {\n        try {\n            if (!initialized) {\n                session.execute(getCreateKeyspaceStatement());\n                session.execute(getCreateWorkflowsTableStatement());\n                session.execute(getCreateTaskLookupTableStatement());\n                session.execute(getCreateTaskDefLimitTableStatement());\n                session.execute(getCreateWorkflowDefsTableStatement());\n                session.execute(getCreateWorkflowDefsIndexTableStatement());\n                session.execute(getCreateTaskDefsTableStatement());\n                session.execute(getCreateEventHandlersTableStatement());\n                session.execute(getCreateEventExecutionsTableStatement());\n                LOGGER.info(\n                        \"{} initialization complete! Tables created!\", getClass().getSimpleName());\n                initialized = true;\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"Error initializing and setting up keyspace and table in cassandra\", e);\n            throw e;\n        }\n    }\n\n    private String getCreateKeyspaceStatement() {\n        return SchemaBuilder.createKeyspace(properties.getKeyspace())\n                .ifNotExists()\n                .with()\n                .replication(\n                        ImmutableMap.of(\n                                \"class\",\n                                properties.getReplicationStrategy(),\n                                properties.getReplicationFactorKey(),\n                                properties.getReplicationFactorValue()))\n                .durableWrites(true)\n                .getQueryString();\n    }\n\n    private String getCreateWorkflowsTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_WORKFLOWS)\n                .ifNotExists()\n                .addPartitionKey(WORKFLOW_ID_KEY, DataType.uuid())\n                .addPartitionKey(SHARD_ID_KEY, DataType.cint())\n                .addClusteringColumn(ENTITY_KEY, DataType.text())\n                .addClusteringColumn(TASK_ID_KEY, DataType.text())\n                .addColumn(PAYLOAD_KEY, DataType.text())\n                .addStaticColumn(TOTAL_TASKS_KEY, DataType.cint())\n                .addStaticColumn(TOTAL_PARTITIONS_KEY, DataType.cint())\n                .getQueryString();\n    }\n\n    private String getCreateTaskLookupTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_TASK_LOOKUP)\n                .ifNotExists()\n                .addPartitionKey(TASK_ID_KEY, DataType.uuid())\n                .addColumn(WORKFLOW_ID_KEY, DataType.uuid())\n                .getQueryString();\n    }\n\n    private String getCreateTaskDefLimitTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_TASK_DEF_LIMIT)\n                .ifNotExists()\n                .addPartitionKey(TASK_DEF_NAME_KEY, DataType.text())\n                .addClusteringColumn(TASK_ID_KEY, DataType.uuid())\n                .addColumn(WORKFLOW_ID_KEY, DataType.uuid())\n                .getQueryString();\n    }\n\n    private String getCreateWorkflowDefsTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_WORKFLOW_DEFS)\n                .ifNotExists()\n                .addPartitionKey(WORKFLOW_DEF_NAME_KEY, DataType.text())\n                .addClusteringColumn(WORKFLOW_VERSION_KEY, DataType.cint())\n                .addColumn(WORKFLOW_DEFINITION_KEY, DataType.text())\n                .getQueryString();\n    }\n\n    private String getCreateWorkflowDefsIndexTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_WORKFLOW_DEFS_INDEX)\n                .ifNotExists()\n                .addPartitionKey(WORKFLOW_DEF_INDEX_KEY, DataType.text())\n                .addClusteringColumn(WORKFLOW_DEF_NAME_VERSION_KEY, DataType.text())\n                .addColumn(WORKFLOW_DEF_INDEX_VALUE, DataType.text())\n                .getQueryString();\n    }\n\n    private String getCreateTaskDefsTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_TASK_DEFS)\n                .ifNotExists()\n                .addPartitionKey(TASK_DEFS_KEY, DataType.text())\n                .addClusteringColumn(TASK_DEF_NAME_KEY, DataType.text())\n                .addColumn(TASK_DEFINITION_KEY, DataType.text())\n                .getQueryString();\n    }\n\n    private String getCreateEventHandlersTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_EVENT_HANDLERS)\n                .ifNotExists()\n                .addPartitionKey(HANDLERS_KEY, DataType.text())\n                .addClusteringColumn(EVENT_HANDLER_NAME_KEY, DataType.text())\n                .addColumn(EVENT_HANDLER_KEY, DataType.text())\n                .getQueryString();\n    }\n\n    private String getCreateEventExecutionsTableStatement() {\n        return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_EVENT_EXECUTIONS)\n                .ifNotExists()\n                .addPartitionKey(MESSAGE_ID_KEY, DataType.text())\n                .addPartitionKey(EVENT_HANDLER_NAME_KEY, DataType.text())\n                .addClusteringColumn(EVENT_EXECUTION_ID_KEY, DataType.text())\n                .addColumn(PAYLOAD_KEY, DataType.text())\n                .getQueryString();\n    }\n\n    String toJson(Object value) {\n        try {\n            return objectMapper.writeValueAsString(value);\n        } catch (JsonProcessingException e) {\n            throw new NonTransientException(\"Error serializing to json\", e);\n        }\n    }\n\n    <T> T readValue(String json, Class<T> clazz) {\n        try {\n            return objectMapper.readValue(json, clazz);\n        } catch (IOException e) {\n            throw new NonTransientException(\"Error de-serializing json\", e);\n        }\n    }\n\n    void recordCassandraDaoRequests(String action) {\n        recordCassandraDaoRequests(action, \"n/a\", \"n/a\");\n    }\n\n    void recordCassandraDaoRequests(String action, String taskType, String workflowType) {\n        Monitors.recordDaoRequests(DAO_NAME, action, taskType, workflowType);\n    }\n\n    void recordCassandraDaoEventRequests(String action, String event) {\n        Monitors.recordDaoEventRequests(DAO_NAME, action, event);\n    }\n\n    void recordCassandraDaoPayloadSize(\n            String action, int size, String taskType, String workflowType) {\n        Monitors.recordDaoPayloadSize(DAO_NAME, action, taskType, workflowType, size);\n    }\n\n    static class WorkflowMetadata {\n\n        private int totalTasks;\n        private int totalPartitions;\n\n        public int getTotalTasks() {\n            return totalTasks;\n        }\n\n        public void setTotalTasks(int totalTasks) {\n            this.totalTasks = totalTasks;\n        }\n\n        public int getTotalPartitions() {\n            return totalPartitions;\n        }\n\n        public void setTotalPartitions(int totalPartitions) {\n            this.totalPartitions = totalPartitions;\n        }\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/dao/CassandraEventHandlerDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.cassandra.config.CassandraProperties;\nimport com.netflix.conductor.cassandra.util.Statements;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.EventHandlerDAO;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport com.datastax.driver.core.PreparedStatement;\nimport com.datastax.driver.core.ResultSet;\nimport com.datastax.driver.core.Row;\nimport com.datastax.driver.core.Session;\nimport com.datastax.driver.core.exceptions.DriverException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.HANDLERS_KEY;\n\n@Trace\npublic class CassandraEventHandlerDAO extends CassandraBaseDAO implements EventHandlerDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CassandraEventHandlerDAO.class);\n    private static final String CLASS_NAME = CassandraEventHandlerDAO.class.getSimpleName();\n\n    private final PreparedStatement insertEventHandlerStatement;\n    private final PreparedStatement selectAllEventHandlersStatement;\n    private final PreparedStatement deleteEventHandlerStatement;\n\n    public CassandraEventHandlerDAO(\n            Session session,\n            ObjectMapper objectMapper,\n            CassandraProperties properties,\n            Statements statements) {\n        super(session, objectMapper, properties);\n\n        insertEventHandlerStatement =\n                session.prepare(statements.getInsertEventHandlerStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        selectAllEventHandlersStatement =\n                session.prepare(statements.getSelectAllEventHandlersStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        deleteEventHandlerStatement =\n                session.prepare(statements.getDeleteEventHandlerStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n    }\n\n    @Override\n    public void addEventHandler(EventHandler eventHandler) {\n        insertOrUpdateEventHandler(eventHandler);\n    }\n\n    @Override\n    public void updateEventHandler(EventHandler eventHandler) {\n        insertOrUpdateEventHandler(eventHandler);\n    }\n\n    @Override\n    public void removeEventHandler(String name) {\n        try {\n            recordCassandraDaoRequests(\"removeEventHandler\");\n            session.execute(deleteEventHandlerStatement.bind(name));\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"removeEventHandler\");\n            String errorMsg = String.format(\"Failed to remove event handler: %s\", name);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public List<EventHandler> getAllEventHandlers() {\n        return getAllEventHandlersFromDB();\n    }\n\n    @Override\n    public List<EventHandler> getEventHandlersForEvent(String event, boolean activeOnly) {\n        if (activeOnly) {\n            return getAllEventHandlers().stream()\n                    .filter(eventHandler -> eventHandler.getEvent().equals(event))\n                    .filter(EventHandler::isActive)\n                    .collect(Collectors.toList());\n        } else {\n            return getAllEventHandlers().stream()\n                    .filter(eventHandler -> eventHandler.getEvent().equals(event))\n                    .collect(Collectors.toList());\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private List<EventHandler> getAllEventHandlersFromDB() {\n        try {\n            ResultSet resultSet =\n                    session.execute(selectAllEventHandlersStatement.bind(HANDLERS_KEY));\n            List<Row> rows = resultSet.all();\n            if (rows.size() == 0) {\n                LOGGER.info(\"No event handlers were found.\");\n                return Collections.EMPTY_LIST;\n            }\n            return rows.stream()\n                    .map(row -> readValue(row.getString(EVENT_HANDLER_KEY), EventHandler.class))\n                    .collect(Collectors.toList());\n\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getAllEventHandlersFromDB\");\n            String errorMsg = \"Failed to get all event handlers\";\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    private void insertOrUpdateEventHandler(EventHandler eventHandler) {\n        try {\n            String handler = toJson(eventHandler);\n            session.execute(insertEventHandlerStatement.bind(eventHandler.getName(), handler));\n            recordCassandraDaoRequests(\"storeEventHandler\");\n            recordCassandraDaoPayloadSize(\"storeEventHandler\", handler.length(), \"n/a\", \"n/a\");\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"insertOrUpdateEventHandler\");\n            String errorMsg =\n                    String.format(\n                            \"Error creating/updating event handler: %s/%s\",\n                            eventHandler.getName(), eventHandler.getEvent());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/dao/CassandraExecutionDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.cassandra.config.CassandraProperties;\nimport com.netflix.conductor.cassandra.util.Statements;\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.ConcurrentExecutionLimitDAO;\nimport com.netflix.conductor.dao.ExecutionDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.datastax.driver.core.*;\nimport com.datastax.driver.core.exceptions.DriverException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\n\nimport static com.netflix.conductor.cassandra.util.Constants.*;\n\n@Trace\npublic class CassandraExecutionDAO extends CassandraBaseDAO\n        implements ExecutionDAO, ConcurrentExecutionLimitDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CassandraExecutionDAO.class);\n    private static final String CLASS_NAME = CassandraExecutionDAO.class.getSimpleName();\n\n    protected final PreparedStatement insertWorkflowStatement;\n    protected final PreparedStatement insertTaskStatement;\n    protected final PreparedStatement insertEventExecutionStatement;\n\n    protected final PreparedStatement selectTotalStatement;\n    protected final PreparedStatement selectTaskStatement;\n    protected final PreparedStatement selectWorkflowStatement;\n    protected final PreparedStatement selectWorkflowWithTasksStatement;\n    protected final PreparedStatement selectTaskLookupStatement;\n    protected final PreparedStatement selectTasksFromTaskDefLimitStatement;\n    protected final PreparedStatement selectEventExecutionsStatement;\n\n    protected final PreparedStatement updateWorkflowStatement;\n    protected final PreparedStatement updateTotalTasksStatement;\n    protected final PreparedStatement updateTotalPartitionsStatement;\n    protected final PreparedStatement updateTaskLookupStatement;\n    protected final PreparedStatement updateTaskDefLimitStatement;\n    protected final PreparedStatement updateEventExecutionStatement;\n\n    protected final PreparedStatement deleteWorkflowStatement;\n    protected final PreparedStatement deleteTaskStatement;\n    protected final PreparedStatement deleteTaskLookupStatement;\n    protected final PreparedStatement deleteTaskDefLimitStatement;\n    protected final PreparedStatement deleteEventExecutionStatement;\n\n    protected final int eventExecutionsTTL;\n\n    public CassandraExecutionDAO(\n            Session session,\n            ObjectMapper objectMapper,\n            CassandraProperties properties,\n            Statements statements) {\n        super(session, objectMapper, properties);\n\n        eventExecutionsTTL = (int) properties.getEventExecutionPersistenceTtl().getSeconds();\n\n        this.insertWorkflowStatement =\n                session.prepare(statements.getInsertWorkflowStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.insertTaskStatement =\n                session.prepare(statements.getInsertTaskStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.insertEventExecutionStatement =\n                session.prepare(statements.getInsertEventExecutionStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n\n        this.selectTotalStatement =\n                session.prepare(statements.getSelectTotalStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectTaskStatement =\n                session.prepare(statements.getSelectTaskStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectWorkflowStatement =\n                session.prepare(statements.getSelectWorkflowStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectWorkflowWithTasksStatement =\n                session.prepare(statements.getSelectWorkflowWithTasksStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectTaskLookupStatement =\n                session.prepare(statements.getSelectTaskFromLookupTableStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectTasksFromTaskDefLimitStatement =\n                session.prepare(statements.getSelectTasksFromTaskDefLimitStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectEventExecutionsStatement =\n                session.prepare(\n                                statements\n                                        .getSelectAllEventExecutionsForMessageFromEventExecutionsStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n\n        this.updateWorkflowStatement =\n                session.prepare(statements.getUpdateWorkflowStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.updateTotalTasksStatement =\n                session.prepare(statements.getUpdateTotalTasksStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.updateTotalPartitionsStatement =\n                session.prepare(statements.getUpdateTotalPartitionsStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.updateTaskLookupStatement =\n                session.prepare(statements.getUpdateTaskLookupStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.updateTaskDefLimitStatement =\n                session.prepare(statements.getUpdateTaskDefLimitStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.updateEventExecutionStatement =\n                session.prepare(statements.getUpdateEventExecutionStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n\n        this.deleteWorkflowStatement =\n                session.prepare(statements.getDeleteWorkflowStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.deleteTaskStatement =\n                session.prepare(statements.getDeleteTaskStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.deleteTaskLookupStatement =\n                session.prepare(statements.getDeleteTaskLookupStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.deleteTaskDefLimitStatement =\n                session.prepare(statements.getDeleteTaskDefLimitStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.deleteEventExecutionStatement =\n                session.prepare(statements.getDeleteEventExecutionsStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n    }\n\n    @Override\n    public List<TaskModel> getPendingTasksByWorkflow(String taskName, String workflowId) {\n        List<TaskModel> tasks = getTasksForWorkflow(workflowId);\n        return tasks.stream()\n                .filter(task -> taskName.equals(task.getTaskType()))\n                .filter(task -> TaskModel.Status.IN_PROGRESS.equals(task.getStatus()))\n                .collect(Collectors.toList());\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public List<TaskModel> getTasks(String taskType, String startKey, int count) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    /**\n     * Inserts tasks into the Cassandra datastore. <b>Note:</b> Creates the task_id to workflow_id\n     * mapping in the task_lookup table first. Once this succeeds, inserts the tasks into the\n     * workflows table. Tasks belonging to the same shard are created using batch statements.\n     *\n     * @param tasks tasks to be created\n     */\n    @Override\n    public List<TaskModel> createTasks(List<TaskModel> tasks) {\n        validateTasks(tasks);\n        String workflowId = tasks.get(0).getWorkflowInstanceId();\n        UUID workflowUUID = toUUID(workflowId, \"Invalid workflow id\");\n        try {\n            WorkflowMetadata workflowMetadata = getWorkflowMetadata(workflowId);\n            int totalTasks = workflowMetadata.getTotalTasks() + tasks.size();\n            // TODO: write into multiple shards based on number of tasks\n\n            // update the task_lookup table\n            tasks.forEach(\n                    task -> {\n                        if (task.getScheduledTime() == 0) {\n                            task.setScheduledTime(System.currentTimeMillis());\n                        }\n                        session.execute(\n                                updateTaskLookupStatement.bind(\n                                        workflowUUID, toUUID(task.getTaskId(), \"Invalid task id\")));\n                    });\n\n            // update all the tasks in the workflow using batch\n            BatchStatement batchStatement = new BatchStatement();\n            tasks.forEach(\n                    task -> {\n                        String taskPayload = toJson(task);\n                        batchStatement.add(\n                                insertTaskStatement.bind(\n                                        workflowUUID,\n                                        DEFAULT_SHARD_ID,\n                                        task.getTaskId(),\n                                        taskPayload));\n                        recordCassandraDaoRequests(\n                                \"createTask\", task.getTaskType(), task.getWorkflowType());\n                        recordCassandraDaoPayloadSize(\n                                \"createTask\",\n                                taskPayload.length(),\n                                task.getTaskType(),\n                                task.getWorkflowType());\n                    });\n            batchStatement.add(\n                    updateTotalTasksStatement.bind(totalTasks, workflowUUID, DEFAULT_SHARD_ID));\n            session.execute(batchStatement);\n\n            // update the total tasks and partitions for the workflow\n            session.execute(\n                    updateTotalPartitionsStatement.bind(\n                            DEFAULT_TOTAL_PARTITIONS, totalTasks, workflowUUID));\n\n            return tasks;\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"createTasks\");\n            String errorMsg =\n                    String.format(\n                            \"Error creating %d tasks for workflow: %s\", tasks.size(), workflowId);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public void updateTask(TaskModel task) {\n        try {\n            // TODO: calculate the shard number the task belongs to\n            String taskPayload = toJson(task);\n            recordCassandraDaoRequests(\"updateTask\", task.getTaskType(), task.getWorkflowType());\n            recordCassandraDaoPayloadSize(\n                    \"updateTask\", taskPayload.length(), task.getTaskType(), task.getWorkflowType());\n            session.execute(\n                    insertTaskStatement.bind(\n                            UUID.fromString(task.getWorkflowInstanceId()),\n                            DEFAULT_SHARD_ID,\n                            task.getTaskId(),\n                            taskPayload));\n            if (task.getTaskDefinition().isPresent()\n                    && task.getTaskDefinition().get().concurrencyLimit() > 0) {\n                if (task.getStatus().isTerminal()) {\n                    removeTaskFromLimit(task);\n                } else if (task.getStatus() == TaskModel.Status.IN_PROGRESS) {\n                    addTaskToLimit(task);\n                }\n            }\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"updateTask\");\n            String errorMsg =\n                    String.format(\n                            \"Error updating task: %s in workflow: %s\",\n                            task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public boolean exceedsLimit(TaskModel task) {\n        Optional<TaskDef> taskDefinition = task.getTaskDefinition();\n        if (taskDefinition.isEmpty()) {\n            return false;\n        }\n        int limit = taskDefinition.get().concurrencyLimit();\n        if (limit <= 0) {\n            return false;\n        }\n\n        try {\n            recordCassandraDaoRequests(\n                    \"selectTaskDefLimit\", task.getTaskType(), task.getWorkflowType());\n            ResultSet resultSet =\n                    session.execute(\n                            selectTasksFromTaskDefLimitStatement.bind(task.getTaskDefName()));\n            List<String> taskIds =\n                    resultSet.all().stream()\n                            .map(row -> row.getUUID(TASK_ID_KEY).toString())\n                            .collect(Collectors.toList());\n            long current = taskIds.size();\n\n            if (!taskIds.contains(task.getTaskId()) && current >= limit) {\n                LOGGER.info(\n                        \"Task execution count limited. task - {}:{}, limit: {}, current: {}\",\n                        task.getTaskId(),\n                        task.getTaskDefName(),\n                        limit,\n                        current);\n                Monitors.recordTaskConcurrentExecutionLimited(task.getTaskDefName(), limit);\n                return true;\n            }\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"exceedsLimit\");\n            String errorMsg =\n                    String.format(\n                            \"Failed to get in progress limit - %s:%s in workflow :%s\",\n                            task.getTaskDefName(), task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n        return false;\n    }\n\n    @Override\n    public boolean removeTask(String taskId) {\n        TaskModel task = getTask(taskId);\n        if (task == null) {\n            LOGGER.warn(\"No such task found by id {}\", taskId);\n            return false;\n        }\n        return removeTask(task);\n    }\n\n    @Override\n    public TaskModel getTask(String taskId) {\n        try {\n            String workflowId = lookupWorkflowIdFromTaskId(taskId);\n            if (workflowId == null) {\n                return null;\n            }\n            // TODO: implement for query against multiple shards\n\n            ResultSet resultSet =\n                    session.execute(\n                            selectTaskStatement.bind(\n                                    UUID.fromString(workflowId), DEFAULT_SHARD_ID, taskId));\n            return Optional.ofNullable(resultSet.one())\n                    .map(\n                            row -> {\n                                String taskRow = row.getString(PAYLOAD_KEY);\n                                TaskModel task = readValue(taskRow, TaskModel.class);\n                                recordCassandraDaoRequests(\n                                        \"getTask\", task.getTaskType(), task.getWorkflowType());\n                                recordCassandraDaoPayloadSize(\n                                        \"getTask\",\n                                        taskRow.length(),\n                                        task.getTaskType(),\n                                        task.getWorkflowType());\n                                return task;\n                            })\n                    .orElse(null);\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getTask\");\n            String errorMsg = String.format(\"Error getting task by id: %s\", taskId);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    @Override\n    public List<TaskModel> getTasks(List<String> taskIds) {\n        Preconditions.checkNotNull(taskIds);\n        Preconditions.checkArgument(taskIds.size() > 0, \"Task ids list cannot be empty\");\n        String workflowId = lookupWorkflowIdFromTaskId(taskIds.get(0));\n        if (workflowId == null) {\n            return null;\n        }\n        return getWorkflow(workflowId, true).getTasks().stream()\n                .filter(task -> taskIds.contains(task.getTaskId()))\n                .collect(Collectors.toList());\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public List<TaskModel> getPendingTasksForTaskType(String taskType) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    @Override\n    public List<TaskModel> getTasksForWorkflow(String workflowId) {\n        return getWorkflow(workflowId, true).getTasks();\n    }\n\n    @Override\n    public String createWorkflow(WorkflowModel workflow) {\n        try {\n            List<TaskModel> tasks = workflow.getTasks();\n            workflow.setTasks(new LinkedList<>());\n            String payload = toJson(workflow);\n\n            recordCassandraDaoRequests(\"createWorkflow\", \"n/a\", workflow.getWorkflowName());\n            recordCassandraDaoPayloadSize(\n                    \"createWorkflow\", payload.length(), \"n/a\", workflow.getWorkflowName());\n            session.execute(\n                    insertWorkflowStatement.bind(\n                            UUID.fromString(workflow.getWorkflowId()), 1, \"\", payload, 0, 1));\n\n            workflow.setTasks(tasks);\n            return workflow.getWorkflowId();\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"createWorkflow\");\n            String errorMsg =\n                    String.format(\"Error creating workflow: %s\", workflow.getWorkflowId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public String updateWorkflow(WorkflowModel workflow) {\n        try {\n            List<TaskModel> tasks = workflow.getTasks();\n            workflow.setTasks(new LinkedList<>());\n            String payload = toJson(workflow);\n            recordCassandraDaoRequests(\"updateWorkflow\", \"n/a\", workflow.getWorkflowName());\n            recordCassandraDaoPayloadSize(\n                    \"updateWorkflow\", payload.length(), \"n/a\", workflow.getWorkflowName());\n            session.execute(\n                    updateWorkflowStatement.bind(\n                            payload, UUID.fromString(workflow.getWorkflowId())));\n            workflow.setTasks(tasks);\n            return workflow.getWorkflowId();\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"updateWorkflow\");\n            String errorMsg =\n                    String.format(\"Failed to update workflow: %s\", workflow.getWorkflowId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    @Override\n    public boolean removeWorkflow(String workflowId) {\n        WorkflowModel workflow = getWorkflow(workflowId, true);\n        boolean removed = false;\n        // TODO: calculate number of shards and iterate\n        if (workflow != null) {\n            try {\n                recordCassandraDaoRequests(\"removeWorkflow\", \"n/a\", workflow.getWorkflowName());\n                ResultSet resultSet =\n                        session.execute(\n                                deleteWorkflowStatement.bind(\n                                        UUID.fromString(workflowId), DEFAULT_SHARD_ID));\n                removed = resultSet.wasApplied();\n            } catch (DriverException e) {\n                Monitors.error(CLASS_NAME, \"removeWorkflow\");\n                String errorMsg = String.format(\"Failed to remove workflow: %s\", workflowId);\n                LOGGER.error(errorMsg, e);\n                throw new TransientException(errorMsg);\n            }\n            workflow.getTasks().forEach(this::removeTaskLookup);\n        }\n        return removed;\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not yet implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public boolean removeWorkflowWithExpiry(String workflowId, int ttlSeconds) {\n        throw new UnsupportedOperationException(\n                \"This method is not currently implemented in CassandraExecutionDAO. Please use RedisDAO mode instead now for using TTLs.\");\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public void removeFromPendingWorkflow(String workflowType, String workflowId) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    @Override\n    public WorkflowModel getWorkflow(String workflowId) {\n        return getWorkflow(workflowId, true);\n    }\n\n    @Override\n    public WorkflowModel getWorkflow(String workflowId, boolean includeTasks) {\n        UUID workflowUUID = toUUID(workflowId, \"Invalid workflow id\");\n        try {\n            WorkflowModel workflow = null;\n            ResultSet resultSet;\n            if (includeTasks) {\n                resultSet =\n                        session.execute(\n                                selectWorkflowWithTasksStatement.bind(\n                                        workflowUUID, DEFAULT_SHARD_ID));\n                List<TaskModel> tasks = new ArrayList<>();\n\n                List<Row> rows = resultSet.all();\n                if (rows.size() == 0) {\n                    LOGGER.info(\"Workflow {} not found in datastore\", workflowId);\n                    return null;\n                }\n                for (Row row : rows) {\n                    String entityKey = row.getString(ENTITY_KEY);\n                    if (ENTITY_TYPE_WORKFLOW.equals(entityKey)) {\n                        workflow = readValue(row.getString(PAYLOAD_KEY), WorkflowModel.class);\n                    } else if (ENTITY_TYPE_TASK.equals(entityKey)) {\n                        TaskModel task = readValue(row.getString(PAYLOAD_KEY), TaskModel.class);\n                        tasks.add(task);\n                    } else {\n                        throw new NonTransientException(\n                                String.format(\n                                        \"Invalid row with entityKey: %s found in datastore for workflow: %s\",\n                                        entityKey, workflowId));\n                    }\n                }\n\n                if (workflow != null) {\n                    recordCassandraDaoRequests(\"getWorkflow\", \"n/a\", workflow.getWorkflowName());\n                    tasks.sort(Comparator.comparingInt(TaskModel::getSeq));\n                    workflow.setTasks(tasks);\n                }\n            } else {\n                resultSet = session.execute(selectWorkflowStatement.bind(workflowUUID));\n                workflow =\n                        Optional.ofNullable(resultSet.one())\n                                .map(\n                                        row -> {\n                                            WorkflowModel wf =\n                                                    readValue(\n                                                            row.getString(PAYLOAD_KEY),\n                                                            WorkflowModel.class);\n                                            recordCassandraDaoRequests(\n                                                    \"getWorkflow\", \"n/a\", wf.getWorkflowName());\n                                            return wf;\n                                        })\n                                .orElse(null);\n            }\n            return workflow;\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getWorkflow\");\n            String errorMsg = String.format(\"Failed to get workflow: %s\", workflowId);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public List<String> getRunningWorkflowIds(String workflowName, int version) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public List<WorkflowModel> getPendingWorkflowsByType(String workflowName, int version) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public long getPendingWorkflowCount(String workflowName) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public long getInProgressTaskCount(String taskDefName) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public List<WorkflowModel> getWorkflowsByType(\n            String workflowName, Long startTime, Long endTime) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    /**\n     * This is a dummy implementation and this feature is not implemented for Cassandra backed\n     * Conductor\n     */\n    @Override\n    public List<WorkflowModel> getWorkflowsByCorrelationId(\n            String workflowName, String correlationId, boolean includeTasks) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    @Override\n    public boolean canSearchAcrossWorkflows() {\n        return false;\n    }\n\n    @Override\n    public boolean addEventExecution(EventExecution eventExecution) {\n        try {\n            String jsonPayload = toJson(eventExecution);\n            recordCassandraDaoEventRequests(\"addEventExecution\", eventExecution.getEvent());\n            recordCassandraDaoPayloadSize(\n                    \"addEventExecution\", jsonPayload.length(), eventExecution.getEvent(), \"n/a\");\n            return session.execute(\n                            insertEventExecutionStatement.bind(\n                                    eventExecution.getMessageId(),\n                                    eventExecution.getName(),\n                                    eventExecution.getId(),\n                                    jsonPayload))\n                    .wasApplied();\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"addEventExecution\");\n            String errorMsg =\n                    String.format(\n                            \"Failed to add event execution for event: %s, handler: %s\",\n                            eventExecution.getEvent(), eventExecution.getName());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    @Override\n    public void updateEventExecution(EventExecution eventExecution) {\n        try {\n            String jsonPayload = toJson(eventExecution);\n            recordCassandraDaoEventRequests(\"updateEventExecution\", eventExecution.getEvent());\n            recordCassandraDaoPayloadSize(\n                    \"updateEventExecution\", jsonPayload.length(), eventExecution.getEvent(), \"n/a\");\n            session.execute(\n                    updateEventExecutionStatement.bind(\n                            eventExecutionsTTL,\n                            jsonPayload,\n                            eventExecution.getMessageId(),\n                            eventExecution.getName(),\n                            eventExecution.getId()));\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"updateEventExecution\");\n            String errorMsg =\n                    String.format(\n                            \"Failed to update event execution for event: %s, handler: %s\",\n                            eventExecution.getEvent(), eventExecution.getName());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    @Override\n    public void removeEventExecution(EventExecution eventExecution) {\n        try {\n            recordCassandraDaoEventRequests(\"removeEventExecution\", eventExecution.getEvent());\n            session.execute(\n                    deleteEventExecutionStatement.bind(\n                            eventExecution.getMessageId(),\n                            eventExecution.getName(),\n                            eventExecution.getId()));\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"removeEventExecution\");\n            String errorMsg =\n                    String.format(\n                            \"Failed to remove event execution for event: %s, handler: %s\",\n                            eventExecution.getEvent(), eventExecution.getName());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    @VisibleForTesting\n    List<EventExecution> getEventExecutions(\n            String eventHandlerName, String eventName, String messageId) {\n        try {\n            return session\n                    .execute(selectEventExecutionsStatement.bind(messageId, eventHandlerName))\n                    .all()\n                    .stream()\n                    .filter(row -> !row.isNull(PAYLOAD_KEY))\n                    .map(row -> readValue(row.getString(PAYLOAD_KEY), EventExecution.class))\n                    .collect(Collectors.toList());\n        } catch (DriverException e) {\n            String errorMsg =\n                    String.format(\n                            \"Failed to fetch event executions for event: %s, handler: %s\",\n                            eventName, eventHandlerName);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    @Override\n    public void addTaskToLimit(TaskModel task) {\n        try {\n            recordCassandraDaoRequests(\n                    \"addTaskToLimit\", task.getTaskType(), task.getWorkflowType());\n            session.execute(\n                    updateTaskDefLimitStatement.bind(\n                            UUID.fromString(task.getWorkflowInstanceId()),\n                            task.getTaskDefName(),\n                            UUID.fromString(task.getTaskId())));\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"addTaskToLimit\");\n            String errorMsg =\n                    String.format(\n                            \"Error updating taskDefLimit for task - %s:%s in workflow: %s\",\n                            task.getTaskDefName(), task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public void removeTaskFromLimit(TaskModel task) {\n        try {\n            recordCassandraDaoRequests(\n                    \"removeTaskFromLimit\", task.getTaskType(), task.getWorkflowType());\n            session.execute(\n                    deleteTaskDefLimitStatement.bind(\n                            task.getTaskDefName(), UUID.fromString(task.getTaskId())));\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"removeTaskFromLimit\");\n            String errorMsg =\n                    String.format(\n                            \"Error updating taskDefLimit for task - %s:%s in workflow: %s\",\n                            task.getTaskDefName(), task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    protected boolean removeTask(TaskModel task) {\n        // TODO: calculate shard number based on seq and maxTasksPerShard\n        try {\n            // get total tasks for this workflow\n            WorkflowMetadata workflowMetadata = getWorkflowMetadata(task.getWorkflowInstanceId());\n            int totalTasks = workflowMetadata.getTotalTasks();\n\n            // remove from task_lookup table\n            removeTaskLookup(task);\n\n            recordCassandraDaoRequests(\"removeTask\", task.getTaskType(), task.getWorkflowType());\n            // delete task from workflows table and decrement total tasks by 1\n            BatchStatement batchStatement = new BatchStatement();\n            batchStatement.add(\n                    deleteTaskStatement.bind(\n                            UUID.fromString(task.getWorkflowInstanceId()),\n                            DEFAULT_SHARD_ID,\n                            task.getTaskId()));\n            batchStatement.add(\n                    updateTotalTasksStatement.bind(\n                            totalTasks - 1,\n                            UUID.fromString(task.getWorkflowInstanceId()),\n                            DEFAULT_SHARD_ID));\n            ResultSet resultSet = session.execute(batchStatement);\n            if (task.getTaskDefinition().isPresent()\n                    && task.getTaskDefinition().get().concurrencyLimit() > 0) {\n                removeTaskFromLimit(task);\n            }\n            return resultSet.wasApplied();\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"removeTask\");\n            String errorMsg = String.format(\"Failed to remove task: %s\", task.getTaskId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    protected void removeTaskLookup(TaskModel task) {\n        try {\n            recordCassandraDaoRequests(\n                    \"removeTaskLookup\", task.getTaskType(), task.getWorkflowType());\n            if (task.getTaskDefinition().isPresent()\n                    && task.getTaskDefinition().get().concurrencyLimit() > 0) {\n                removeTaskFromLimit(task);\n            }\n            session.execute(deleteTaskLookupStatement.bind(UUID.fromString(task.getTaskId())));\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"removeTaskLookup\");\n            String errorMsg = String.format(\"Failed to remove task lookup: %s\", task.getTaskId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    @VisibleForTesting\n    void validateTasks(List<TaskModel> tasks) {\n        Preconditions.checkNotNull(tasks, \"Tasks object cannot be null\");\n        Preconditions.checkArgument(!tasks.isEmpty(), \"Tasks object cannot be empty\");\n        tasks.forEach(\n                task -> {\n                    Preconditions.checkNotNull(task, \"task object cannot be null\");\n                    Preconditions.checkNotNull(task.getTaskId(), \"Task id cannot be null\");\n                    Preconditions.checkNotNull(\n                            task.getWorkflowInstanceId(), \"Workflow instance id cannot be null\");\n                    Preconditions.checkNotNull(\n                            task.getReferenceTaskName(), \"Task reference name cannot be null\");\n                });\n\n        String workflowId = tasks.get(0).getWorkflowInstanceId();\n        Optional<TaskModel> optionalTask =\n                tasks.stream()\n                        .filter(task -> !workflowId.equals(task.getWorkflowInstanceId()))\n                        .findAny();\n        if (optionalTask.isPresent()) {\n            throw new NonTransientException(\n                    \"Tasks of multiple workflows cannot be created/updated simultaneously\");\n        }\n    }\n\n    @VisibleForTesting\n    WorkflowMetadata getWorkflowMetadata(String workflowId) {\n        ResultSet resultSet =\n                session.execute(selectTotalStatement.bind(UUID.fromString(workflowId)));\n        recordCassandraDaoRequests(\"getWorkflowMetadata\");\n        return Optional.ofNullable(resultSet.one())\n                .map(\n                        row -> {\n                            WorkflowMetadata workflowMetadata = new WorkflowMetadata();\n                            workflowMetadata.setTotalTasks(row.getInt(TOTAL_TASKS_KEY));\n                            workflowMetadata.setTotalPartitions(row.getInt(TOTAL_PARTITIONS_KEY));\n                            return workflowMetadata;\n                        })\n                .orElseThrow(\n                        () ->\n                                new NotFoundException(\n                                        \"Workflow with id: %s not found in data store\",\n                                        workflowId));\n    }\n\n    @VisibleForTesting\n    String lookupWorkflowIdFromTaskId(String taskId) {\n        UUID taskUUID = toUUID(taskId, \"Invalid task id\");\n        try {\n            ResultSet resultSet = session.execute(selectTaskLookupStatement.bind(taskUUID));\n            return Optional.ofNullable(resultSet.one())\n                    .map(row -> row.getUUID(WORKFLOW_ID_KEY).toString())\n                    .orElse(null);\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"lookupWorkflowIdFromTaskId\");\n            String errorMsg = String.format(\"Failed to lookup workflowId from taskId: %s\", taskId);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/dao/CassandraMetadataDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao;\n\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.PriorityQueue;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.cassandra.config.CassandraProperties;\nimport com.netflix.conductor.cassandra.util.Statements;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.exception.ConflictException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport com.datastax.driver.core.PreparedStatement;\nimport com.datastax.driver.core.ResultSet;\nimport com.datastax.driver.core.Row;\nimport com.datastax.driver.core.Session;\nimport com.datastax.driver.core.exceptions.DriverException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEFINITION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEFS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEFINITION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_VERSION_KEY;\nimport static com.netflix.conductor.common.metadata.tasks.TaskDef.ONE_HOUR;\n\n@Trace\npublic class CassandraMetadataDAO extends CassandraBaseDAO implements MetadataDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CassandraMetadataDAO.class);\n    private static final String CLASS_NAME = CassandraMetadataDAO.class.getSimpleName();\n    private static final String INDEX_DELIMITER = \"/\";\n\n    private final PreparedStatement insertWorkflowDefStatement;\n    private final PreparedStatement insertWorkflowDefVersionIndexStatement;\n    private final PreparedStatement insertTaskDefStatement;\n\n    private final PreparedStatement selectWorkflowDefStatement;\n\n    private final PreparedStatement selectAllWorkflowDefVersionsByNameStatement;\n    private final PreparedStatement selectAllWorkflowDefsStatement;\n    private final PreparedStatement selectAllWorkflowDefsLatestVersionsStatement;\n    private final PreparedStatement selectTaskDefStatement;\n    private final PreparedStatement selectAllTaskDefsStatement;\n\n    private final PreparedStatement updateWorkflowDefStatement;\n\n    private final PreparedStatement deleteWorkflowDefStatement;\n    private final PreparedStatement deleteWorkflowDefIndexStatement;\n    private final PreparedStatement deleteTaskDefStatement;\n\n    public CassandraMetadataDAO(\n            Session session,\n            ObjectMapper objectMapper,\n            CassandraProperties properties,\n            Statements statements) {\n        super(session, objectMapper, properties);\n\n        this.insertWorkflowDefStatement =\n                session.prepare(statements.getInsertWorkflowDefStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.insertWorkflowDefVersionIndexStatement =\n                session.prepare(statements.getInsertWorkflowDefVersionIndexStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.insertTaskDefStatement =\n                session.prepare(statements.getInsertTaskDefStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n\n        this.selectWorkflowDefStatement =\n                session.prepare(statements.getSelectWorkflowDefStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectAllWorkflowDefVersionsByNameStatement =\n                session.prepare(statements.getSelectAllWorkflowDefVersionsByNameStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectAllWorkflowDefsStatement =\n                session.prepare(statements.getSelectAllWorkflowDefsStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectAllWorkflowDefsLatestVersionsStatement =\n                session.prepare(statements.getSelectAllWorkflowDefsLatestVersionsStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectTaskDefStatement =\n                session.prepare(statements.getSelectTaskDefStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n        this.selectAllTaskDefsStatement =\n                session.prepare(statements.getSelectAllTaskDefsStatement())\n                        .setConsistencyLevel(properties.getReadConsistencyLevel());\n\n        this.updateWorkflowDefStatement =\n                session.prepare(statements.getUpdateWorkflowDefStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n\n        this.deleteWorkflowDefStatement =\n                session.prepare(statements.getDeleteWorkflowDefStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.deleteWorkflowDefIndexStatement =\n                session.prepare(statements.getDeleteWorkflowDefIndexStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n        this.deleteTaskDefStatement =\n                session.prepare(statements.getDeleteTaskDefStatement())\n                        .setConsistencyLevel(properties.getWriteConsistencyLevel());\n    }\n\n    @Override\n    public TaskDef createTaskDef(TaskDef taskDef) {\n        return insertOrUpdateTaskDef(taskDef);\n    }\n\n    @Override\n    public TaskDef updateTaskDef(TaskDef taskDef) {\n        return insertOrUpdateTaskDef(taskDef);\n    }\n\n    @Override\n    public TaskDef getTaskDef(String name) {\n        return getTaskDefFromDB(name);\n    }\n\n    @Override\n    public List<TaskDef> getAllTaskDefs() {\n        return getAllTaskDefsFromDB();\n    }\n\n    @Override\n    public void removeTaskDef(String name) {\n        try {\n            recordCassandraDaoRequests(\"removeTaskDef\");\n            session.execute(deleteTaskDefStatement.bind(name));\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"removeTaskDef\");\n            String errorMsg = String.format(\"Failed to remove task definition: %s\", name);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public void createWorkflowDef(WorkflowDef workflowDef) {\n        try {\n            String workflowDefinition = toJson(workflowDef);\n            if (!session.execute(\n                            insertWorkflowDefStatement.bind(\n                                    workflowDef.getName(),\n                                    workflowDef.getVersion(),\n                                    workflowDefinition))\n                    .wasApplied()) {\n                throw new ConflictException(\n                        \"Workflow: %s, version: %s already exists!\",\n                        workflowDef.getName(), workflowDef.getVersion());\n            }\n            String workflowDefIndex =\n                    getWorkflowDefIndexValue(workflowDef.getName(), workflowDef.getVersion());\n            session.execute(\n                    insertWorkflowDefVersionIndexStatement.bind(\n                            workflowDefIndex, workflowDefIndex));\n            recordCassandraDaoRequests(\"createWorkflowDef\");\n            recordCassandraDaoPayloadSize(\n                    \"createWorkflowDef\", workflowDefinition.length(), \"n/a\", workflowDef.getName());\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"createWorkflowDef\");\n            String errorMsg =\n                    String.format(\n                            \"Error creating workflow definition: %s/%d\",\n                            workflowDef.getName(), workflowDef.getVersion());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public void updateWorkflowDef(WorkflowDef workflowDef) {\n        try {\n            String workflowDefinition = toJson(workflowDef);\n            session.execute(\n                    updateWorkflowDefStatement.bind(\n                            workflowDefinition, workflowDef.getName(), workflowDef.getVersion()));\n            String workflowDefIndex =\n                    getWorkflowDefIndexValue(workflowDef.getName(), workflowDef.getVersion());\n            session.execute(\n                    insertWorkflowDefVersionIndexStatement.bind(\n                            workflowDefIndex, workflowDefIndex));\n            recordCassandraDaoRequests(\"updateWorkflowDef\");\n            recordCassandraDaoPayloadSize(\n                    \"updateWorkflowDef\", workflowDefinition.length(), \"n/a\", workflowDef.getName());\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"updateWorkflowDef\");\n            String errorMsg =\n                    String.format(\n                            \"Error updating workflow definition: %s/%d\",\n                            workflowDef.getName(), workflowDef.getVersion());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public Optional<WorkflowDef> getLatestWorkflowDef(String name) {\n        List<WorkflowDef> workflowDefList = getAllWorkflowDefVersions(name);\n        if (workflowDefList != null && workflowDefList.size() > 0) {\n            workflowDefList.sort(Comparator.comparingInt(WorkflowDef::getVersion));\n            return Optional.of(workflowDefList.get(workflowDefList.size() - 1));\n        }\n        return Optional.empty();\n    }\n\n    @Override\n    public Optional<WorkflowDef> getWorkflowDef(String name, int version) {\n        try {\n            recordCassandraDaoRequests(\"getWorkflowDef\");\n            ResultSet resultSet = session.execute(selectWorkflowDefStatement.bind(name, version));\n            WorkflowDef workflowDef =\n                    Optional.ofNullable(resultSet.one())\n                            .map(\n                                    row ->\n                                            readValue(\n                                                    row.getString(WORKFLOW_DEFINITION_KEY),\n                                                    WorkflowDef.class))\n                            .orElse(null);\n            return Optional.ofNullable(workflowDef);\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getTaskDef\");\n            String errorMsg = String.format(\"Error fetching workflow def: %s/%d\", name, version);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public void removeWorkflowDef(String name, Integer version) {\n        try {\n            session.execute(deleteWorkflowDefStatement.bind(name, version));\n            session.execute(\n                    deleteWorkflowDefIndexStatement.bind(\n                            WORKFLOW_DEF_INDEX_KEY, getWorkflowDefIndexValue(name, version)));\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"removeWorkflowDef\");\n            String errorMsg =\n                    String.format(\"Failed to remove workflow definition: %s/%d\", name, version);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public List<WorkflowDef> getAllWorkflowDefs() {\n        try {\n            ResultSet resultSet =\n                    session.execute(selectAllWorkflowDefsStatement.bind(WORKFLOW_DEF_INDEX_KEY));\n            List<Row> rows = resultSet.all();\n            if (rows.size() == 0) {\n                LOGGER.info(\"No workflow definitions were found.\");\n                return Collections.EMPTY_LIST;\n            }\n            return rows.stream()\n                    .map(\n                            row -> {\n                                String defNameVersion =\n                                        row.getString(WORKFLOW_DEF_NAME_VERSION_KEY);\n                                var nameVersion = getWorkflowNameAndVersion(defNameVersion);\n                                return getWorkflowDef(nameVersion.getLeft(), nameVersion.getRight())\n                                        .orElse(null);\n                            })\n                    .filter(Objects::nonNull)\n                    .collect(Collectors.toList());\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getAllWorkflowDefs\");\n            String errorMsg = \"Error retrieving all workflow defs\";\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @Override\n    public List<WorkflowDef> getAllWorkflowDefsLatestVersions() {\n        try {\n            ResultSet resultSet =\n                    session.execute(\n                            selectAllWorkflowDefsLatestVersionsStatement.bind(\n                                    WORKFLOW_DEF_INDEX_KEY));\n            List<Row> rows = resultSet.all();\n            if (rows.size() == 0) {\n                LOGGER.info(\"No workflow definitions were found.\");\n                return Collections.EMPTY_LIST;\n            }\n            Map<String, PriorityQueue<WorkflowDef>> allWorkflowDefs = new HashMap<>();\n\n            for (Row row : rows) {\n                String defNameVersion = row.getString(WORKFLOW_DEF_NAME_VERSION_KEY);\n                var nameVersion = getWorkflowNameAndVersion(defNameVersion);\n                WorkflowDef def =\n                        getWorkflowDef(nameVersion.getLeft(), nameVersion.getRight()).orElse(null);\n                if (def == null) {\n                    continue;\n                }\n                if (allWorkflowDefs.get(def.getName()) == null) {\n                    allWorkflowDefs.put(\n                            def.getName(),\n                            new PriorityQueue<>(\n                                    (WorkflowDef w1, WorkflowDef w2) ->\n                                            Integer.compare(w2.getVersion(), w1.getVersion())));\n                }\n                allWorkflowDefs.get(def.getName()).add(def);\n            }\n            return allWorkflowDefs.values().stream()\n                    .map(PriorityQueue::poll)\n                    .collect(Collectors.toList());\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getAllWorkflowDefsLatestVersions\");\n            String errorMsg = \"Error retrieving all workflow defs latest versions\";\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    private TaskDef getTaskDefFromDB(String name) {\n        try {\n            ResultSet resultSet = session.execute(selectTaskDefStatement.bind(name));\n            recordCassandraDaoRequests(\"getTaskDef\", name, null);\n            return Optional.ofNullable(resultSet.one()).map(this::setDefaults).orElse(null);\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getTaskDef\");\n            String errorMsg = String.format(\"Failed to get task def: %s\", name);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private List<TaskDef> getAllTaskDefsFromDB() {\n        try {\n            ResultSet resultSet = session.execute(selectAllTaskDefsStatement.bind(TASK_DEFS_KEY));\n            List<Row> rows = resultSet.all();\n            if (rows.size() == 0) {\n                LOGGER.info(\"No task definitions were found.\");\n                return Collections.EMPTY_LIST;\n            }\n            return rows.stream().map(this::setDefaults).collect(Collectors.toList());\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getAllTaskDefs\");\n            String errorMsg = \"Failed to get all task defs\";\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    private List<WorkflowDef> getAllWorkflowDefVersions(String name) {\n        try {\n            ResultSet resultSet =\n                    session.execute(selectAllWorkflowDefVersionsByNameStatement.bind(name));\n            recordCassandraDaoRequests(\"getAllWorkflowDefVersions\", \"n/a\", name);\n            List<Row> rows = resultSet.all();\n            if (rows.size() == 0) {\n                LOGGER.info(\"Not workflow definitions were found for : {}\", name);\n                return null;\n            }\n            return rows.stream()\n                    .map(\n                            row ->\n                                    readValue(\n                                            row.getString(WORKFLOW_DEFINITION_KEY),\n                                            WorkflowDef.class))\n                    .collect(Collectors.toList());\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"getAllWorkflowDefVersions\");\n            String errorMsg = String.format(\"Failed to get workflows defs for : %s\", name);\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    private TaskDef insertOrUpdateTaskDef(TaskDef taskDef) {\n        try {\n            String taskDefinition = toJson(taskDef);\n            session.execute(insertTaskDefStatement.bind(taskDef.getName(), taskDefinition));\n            recordCassandraDaoRequests(\"storeTaskDef\");\n            recordCassandraDaoPayloadSize(\n                    \"storeTaskDef\", taskDefinition.length(), taskDef.getName(), \"n/a\");\n        } catch (DriverException e) {\n            Monitors.error(CLASS_NAME, \"insertOrUpdateTaskDef\");\n            String errorMsg =\n                    String.format(\"Error creating/updating task definition: %s\", taskDef.getName());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n        return taskDef;\n    }\n\n    @VisibleForTesting\n    String getWorkflowDefIndexValue(String name, int version) {\n        return name + INDEX_DELIMITER + version;\n    }\n\n    @VisibleForTesting\n    ImmutablePair<String, Integer> getWorkflowNameAndVersion(String nameVersionStr) {\n        int lastIndexOfDelimiter = nameVersionStr.lastIndexOf(INDEX_DELIMITER);\n\n        if (lastIndexOfDelimiter == -1) {\n            throw new IllegalStateException(\n                    nameVersionStr\n                            + \" is not in the 'workflowName\"\n                            + INDEX_DELIMITER\n                            + \"version' pattern.\");\n        }\n\n        String workflowName = nameVersionStr.substring(0, lastIndexOfDelimiter);\n        String versionStr = nameVersionStr.substring(lastIndexOfDelimiter + 1);\n\n        try {\n            return new ImmutablePair<>(workflowName, Integer.parseInt(versionStr));\n        } catch (NumberFormatException e) {\n            throw new IllegalStateException(\n                    versionStr + \" in \" + nameVersionStr + \" is not a valid number.\");\n        }\n    }\n\n    private TaskDef setDefaults(Row row) {\n        TaskDef taskDef = readValue(row.getString(TASK_DEFINITION_KEY), TaskDef.class);\n        if (taskDef != null && taskDef.getResponseTimeoutSeconds() == 0) {\n            taskDef.setResponseTimeoutSeconds(\n                    taskDef.getTimeoutSeconds() == 0 ? ONE_HOUR : taskDef.getTimeoutSeconds() - 1);\n        }\n        return taskDef;\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/dao/CassandraPollDataDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao;\n\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.dao.PollDataDAO;\n\n/**\n * This is a dummy implementation and this feature is not implemented for Cassandra backed\n * Conductor.\n */\npublic class CassandraPollDataDAO implements PollDataDAO {\n\n    @Override\n    public void updateLastPollData(String taskDefName, String domain, String workerId) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraPollDataDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    @Override\n    public PollData getPollData(String taskDefName, String domain) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraPollDataDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    @Override\n    public List<PollData> getPollData(String taskDefName) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in CassandraPollDataDAO. Please use ExecutionDAOFacade instead.\");\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/util/Constants.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.util;\n\npublic interface Constants {\n\n    String DAO_NAME = \"cassandra\";\n\n    String TABLE_WORKFLOWS = \"workflows\";\n    String TABLE_TASK_LOOKUP = \"task_lookup\";\n    String TABLE_TASK_DEF_LIMIT = \"task_def_limit\";\n    String TABLE_WORKFLOW_DEFS = \"workflow_definitions\";\n    String TABLE_WORKFLOW_DEFS_INDEX = \"workflow_defs_index\";\n    String TABLE_TASK_DEFS = \"task_definitions\";\n    String TABLE_EVENT_HANDLERS = \"event_handlers\";\n    String TABLE_EVENT_EXECUTIONS = \"event_executions\";\n\n    String WORKFLOW_ID_KEY = \"workflow_id\";\n    String SHARD_ID_KEY = \"shard_id\";\n    String TASK_ID_KEY = \"task_id\";\n    String ENTITY_KEY = \"entity\";\n    String PAYLOAD_KEY = \"payload\";\n    String TOTAL_TASKS_KEY = \"total_tasks\";\n    String TOTAL_PARTITIONS_KEY = \"total_partitions\";\n    String TASK_DEF_NAME_KEY = \"task_def_name\";\n    String WORKFLOW_DEF_NAME_KEY = \"workflow_def_name\";\n    String WORKFLOW_VERSION_KEY = \"version\";\n    String WORKFLOW_DEFINITION_KEY = \"workflow_definition\";\n    String WORKFLOW_DEF_INDEX_KEY = \"workflow_def_version_index\";\n    String WORKFLOW_DEF_INDEX_VALUE = \"workflow_def_index_value\";\n    String WORKFLOW_DEF_NAME_VERSION_KEY = \"workflow_def_name_version\";\n    String TASK_DEFS_KEY = \"task_defs\";\n    String TASK_DEFINITION_KEY = \"task_definition\";\n    String HANDLERS_KEY = \"handlers\";\n    String EVENT_HANDLER_NAME_KEY = \"event_handler_name\";\n    String EVENT_HANDLER_KEY = \"event_handler\";\n    String MESSAGE_ID_KEY = \"message_id\";\n    String EVENT_EXECUTION_ID_KEY = \"event_execution_id\";\n\n    String ENTITY_TYPE_WORKFLOW = \"workflow\";\n    String ENTITY_TYPE_TASK = \"task\";\n\n    int DEFAULT_SHARD_ID = 1;\n    int DEFAULT_TOTAL_PARTITIONS = 1;\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/util/Statements.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.util;\n\nimport com.datastax.driver.core.querybuilder.QueryBuilder;\n\nimport static com.netflix.conductor.cassandra.util.Constants.ENTITY_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.ENTITY_TYPE_TASK;\nimport static com.netflix.conductor.cassandra.util.Constants.ENTITY_TYPE_WORKFLOW;\nimport static com.netflix.conductor.cassandra.util.Constants.EVENT_EXECUTION_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_NAME_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.HANDLERS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.MESSAGE_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.PAYLOAD_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.SHARD_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_EXECUTIONS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_HANDLERS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEFS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEF_LIMIT;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_LOOKUP;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOWS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS;\nimport static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS_INDEX;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEFINITION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEFS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_DEF_NAME_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TASK_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TOTAL_PARTITIONS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.TOTAL_TASKS_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEFINITION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_VALUE;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_VERSION_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_ID_KEY;\nimport static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_VERSION_KEY;\n\nimport static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;\nimport static com.datastax.driver.core.querybuilder.QueryBuilder.eq;\nimport static com.datastax.driver.core.querybuilder.QueryBuilder.set;\n\n/**\n * DML statements\n *\n * <p><em>MetadataDAO</em>\n *\n * <ul>\n *   <li>INSERT INTO conductor.workflow_definitions (workflow_def_name,version,workflow_definition)\n *       VALUES (?,?,?) IF NOT EXISTS;\n *   <li>INSERT INTO conductor.workflow_defs_index\n *       (workflow_def_version_index,workflow_def_name_version, workflow_def_index_value) VALUES\n *       ('workflow_def_version_index',?,?);\n *   <li>INSERT INTO conductor.task_definitions (task_defs,task_def_name,task_definition) VALUES\n *       ('task_defs',?,?);\n *   <li>SELECT workflow_definition FROM conductor.workflow_definitions WHERE workflow_def_name=?\n *       AND version=?;\n *   <li>SELECT * FROM conductor.workflow_definitions WHERE workflow_def_name=?;\n *   <li>SELECT * FROM conductor.workflow_defs_index WHERE workflow_def_version_index=?;\n *   <li>SELECT task_definition FROM conductor.task_definitions WHERE task_defs='task_defs' AND\n *       task_def_name=?;\n *   <li>SELECT * FROM conductor.task_definitions WHERE task_defs=?;\n *   <li>UPDATE conductor.workflow_definitions SET workflow_definition=? WHERE workflow_def_name=?\n *       AND version=?;\n *   <li>DELETE FROM conductor.workflow_definitions WHERE workflow_def_name=? AND version=?;\n *   <li>DELETE FROM conductor.workflow_defs_index WHERE workflow_def_version_index=? AND\n *       workflow_def_name_version=?;\n *   <li>DELETE FROM conductor.task_definitions WHERE task_defs='task_defs' AND task_def_name=?;\n * </ul>\n *\n * <em>ExecutionDAO</em>\n *\n * <ul>\n *   <li>INSERT INTO conductor.workflows\n *       (workflow_id,shard_id,task_id,entity,payload,total_tasks,total_partitions) VALUES\n *       (?,?,?,'workflow',?,?,?);\n *   <li>INSERT INTO conductor.workflows (workflow_id,shard_id,task_id,entity,payload) VALUES\n *       (?,?,?,'task',?);\n *   <li>INSERT INTO conductor.event_executions\n *       (message_id,event_handler_name,event_execution_id,payload) VALUES (?,?,?,?) IF NOT EXISTS;\n *   <li>SELECT total_tasks,total_partitions FROM conductor.workflows WHERE workflow_id=? AND\n *       shard_id=1;\n *   <li>SELECT payload FROM conductor.workflows WHERE workflow_id=? AND shard_id=? AND\n *       entity='task' AND task_id=?;\n *   <li>SELECT payload FROM conductor.workflows WHERE workflow_id=? AND shard_id=1 AND\n *       entity='workflow';\n *   <li>SELECT * FROM conductor.workflows WHERE workflow_id=? AND shard_id=?;\n *   <li>SELECT workflow_id FROM conductor.task_lookup WHERE task_id=?;\n *   <li>SELECT * FROM conductor.task_def_limit WHERE task_def_name=?;\n *   <li>SELECT * FROM conductor.event_executions WHERE message_id=? AND event_handler_name=?;\n *   <li>UPDATE conductor.workflows SET payload=? WHERE workflow_id=? AND shard_id=1 AND\n *       entity='workflow' AND task_id='';\n *   <li>UPDATE conductor.workflows SET total_tasks=? WHERE workflow_id=? AND shard_id=?;\n *   <li>UPDATE conductor.workflows SET total_partitions=?,total_tasks=? WHERE workflow_id=? AND\n *       shard_id=1;\n *   <li>UPDATE conductor.task_lookup SET workflow_id=? WHERE task_id=?;\n *   <li>UPDATE conductor.task_def_limit SET workflow_id=? WHERE task_def_name=? AND task_id=?;\n *   <li>UPDATE conductor.event_executions USING TTL ? SET payload=? WHERE message_id=? AND\n *       event_handler_name=? AND event_execution_id=?;\n *   <li>DELETE FROM conductor.workflows WHERE workflow_id=? AND shard_id=?;\n *   <li>DELETE FROM conductor.workflows WHERE workflow_id=? AND shard_id=? AND entity='task' AND\n *       task_id=?;\n *   <li>DELETE FROM conductor.task_lookup WHERE task_id=?;\n *   <li>DELETE FROM conductor.task_def_limit WHERE task_def_name=? AND task_id=?;\n *   <li>DELETE FROM conductor.event_executions WHERE message_id=? AND event_handler_name=? AND\n *       event_execution_id=?;\n * </ul>\n *\n * <em>EventHandlerDAO</em>\n *\n * <ul>\n *   <li>INSERT INTO conductor.event_handlers (handlers,event_handler_name,event_handler) VALUES\n *       ('handlers',?,?);\n *   <li>SELECT * FROM conductor.event_handlers WHERE handlers=?;\n *   <li>DELETE FROM conductor.event_handlers WHERE handlers='handlers' AND event_handler_name=?;\n * </ul>\n */\npublic class Statements {\n\n    private final String keyspace;\n\n    public Statements(String keyspace) {\n        this.keyspace = keyspace;\n    }\n\n    // MetadataDAO\n    // Insert Statements\n\n    /**\n     * @return cql query statement to insert a new workflow definition into the\n     *     \"workflow_definitions\" table\n     */\n    public String getInsertWorkflowDefStatement() {\n        return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOW_DEFS)\n                .value(WORKFLOW_DEF_NAME_KEY, bindMarker())\n                .value(WORKFLOW_VERSION_KEY, bindMarker())\n                .value(WORKFLOW_DEFINITION_KEY, bindMarker())\n                .ifNotExists()\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to insert a workflow def name version index into the\n     *     \"workflow_defs_index\" table\n     */\n    public String getInsertWorkflowDefVersionIndexStatement() {\n        return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOW_DEFS_INDEX)\n                .value(WORKFLOW_DEF_INDEX_KEY, WORKFLOW_DEF_INDEX_KEY)\n                .value(WORKFLOW_DEF_NAME_VERSION_KEY, bindMarker())\n                .value(WORKFLOW_DEF_INDEX_VALUE, bindMarker())\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to insert a new task definition into the \"task_definitions\" table\n     */\n    public String getInsertTaskDefStatement() {\n        return QueryBuilder.insertInto(keyspace, TABLE_TASK_DEFS)\n                .value(TASK_DEFS_KEY, TASK_DEFS_KEY)\n                .value(TASK_DEF_NAME_KEY, bindMarker())\n                .value(TASK_DEFINITION_KEY, bindMarker())\n                .getQueryString();\n    }\n\n    // Select Statements\n\n    /**\n     * @return cql query statement to fetch a workflow definition by name and version from the\n     *     \"workflow_definitions\" table\n     */\n    public String getSelectWorkflowDefStatement() {\n        return QueryBuilder.select(WORKFLOW_DEFINITION_KEY)\n                .from(keyspace, TABLE_WORKFLOW_DEFS)\n                .where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))\n                .and(eq(WORKFLOW_VERSION_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve all versions of a workflow definition by name from\n     *     the \"workflow_definitions\" table\n     */\n    public String getSelectAllWorkflowDefVersionsByNameStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_WORKFLOW_DEFS)\n                .where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to fetch all workflow def names and version from the\n     *     \"workflow_defs_index\" table\n     */\n    public String getSelectAllWorkflowDefsStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_WORKFLOW_DEFS_INDEX)\n                .where(eq(WORKFLOW_DEF_INDEX_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    public String getSelectAllWorkflowDefsLatestVersionsStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_WORKFLOW_DEFS_INDEX)\n                .where(eq(WORKFLOW_DEF_INDEX_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to fetch a task definition by name from the \"task_definitions\"\n     *     table\n     */\n    public String getSelectTaskDefStatement() {\n        return QueryBuilder.select(TASK_DEFINITION_KEY)\n                .from(keyspace, TABLE_TASK_DEFS)\n                .where(eq(TASK_DEFS_KEY, TASK_DEFS_KEY))\n                .and(eq(TASK_DEF_NAME_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve all task definitions from the \"task_definitions\"\n     *     table\n     */\n    public String getSelectAllTaskDefsStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_TASK_DEFS)\n                .where(eq(TASK_DEFS_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    // Update Statement\n\n    /**\n     * @return cql query statement to update a workflow definitinos in the \"workflow_definitions\"\n     *     table\n     */\n    public String getUpdateWorkflowDefStatement() {\n        return QueryBuilder.update(keyspace, TABLE_WORKFLOW_DEFS)\n                .with(set(WORKFLOW_DEFINITION_KEY, bindMarker()))\n                .where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))\n                .and(eq(WORKFLOW_VERSION_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    // Delete Statements\n\n    /**\n     * @return cql query statement to delete a workflow definition by name and version from the\n     *     \"workflow_definitions\" table\n     */\n    public String getDeleteWorkflowDefStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_WORKFLOW_DEFS)\n                .where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))\n                .and(eq(WORKFLOW_VERSION_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to delete a workflow def name/version from the\n     *     \"workflow_defs_index\" table\n     */\n    public String getDeleteWorkflowDefIndexStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_WORKFLOW_DEFS_INDEX)\n                .where(eq(WORKFLOW_DEF_INDEX_KEY, bindMarker()))\n                .and(eq(WORKFLOW_DEF_NAME_VERSION_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to delete a task definition by name from the \"task_definitions\"\n     *     table\n     */\n    public String getDeleteTaskDefStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_TASK_DEFS)\n                .where(eq(TASK_DEFS_KEY, TASK_DEFS_KEY))\n                .and(eq(TASK_DEF_NAME_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    // ExecutionDAO\n    // Insert Statements\n\n    /**\n     * @return cql query statement to insert a new workflow into the \"workflows\" table\n     */\n    public String getInsertWorkflowStatement() {\n        return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOWS)\n                .value(WORKFLOW_ID_KEY, bindMarker())\n                .value(SHARD_ID_KEY, bindMarker())\n                .value(TASK_ID_KEY, bindMarker())\n                .value(ENTITY_KEY, ENTITY_TYPE_WORKFLOW)\n                .value(PAYLOAD_KEY, bindMarker())\n                .value(TOTAL_TASKS_KEY, bindMarker())\n                .value(TOTAL_PARTITIONS_KEY, bindMarker())\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to insert a new task into the \"workflows\" table\n     */\n    public String getInsertTaskStatement() {\n        return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOWS)\n                .value(WORKFLOW_ID_KEY, bindMarker())\n                .value(SHARD_ID_KEY, bindMarker())\n                .value(TASK_ID_KEY, bindMarker())\n                .value(ENTITY_KEY, ENTITY_TYPE_TASK)\n                .value(PAYLOAD_KEY, bindMarker())\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to insert a new event execution into the \"event_executions\" table\n     */\n    public String getInsertEventExecutionStatement() {\n        return QueryBuilder.insertInto(keyspace, TABLE_EVENT_EXECUTIONS)\n                .value(MESSAGE_ID_KEY, bindMarker())\n                .value(EVENT_HANDLER_NAME_KEY, bindMarker())\n                .value(EVENT_EXECUTION_ID_KEY, bindMarker())\n                .value(PAYLOAD_KEY, bindMarker())\n                .ifNotExists()\n                .getQueryString();\n    }\n\n    // Select Statements\n\n    /**\n     * @return cql query statement to retrieve the total_tasks and total_partitions for a workflow\n     *     from the \"workflows\" table\n     */\n    public String getSelectTotalStatement() {\n        return QueryBuilder.select(TOTAL_TASKS_KEY, TOTAL_PARTITIONS_KEY)\n                .from(keyspace, TABLE_WORKFLOWS)\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, 1))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve a task from the \"workflows\" table\n     */\n    public String getSelectTaskStatement() {\n        return QueryBuilder.select(PAYLOAD_KEY)\n                .from(keyspace, TABLE_WORKFLOWS)\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, bindMarker()))\n                .and(eq(ENTITY_KEY, ENTITY_TYPE_TASK))\n                .and(eq(TASK_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve a workflow (without its tasks) from the \"workflows\"\n     *     table\n     */\n    public String getSelectWorkflowStatement() {\n        return QueryBuilder.select(PAYLOAD_KEY)\n                .from(keyspace, TABLE_WORKFLOWS)\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, 1))\n                .and(eq(ENTITY_KEY, ENTITY_TYPE_WORKFLOW))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve a workflow with its tasks from the \"workflows\" table\n     */\n    public String getSelectWorkflowWithTasksStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_WORKFLOWS)\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve the workflow_id for a particular task_id from the\n     *     \"task_lookup\" table\n     */\n    public String getSelectTaskFromLookupTableStatement() {\n        return QueryBuilder.select(WORKFLOW_ID_KEY)\n                .from(keyspace, TABLE_TASK_LOOKUP)\n                .where(eq(TASK_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve all task ids for a given taskDefName with concurrent\n     *     execution limit configured from the \"task_def_limit\" table\n     */\n    public String getSelectTasksFromTaskDefLimitStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_TASK_DEF_LIMIT)\n                .where(eq(TASK_DEF_NAME_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to retrieve all event executions for a given message and event\n     *     handler from the \"event_executions\" table\n     */\n    public String getSelectAllEventExecutionsForMessageFromEventExecutionsStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_EVENT_EXECUTIONS)\n                .where(eq(MESSAGE_ID_KEY, bindMarker()))\n                .and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    // Update Statements\n\n    /**\n     * @return cql query statement to update a workflow in the \"workflows\" table\n     */\n    public String getUpdateWorkflowStatement() {\n        return QueryBuilder.update(keyspace, TABLE_WORKFLOWS)\n                .with(set(PAYLOAD_KEY, bindMarker()))\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, 1))\n                .and(eq(ENTITY_KEY, ENTITY_TYPE_WORKFLOW))\n                .and(eq(TASK_ID_KEY, \"\"))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to update the total_tasks in a shard for a workflow in the\n     *     \"workflows\" table\n     */\n    public String getUpdateTotalTasksStatement() {\n        return QueryBuilder.update(keyspace, TABLE_WORKFLOWS)\n                .with(set(TOTAL_TASKS_KEY, bindMarker()))\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to update the total_partitions for a workflow in the \"workflows\"\n     *     table\n     */\n    public String getUpdateTotalPartitionsStatement() {\n        return QueryBuilder.update(keyspace, TABLE_WORKFLOWS)\n                .with(set(TOTAL_PARTITIONS_KEY, bindMarker()))\n                .and(set(TOTAL_TASKS_KEY, bindMarker()))\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, 1))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to add a new task_id to workflow_id mapping to the \"task_lookup\"\n     *     table\n     */\n    public String getUpdateTaskLookupStatement() {\n        return QueryBuilder.update(keyspace, TABLE_TASK_LOOKUP)\n                .with(set(WORKFLOW_ID_KEY, bindMarker()))\n                .where(eq(TASK_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to add a new task_id to the \"task_def_limit\" table\n     */\n    public String getUpdateTaskDefLimitStatement() {\n        return QueryBuilder.update(keyspace, TABLE_TASK_DEF_LIMIT)\n                .with(set(WORKFLOW_ID_KEY, bindMarker()))\n                .where(eq(TASK_DEF_NAME_KEY, bindMarker()))\n                .and(eq(TASK_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to update an event execution in the \"event_executions\" table\n     */\n    public String getUpdateEventExecutionStatement() {\n        return QueryBuilder.update(keyspace, TABLE_EVENT_EXECUTIONS)\n                .using(QueryBuilder.ttl(bindMarker()))\n                .with(set(PAYLOAD_KEY, bindMarker()))\n                .where(eq(MESSAGE_ID_KEY, bindMarker()))\n                .and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))\n                .and(eq(EVENT_EXECUTION_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    // Delete statements\n\n    /**\n     * @return cql query statement to delete a workflow from the \"workflows\" table\n     */\n    public String getDeleteWorkflowStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_WORKFLOWS)\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to delete a task_id to workflow_id mapping from the \"task_lookup\"\n     *     table\n     */\n    public String getDeleteTaskLookupStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_TASK_LOOKUP)\n                .where(eq(TASK_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to delete a task from the \"workflows\" table\n     */\n    public String getDeleteTaskStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_WORKFLOWS)\n                .where(eq(WORKFLOW_ID_KEY, bindMarker()))\n                .and(eq(SHARD_ID_KEY, bindMarker()))\n                .and(eq(ENTITY_KEY, ENTITY_TYPE_TASK))\n                .and(eq(TASK_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to delete a task_id from the \"task_def_limit\" table\n     */\n    public String getDeleteTaskDefLimitStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_TASK_DEF_LIMIT)\n                .where(eq(TASK_DEF_NAME_KEY, bindMarker()))\n                .and(eq(TASK_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    /**\n     * @return cql query statement to delete an event execution from the \"event_execution\" table\n     */\n    public String getDeleteEventExecutionsStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_EVENT_EXECUTIONS)\n                .where(eq(MESSAGE_ID_KEY, bindMarker()))\n                .and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))\n                .and(eq(EVENT_EXECUTION_ID_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    // EventHandlerDAO\n    // Insert Statements\n\n    /**\n     * @return cql query statement to insert an event handler into the \"event_handlers\" table\n     */\n    public String getInsertEventHandlerStatement() {\n        return QueryBuilder.insertInto(keyspace, TABLE_EVENT_HANDLERS)\n                .value(HANDLERS_KEY, HANDLERS_KEY)\n                .value(EVENT_HANDLER_NAME_KEY, bindMarker())\n                .value(EVENT_HANDLER_KEY, bindMarker())\n                .getQueryString();\n    }\n\n    // Select Statements\n\n    /**\n     * @return cql query statement to retrieve all event handlers from the \"event_handlers\" table\n     */\n    public String getSelectAllEventHandlersStatement() {\n        return QueryBuilder.select()\n                .all()\n                .from(keyspace, TABLE_EVENT_HANDLERS)\n                .where(eq(HANDLERS_KEY, bindMarker()))\n                .getQueryString();\n    }\n\n    // Delete Statements\n\n    /**\n     * @return cql query statement to delete an event handler by name from the \"event_handlers\"\n     *     table\n     */\n    public String getDeleteEventHandlerStatement() {\n        return QueryBuilder.delete()\n                .from(keyspace, TABLE_EVENT_HANDLERS)\n                .where(eq(HANDLERS_KEY, HANDLERS_KEY))\n                .and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))\n                .getQueryString();\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"properties\": [\n    {\n      \"name\": \"conductor.cassandra.write-consistency-level\",\n      \"defaultValue\": \"LOCAL_QUORUM\"\n    },\n    {\n      \"name\": \"conductor.cassandra.read-consistency-level\",\n      \"defaultValue\": \"LOCAL_QUORUM\"\n    }\n  ],\n  \"hints\": [\n    {\n      \"name\": \"conductor.cassandra.write-consistency-level\",\n      \"providers\": [\n        {\n          \"name\": \"handle-as\",\n          \"parameters\": {\n            \"target\": \"java.lang.Enum\"\n          }\n        }\n      ]\n    },\n    {\n      \"name\": \"conductor.cassandra.read-consistency-level\",\n      \"providers\": [\n        {\n          \"name\": \"handle-as\",\n          \"parameters\": {\n            \"target\": \"java.lang.Enum\"\n          }\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "cassandra-persistence/src/test/groovy/com/netflix/conductor/cassandra/dao/CassandraEventHandlerDAOSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao\n\nimport com.netflix.conductor.common.metadata.events.EventExecution\nimport com.netflix.conductor.common.metadata.events.EventHandler\n\nimport spock.lang.Subject\n\nclass CassandraEventHandlerDAOSpec extends CassandraSpec {\n\n    @Subject\n    CassandraEventHandlerDAO eventHandlerDAO\n\n    CassandraExecutionDAO executionDAO\n\n    def setup() {\n        eventHandlerDAO = new CassandraEventHandlerDAO(session, objectMapper, cassandraProperties, statements)\n        executionDAO = new CassandraExecutionDAO(session, objectMapper, cassandraProperties, statements)\n    }\n\n    def testEventHandlerCRUD() {\n        given:\n        String event = \"event\"\n        String eventHandlerName1 = \"event_handler1\"\n        String eventHandlerName2 = \"event_handler2\"\n\n        EventHandler eventHandler = new EventHandler()\n        eventHandler.setName(eventHandlerName1)\n        eventHandler.setEvent(event)\n\n        when: // create event handler\n        eventHandlerDAO.addEventHandler(eventHandler)\n        List<EventHandler> handlers = eventHandlerDAO.getEventHandlersForEvent(event, false)\n\n        then: // fetch all event handlers for event\n        handlers != null && handlers.size() == 1\n        eventHandler.name == handlers[0].name\n        eventHandler.event == handlers[0].event\n        !handlers[0].active\n\n        and: // add an active event handler for the same event\n        EventHandler eventHandler1 = new EventHandler()\n        eventHandler1.setName(eventHandlerName2)\n        eventHandler1.setEvent(event)\n        eventHandler1.setActive(true)\n        eventHandlerDAO.addEventHandler(eventHandler1)\n\n        when: // fetch all event handlers\n        handlers = eventHandlerDAO.getAllEventHandlers()\n\n        then:\n        handlers != null && handlers.size() == 2\n\n        when: // fetch all event handlers for event\n        handlers = eventHandlerDAO.getEventHandlersForEvent(event, false)\n\n        then:\n        handlers != null && handlers.size() == 2\n\n        when: // fetch only active handlers for event\n        handlers = eventHandlerDAO.getEventHandlersForEvent(event, true)\n\n        then:\n        handlers != null && handlers.size() == 1\n        eventHandler1.name == handlers[0].name\n        eventHandler1.event == handlers[0].event\n        handlers[0].active\n\n        when: // remove event handler\n        eventHandlerDAO.removeEventHandler(eventHandlerName1)\n        handlers = eventHandlerDAO.getAllEventHandlers()\n\n        then:\n        handlers != null && handlers.size() == 1\n    }\n\n\n\n    private static EventExecution getEventExecution(String id, String msgId, String name, String event) {\n        EventExecution eventExecution = new EventExecution(id, msgId);\n        eventExecution.setName(name);\n        eventExecution.setEvent(event);\n        eventExecution.setStatus(EventExecution.Status.IN_PROGRESS);\n        return eventExecution;\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/test/groovy/com/netflix/conductor/cassandra/dao/CassandraExecutionDAOSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao\n\nimport com.netflix.conductor.common.metadata.events.EventExecution\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask\nimport com.netflix.conductor.core.exception.NonTransientException\nimport com.netflix.conductor.core.utils.IDGenerator\nimport com.netflix.conductor.model.TaskModel\nimport com.netflix.conductor.model.WorkflowModel\n\nimport spock.lang.Subject\n\nimport static com.netflix.conductor.common.metadata.events.EventExecution.Status.COMPLETED\nimport static com.netflix.conductor.common.metadata.events.EventExecution.Status.IN_PROGRESS\n\nclass CassandraExecutionDAOSpec extends CassandraSpec {\n\n    @Subject\n    CassandraExecutionDAO executionDAO\n\n    def setup() {\n        executionDAO = new CassandraExecutionDAO(session, objectMapper, cassandraProperties, statements)\n    }\n\n    def \"verify if tasks are validated\"() {\n        given:\n        def tasks = []\n\n        // create tasks for a workflow and add to list\n        TaskModel task1 = new TaskModel(workflowInstanceId: 'uuid', taskId: 'task1id', referenceTaskName: 'task1')\n        TaskModel task2 = new TaskModel(workflowInstanceId: 'uuid', taskId: 'task2id', referenceTaskName: 'task2')\n        tasks << task1 << task2\n\n        when:\n        executionDAO.validateTasks(tasks)\n\n        then:\n        noExceptionThrown()\n\n        and:\n        // add a task from a different workflow to the list\n        TaskModel task3 = new TaskModel(workflowInstanceId: 'other-uuid', taskId: 'task3id', referenceTaskName: 'task3')\n        tasks << task3\n\n        when:\n        executionDAO.validateTasks(tasks)\n\n        then:\n        def ex = thrown(NonTransientException.class)\n        ex.message == \"Tasks of multiple workflows cannot be created/updated simultaneously\"\n    }\n\n    def \"workflow CRUD\"() {\n        given:\n        String workflowId = new IDGenerator().generate()\n        WorkflowDef workflowDef = new WorkflowDef()\n        workflowDef.name = \"def1\"\n        workflowDef.setVersion(1)\n        WorkflowModel workflow = new WorkflowModel()\n        workflow.setWorkflowDefinition(workflowDef)\n        workflow.setWorkflowId(workflowId)\n        workflow.setInput(new HashMap<>())\n        workflow.setStatus(WorkflowModel.Status.RUNNING)\n        workflow.setCreateTime(System.currentTimeMillis())\n\n        when:\n        // create a new workflow in the datastore\n        String id = executionDAO.createWorkflow(workflow)\n\n        then:\n        workflowId == id\n\n        when:\n        // read the workflow from the datastore\n        WorkflowModel found = executionDAO.getWorkflow(workflowId)\n\n        then:\n        workflow == found\n\n        and:\n        // update the workflow\n        workflow.setStatus(WorkflowModel.Status.COMPLETED)\n        executionDAO.updateWorkflow(workflow)\n\n        when:\n        found = executionDAO.getWorkflow(workflowId)\n\n        then:\n        workflow == found\n\n        when:\n        // remove the workflow from datastore\n        boolean removed = executionDAO.removeWorkflow(workflowId)\n\n        then:\n        removed\n\n        when:\n        // read workflow again\n        workflow = executionDAO.getWorkflow(workflowId, true)\n\n        then:\n        workflow == null\n    }\n\n    def \"create tasks and verify methods that read tasks and workflow\"() {\n        given: 'we create a workflow'\n        String workflowId = new IDGenerator().generate()\n        WorkflowDef workflowDef = new WorkflowDef(name: 'def1', version: 1)\n        WorkflowModel workflow = new WorkflowModel(workflowDefinition: workflowDef, workflowId: workflowId, input: new HashMap(), status: WorkflowModel.Status.RUNNING, createTime: System.currentTimeMillis())\n        executionDAO.createWorkflow(workflow)\n\n        and: 'create tasks for this workflow'\n        TaskModel task1 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task1', referenceTaskName: 'task1', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n        TaskModel task2 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task2', referenceTaskName: 'task2', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n        TaskModel task3 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task3', referenceTaskName: 'task3', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n\n        def taskList = [task1, task2, task3]\n\n        when: 'add the tasks to the datastore'\n        List<TaskModel> tasks = executionDAO.createTasks(taskList)\n\n        then:\n        tasks != null\n        taskList == tasks\n\n        when: 'read the tasks from the datastore'\n        def retTask1 = executionDAO.getTask(task1.taskId)\n        def retTask2 = executionDAO.getTask(task2.taskId)\n        def retTask3 = executionDAO.getTask(task3.taskId)\n\n        then:\n        task1 == retTask1\n        task2 == retTask2\n        task3 == retTask3\n\n        when: 'lookup workflowId for the task'\n        def foundId1 = executionDAO.lookupWorkflowIdFromTaskId(task1.taskId)\n        def foundId2 = executionDAO.lookupWorkflowIdFromTaskId(task2.taskId)\n        def foundId3 = executionDAO.lookupWorkflowIdFromTaskId(task3.taskId)\n\n        then:\n        foundId1 == workflowId\n        foundId2 == workflowId\n        foundId3 == workflowId\n\n        when: 'check the metadata'\n        def workflowMetadata = executionDAO.getWorkflowMetadata(workflowId)\n\n        then:\n        workflowMetadata.totalTasks == 3\n        workflowMetadata.totalPartitions == 1\n\n        when: 'check the getTasks api'\n        def fetchedTasks = executionDAO.getTasks([task1.taskId, task2.taskId, task3.taskId])\n\n        then:\n        fetchedTasks != null && fetchedTasks.size() == 3\n\n        when: 'get the tasks for the workflow'\n        fetchedTasks = executionDAO.getTasksForWorkflow(workflowId)\n\n        then:\n        fetchedTasks != null && fetchedTasks.size() == 3\n\n        when: 'read workflow with tasks'\n        WorkflowModel found = executionDAO.getWorkflow(workflowId, true)\n\n        then:\n        found != null\n        workflow.workflowId == found.workflowId\n        found.tasks != null && found.tasks.size() == 3\n        found.getTaskByRefName('task1') == task1\n        found.getTaskByRefName('task2') == task2\n        found.getTaskByRefName('task3') == task3\n    }\n\n    def \"verify tasks are updated\"() {\n        given: 'we create a workflow'\n        String workflowId = new IDGenerator().generate()\n        WorkflowDef workflowDef = new WorkflowDef(name: 'def1', version: 1)\n        WorkflowModel workflow = new WorkflowModel(workflowDefinition: workflowDef, workflowId: workflowId, input: new HashMap(), status: WorkflowModel.Status.RUNNING, createTime: System.currentTimeMillis())\n        executionDAO.createWorkflow(workflow)\n\n        and: 'create tasks for this workflow'\n        TaskModel task1 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task1', referenceTaskName: 'task1', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n        TaskModel task2 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task2', referenceTaskName: 'task2', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n        TaskModel task3 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task3', referenceTaskName: 'task3', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n\n        and: 'add the tasks to the datastore'\n        executionDAO.createTasks([task1, task2, task3])\n\n        and: 'change the status of those tasks'\n        task1.setStatus(TaskModel.Status.IN_PROGRESS)\n        task2.setStatus(TaskModel.Status.COMPLETED)\n        task3.setStatus(TaskModel.Status.FAILED)\n\n        when: 'update the tasks'\n        executionDAO.updateTask(task1)\n        executionDAO.updateTask(task2)\n        executionDAO.updateTask(task3)\n\n        then:\n        executionDAO.getTask(task1.taskId).status == TaskModel.Status.IN_PROGRESS\n        executionDAO.getTask(task2.taskId).status == TaskModel.Status.COMPLETED\n        executionDAO.getTask(task3.taskId).status == TaskModel.Status.FAILED\n\n        when: 'get pending tasks for the workflow'\n        List<TaskModel> pendingTasks = executionDAO.getPendingTasksByWorkflow(task1.getTaskType(), workflowId)\n\n        then:\n        pendingTasks != null && pendingTasks.size() == 1\n        pendingTasks[0] == task1\n    }\n\n    def \"verify tasks are removed\"() {\n        given: 'we create a workflow'\n        String workflowId = new IDGenerator().generate()\n        WorkflowDef workflowDef = new WorkflowDef(name: 'def1', version: 1)\n        WorkflowModel workflow = new WorkflowModel(workflowDefinition: workflowDef, workflowId: workflowId, input: new HashMap(), status: WorkflowModel.Status.RUNNING, createTime: System.currentTimeMillis())\n        executionDAO.createWorkflow(workflow)\n\n        and: 'create tasks for this workflow'\n        TaskModel task1 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task1', referenceTaskName: 'task1', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n        TaskModel task2 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task2', referenceTaskName: 'task2', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n        TaskModel task3 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task3', referenceTaskName: 'task3', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())\n\n        and: 'add the tasks to the datastore'\n        executionDAO.createTasks([task1, task2, task3])\n\n        when:\n        boolean removed = executionDAO.removeTask(task3.getTaskId())\n\n        then:\n        removed\n        def workflowMetadata = executionDAO.getWorkflowMetadata(workflowId)\n        workflowMetadata.totalTasks == 2\n        workflowMetadata.totalPartitions == 1\n\n        when: 'read workflow with tasks again'\n        def found = executionDAO.getWorkflow(workflowId)\n\n        then:\n        found != null\n        found.workflowId == workflowId\n        found.tasks.size() == 2\n        found.getTaskByRefName('task1') == task1\n        found.getTaskByRefName('task2') == task2\n\n        and: 'read workflowId for the deleted task id'\n        executionDAO.lookupWorkflowIdFromTaskId(task3.taskId) == null\n\n        and: 'try to read removed task'\n        executionDAO.getTask(task3.getTaskId()) == null\n\n        when: 'remove the workflow'\n        removed = executionDAO.removeWorkflow(workflowId)\n\n        then: 'check task_lookup table'\n        removed\n        executionDAO.lookupWorkflowIdFromTaskId(task1.taskId) == null\n        executionDAO.lookupWorkflowIdFromTaskId(task2.taskId) == null\n    }\n\n    def \"CRUD on task def limit\"() {\n        given:\n        String taskDefName = \"test_task_def\"\n        String taskId = new IDGenerator().generate()\n\n        TaskDef taskDef = new TaskDef(concurrentExecLimit: 1)\n        WorkflowTask workflowTask = new WorkflowTask(taskDefinition: taskDef)\n        workflowTask.setTaskDefinition(taskDef)\n\n        TaskModel task = new TaskModel()\n        task.taskDefName = taskDefName\n        task.taskId = taskId\n        task.workflowInstanceId = new IDGenerator().generate()\n        task.setWorkflowTask(workflowTask)\n        task.setTaskType(\"test_task\")\n        task.setWorkflowType(\"test_workflow\")\n        task.setStatus(TaskModel.Status.SCHEDULED)\n\n        TaskModel newTask = new TaskModel()\n        newTask.setTaskDefName(taskDefName)\n        newTask.setTaskId(new IDGenerator().generate())\n        newTask.setWorkflowInstanceId(new IDGenerator().generate())\n        newTask.setWorkflowTask(workflowTask)\n        newTask.setTaskType(\"test_task\")\n        newTask.setWorkflowType(\"test_workflow\")\n        newTask.setStatus(TaskModel.Status.SCHEDULED)\n\n        when: // no tasks are IN_PROGRESS\n        executionDAO.addTaskToLimit(task)\n\n        then:\n        !executionDAO.exceedsLimit(task)\n\n        when: // set a task to IN_PROGRESS\n        task.setStatus(TaskModel.Status.IN_PROGRESS)\n        executionDAO.addTaskToLimit(task)\n\n        then: // same task is checked\n        !executionDAO.exceedsLimit(task)\n\n        and: // check if new task can be added\n        executionDAO.exceedsLimit(newTask)\n\n        when: // set IN_PROGRESS task to COMPLETED\n        task.setStatus(TaskModel.Status.COMPLETED)\n        executionDAO.removeTaskFromLimit(task)\n\n        then: // check new task again\n        !executionDAO.exceedsLimit(newTask)\n\n        when: // set new task to IN_PROGRESS\n        newTask.setStatus(TaskModel.Status.IN_PROGRESS)\n        executionDAO.addTaskToLimit(newTask)\n\n        then: // check new task again\n        !executionDAO.exceedsLimit(newTask)\n    }\n\n    def \"verify if invalid identifiers throw correct exceptions\"() {\n        when: 'verify that a non-conforming uuid throws an exception'\n        executionDAO.getTask('invalid_id')\n\n        then:\n        thrown(IllegalArgumentException.class)\n\n        when: 'verify that a non-conforming uuid throws an exception'\n        executionDAO.getWorkflow('invalid_id', true)\n\n        then:\n        thrown(IllegalArgumentException.class)\n\n        and: 'verify that a non-existing generated id returns null'\n        executionDAO.getTask(new IDGenerator().generate()) == null\n        executionDAO.getWorkflow(new IDGenerator().generate(), true) == null\n    }\n\n    def \"CRUD on event execution\"() throws Exception {\n        given:\n        String event = \"test-event\"\n        String executionId1 = \"id_1\"\n        String messageId1 = \"message1\"\n        String eventHandler1 = \"test_eh_1\"\n        EventExecution eventExecution1 = getEventExecution(executionId1, messageId1, eventHandler1, event)\n\n        when: // create event execution explicitly\n        executionDAO.addEventExecution(eventExecution1)\n        List<EventExecution> eventExecutionList = executionDAO.getEventExecutions(eventHandler1, event, messageId1)\n\n        then: // fetch executions\n        eventExecutionList != null && eventExecutionList.size() == 1\n        eventExecutionList[0] == eventExecution1\n\n        when: // add a different execution for same message\n        String executionId2 = \"id_2\"\n        EventExecution eventExecution2 = getEventExecution(executionId2, messageId1, eventHandler1, event)\n        executionDAO.addEventExecution(eventExecution2)\n        eventExecutionList = executionDAO.getEventExecutions(eventHandler1, event, messageId1)\n\n        then: // fetch executions\n        eventExecutionList != null && eventExecutionList.size() == 2\n        eventExecutionList[0] == eventExecution1\n        eventExecutionList[1] == eventExecution2\n\n        when: // update the second execution\n        eventExecution2.setStatus(COMPLETED)\n        executionDAO.updateEventExecution(eventExecution2)\n        eventExecutionList = executionDAO.getEventExecutions(eventHandler1, event, messageId1)\n\n        then: // fetch executions\n        eventExecutionList != null && eventExecutionList.size() == 2\n        eventExecutionList[0].status == IN_PROGRESS\n        eventExecutionList[1].status == COMPLETED\n\n        when: // sleep for 5 seconds (TTL)\n        Thread.sleep(5000L)\n        eventExecutionList = executionDAO.getEventExecutions(eventHandler1, event, messageId1)\n\n        then:\n        eventExecutionList != null && eventExecutionList.size() == 1\n\n        when: // delete event execution\n        executionDAO.removeEventExecution(eventExecution1)\n        eventExecutionList = executionDAO.getEventExecutions(eventHandler1, event, messageId1)\n\n        then:\n        eventExecutionList != null && eventExecutionList.empty\n    }\n\n    def \"verify workflow serialization\"() {\n        given: 'define a workflow'\n        String workflowId = new IDGenerator().generate()\n        WorkflowTask workflowTask = new WorkflowTask(taskDefinition: new TaskDef(concurrentExecLimit: 2))\n        WorkflowDef workflowDef = new WorkflowDef(name: UUID.randomUUID().toString(), version: 1, tasks: [workflowTask])\n        WorkflowModel workflow = new WorkflowModel(workflowDefinition: workflowDef, workflowId: workflowId, status: WorkflowModel.Status.RUNNING, createTime: System.currentTimeMillis())\n\n        when: 'serialize workflow'\n        def workflowJson = objectMapper.writeValueAsString(workflow)\n\n        then:\n        !workflowJson.contains('failedReferenceTaskNames')\n        // workflowTask\n        !workflowJson.contains('decisionCases')\n        !workflowJson.contains('defaultCase')\n        !workflowJson.contains('forkTasks')\n        !workflowJson.contains('joinOn')\n        !workflowJson.contains('defaultExclusiveJoinTask')\n        !workflowJson.contains('loopOver')\n    }\n\n    def \"verify task serialization\"() {\n        given: 'define a workflow and tasks for this workflow'\n        String workflowId = new IDGenerator().generate()\n        WorkflowTask workflowTask = new WorkflowTask(taskDefinition: new TaskDef(concurrentExecLimit: 2))\n        TaskModel task = new TaskModel(workflowInstanceId: workflowId, taskType: UUID.randomUUID().toString(), referenceTaskName: UUID.randomUUID().toString(), status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate(), workflowTask: workflowTask)\n\n        when: 'serialize task'\n        def taskJson = objectMapper.writeValueAsString(task)\n\n        then:\n        !taskJson.contains('decisionCases')\n        !taskJson.contains('defaultCase')\n        !taskJson.contains('forkTasks')\n        !taskJson.contains('joinOn')\n        !taskJson.contains('defaultExclusiveJoinTask')\n    }\n\n    def \"serde of workflow with large number of tasks\"() {\n        given: 'create a workflow and tasks for this workflow'\n        String workflowId = new IDGenerator().generate()\n\n        def workflowTasks = (0..999)\n                .collect { new WorkflowTask(name: it, taskReferenceName: it, taskDefinition: new TaskDef(name: it)) }\n        WorkflowDef workflowDef = new WorkflowDef(name: UUID.randomUUID().toString(), version: 1, tasks: workflowTasks)\n\n        def taskList = (0..999)\n                .collect { new TaskModel(workflowInstanceId: workflowId, taskType: it, referenceTaskName: it, status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate(), workflowTask: workflowTasks.get(it)) }\n\n        WorkflowModel workflow = new WorkflowModel(workflowDefinition: workflowDef, workflowId: workflowId, status: WorkflowModel.Status.RUNNING, createTime: System.currentTimeMillis())\n\n        and: 'create workflow'\n        executionDAO.createWorkflow(workflow)\n\n        when: 'add the tasks to the datastore'\n        def start_time = System.currentTimeMillis()\n        executionDAO.createTasks(taskList)\n        println(\"Create 1000 tasks, duration: ${System.currentTimeMillis() - start_time} ms\")\n\n        then:\n        def workflowMetadata = executionDAO.getWorkflowMetadata(workflowId)\n        workflowMetadata.totalTasks == 1000\n\n        when: 'read workflow with tasks'\n        start_time = System.currentTimeMillis()\n        WorkflowModel found = executionDAO.getWorkflow(workflowId, true)\n        println(\"Get workflow with 1000 tasks, duration: ${System.currentTimeMillis() - start_time} ms\")\n\n        then:\n        found != null\n        workflow.workflowId == found.workflowId\n        found.tasks != null && found.tasks.size() == 1000\n        (0..999).collect {found.getTaskByRefName(\"\"+it) == taskList.get(it)}\n    }\n\n    private static EventExecution getEventExecution(String id, String msgId, String name, String event) {\n        EventExecution eventExecution = new EventExecution(id, msgId);\n        eventExecution.setName(name);\n        eventExecution.setEvent(event);\n        eventExecution.setStatus(EventExecution.Status.IN_PROGRESS);\n        return eventExecution;\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/test/groovy/com/netflix/conductor/cassandra/dao/CassandraMetadataDAOSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\n\nimport spock.lang.Subject\n\nclass CassandraMetadataDAOSpec extends CassandraSpec {\n\n    @Subject\n    CassandraMetadataDAO metadataDAO\n\n    def setup() {\n        metadataDAO = new CassandraMetadataDAO(session, objectMapper, cassandraProperties, statements)\n    }\n\n    def cleanup() {\n\n    }\n\n    def \"CRUD on WorkflowDef\"() throws Exception {\n        given:\n        String name = \"workflow_def_1\"\n        int version = 1\n\n        WorkflowDef workflowDef = new WorkflowDef()\n        workflowDef.setName(name)\n        workflowDef.setVersion(version)\n        workflowDef.setOwnerEmail(\"test@junit.com\")\n\n        when: 'create workflow definition'\n        metadataDAO.createWorkflowDef(workflowDef)\n\n        then: // fetch the workflow definition\n        def defOptional = metadataDAO.getWorkflowDef(name, version)\n        defOptional.present\n        defOptional.get() == workflowDef\n\n        and: // register a higher version\n        int higherVersion = 2\n        workflowDef.setVersion(higherVersion)\n        workflowDef.setDescription(\"higher version\")\n\n        when: // register the higher version definition\n        metadataDAO.createWorkflowDef(workflowDef)\n        defOptional = metadataDAO.getWorkflowDef(name, higherVersion)\n\n        then: // fetch the higher version\n        defOptional.present\n        defOptional.get() == workflowDef\n\n        when: // fetch latest version\n        defOptional = metadataDAO.getLatestWorkflowDef(name)\n\n        then:\n        defOptional && defOptional.present\n        defOptional.get() == workflowDef\n\n        when: // modify the definition\n        workflowDef.setOwnerEmail(\"test@junit.com\")\n        metadataDAO.updateWorkflowDef(workflowDef)\n        defOptional = metadataDAO.getWorkflowDef(name, higherVersion)\n\n        then: // fetch the workflow definition\n        defOptional.present\n        defOptional.get() == workflowDef\n\n        when: // delete workflow def\n        metadataDAO.removeWorkflowDef(name, higherVersion)\n        defOptional = metadataDAO.getWorkflowDef(name, higherVersion)\n\n        then:\n        defOptional.empty\n    }\n\n    def \"CRUD on TaskDef\"() {\n        given:\n        String task1Name = \"task1\"\n        String task2Name = \"task2\"\n\n        when: // fetch all task defs\n        def taskDefList = metadataDAO.getAllTaskDefs()\n\n        then:\n        taskDefList.empty\n\n        when: // register a task definition\n        TaskDef taskDef = new TaskDef()\n        taskDef.setName(task1Name)\n        metadataDAO.createTaskDef(taskDef)\n        taskDefList = metadataDAO.getAllTaskDefs()\n\n        then: // fetch all task defs\n        taskDefList && taskDefList.size() == 1\n\n        when: // fetch the task def\n        def returnTaskDef = metadataDAO.getTaskDef(task1Name)\n\n        then:\n        returnTaskDef == taskDef\n\n        when: // register another task definition\n        TaskDef taskDef1 = new TaskDef()\n        taskDef1.setName(task2Name)\n        metadataDAO.createTaskDef(taskDef1)\n        // fetch all task defs\n        taskDefList = metadataDAO.getAllTaskDefs()\n\n        then:\n        taskDefList && taskDefList.size() == 2\n\n        when: // update task def\n        taskDef.setOwnerEmail(\"juni@test.com\")\n        metadataDAO.updateTaskDef(taskDef)\n        returnTaskDef = metadataDAO.getTaskDef(task1Name)\n\n        then:\n        returnTaskDef == taskDef\n\n        when: // delete task def\n        metadataDAO.removeTaskDef(task2Name)\n        taskDefList = metadataDAO.getAllTaskDefs()\n\n        then:\n        taskDefList && taskDefList.size() == 1\n        // fetch deleted task def\n        metadataDAO.getTaskDef(task2Name) == null\n    }\n\n    def \"set default response timeout when not set\"() {\n        given:\n        String task1Name = \"task1\"\n\n        when: // register a task definition\n        TaskDef taskDef = new TaskDef()\n        taskDef.setName(task1Name)\n        taskDef.setResponseTimeoutSeconds(0)\n        metadataDAO.createTaskDef(taskDef)\n        def returnTaskDef = metadataDAO.getTaskDef(task1Name)\n\n        then:\n        returnTaskDef.getResponseTimeoutSeconds() == 3600\n\n        when: // register another task definition\n        taskDef.setTimeoutSeconds(200)\n        taskDef.setResponseTimeoutSeconds(0)\n        metadataDAO.updateTaskDef(taskDef)\n        // fetch all task defs\n        def taskDefList = metadataDAO.getAllTaskDefs()\n\n        then:\n        taskDefList && taskDefList.size() == 1\n        taskDefList.get(0).getResponseTimeoutSeconds() == 199\n\n    }\n\n    def \"Get All WorkflowDef\"() {\n        when:\n        metadataDAO.removeWorkflowDef(\"workflow_def_1\", 1)\n        WorkflowDef workflowDef = new WorkflowDef()\n        workflowDef.setName(\"workflow_def_1\")\n        workflowDef.setVersion(1)\n        workflowDef.setOwnerEmail(\"test@junit.com\")\n        metadataDAO.createWorkflowDef(workflowDef)\n\n        workflowDef.setName(\"workflow_def_2\")\n        metadataDAO.createWorkflowDef(workflowDef)\n        workflowDef.setVersion(2)\n        metadataDAO.createWorkflowDef(workflowDef)\n\n        workflowDef.setName(\"workflow_def_3\")\n        workflowDef.setVersion(1)\n        metadataDAO.createWorkflowDef(workflowDef)\n        workflowDef.setVersion(2)\n        metadataDAO.createWorkflowDef(workflowDef)\n        workflowDef.setVersion(3)\n        metadataDAO.createWorkflowDef(workflowDef)\n\n        then: // fetch the workflow definition\n        def allDefsLatestVersions = metadataDAO.getAllWorkflowDefsLatestVersions()\n        Map<String, WorkflowDef> allDefsMap = allDefsLatestVersions.collectEntries {wfDef -> [wfDef.getName(), wfDef]}\n        allDefsMap.get(\"workflow_def_1\").getVersion() == 1\n        allDefsMap.get(\"workflow_def_2\").getVersion() == 2\n        allDefsMap.get(\"workflow_def_3\").getVersion() == 3\n    }\n\n    def \"parse index string\"() {\n        expect:\n        def pair = metadataDAO.getWorkflowNameAndVersion(nameVersionStr)\n        pair.left == workflowName\n        pair.right == version\n\n        where:\n        nameVersionStr << ['name/1', 'namespace/name/3', '/namespace/name_with_lodash/2', 'name//4', 'name-with$%/895']\n        workflowName << ['name', 'namespace/name', '/namespace/name_with_lodash', 'name/', 'name-with$%']\n        version << [1, 3, 2, 4, 895]\n    }\n\n    def \"parse index string - incorrect values\"() {\n        when:\n        metadataDAO.getWorkflowNameAndVersion(\"name_with_no_version\")\n\n        then:\n        def ex = thrown(IllegalStateException.class)\n        println(ex.message)\n\n        when:\n        metadataDAO.getWorkflowNameAndVersion(\"name_with_no_version/\")\n\n        then:\n        ex = thrown(IllegalStateException.class)\n        println(ex.message)\n\n        when:\n        metadataDAO.getWorkflowNameAndVersion(\"name/non_number_version\")\n\n        then:\n        ex = thrown(IllegalStateException.class)\n        println(ex.message)\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/test/groovy/com/netflix/conductor/cassandra/dao/CassandraSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.dao\n\nimport java.time.Duration\n\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.test.context.ContextConfiguration\nimport org.testcontainers.containers.CassandraContainer\nimport org.testcontainers.spock.Testcontainers\n\nimport com.netflix.conductor.cassandra.config.CassandraProperties\nimport com.netflix.conductor.cassandra.util.Statements\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration\n\nimport com.datastax.driver.core.ConsistencyLevel\nimport com.datastax.driver.core.Session\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport groovy.transform.PackageScope\nimport spock.lang.Shared\nimport spock.lang.Specification\n\n@ContextConfiguration(classes = [TestObjectMapperConfiguration.class])\n@Testcontainers\n@PackageScope\nabstract class CassandraSpec extends Specification {\n\n    @Shared\n    CassandraContainer cassandra = new CassandraContainer()\n\n    @Shared\n    Session session\n\n    @Autowired\n    ObjectMapper objectMapper\n\n    CassandraProperties cassandraProperties\n    Statements statements\n\n    def setupSpec() {\n        session = cassandra.cluster.newSession()\n    }\n\n    def setup() {\n        String keyspaceName = \"junit\"\n        cassandraProperties = Mock(CassandraProperties.class) {\n            getKeyspace() >> keyspaceName\n            getReplicationStrategy() >> \"SimpleStrategy\"\n            getReplicationFactorKey() >> \"replication_factor\"\n            getReplicationFactorValue() >> 1\n            getReadConsistencyLevel() >> ConsistencyLevel.LOCAL_ONE\n            getWriteConsistencyLevel() >> ConsistencyLevel.LOCAL_ONE\n            getTaskDefCacheRefreshInterval() >> Duration.ofSeconds(60)\n            getEventHandlerCacheRefreshInterval() >> Duration.ofSeconds(60)\n            getEventExecutionPersistenceTtl() >> Duration.ofSeconds(5)\n        }\n\n        statements = new Statements(keyspaceName)\n    }\n}\n"
  },
  {
    "path": "cassandra-persistence/src/test/groovy/com/netflix/conductor/cassandra/util/StatementsSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.cassandra.util\n\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nclass StatementsSpec extends Specification {\n\n    @Subject\n    Statements subject\n\n    def setup() {\n        subject = new Statements('test')\n    }\n\n    def \"verify statements\"() {\n        when:\n        subject\n\n        then:\n        with(subject) {\n            insertWorkflowDefStatement == \"INSERT INTO test.workflow_definitions (workflow_def_name,version,workflow_definition) VALUES (?,?,?) IF NOT EXISTS;\"\n            insertTaskDefStatement == \"INSERT INTO test.task_definitions (task_defs,task_def_name,task_definition) VALUES ('task_defs',?,?);\"\n            selectWorkflowDefStatement == \"SELECT workflow_definition FROM test.workflow_definitions WHERE workflow_def_name=? AND version=?;\"\n            selectAllWorkflowDefVersionsByNameStatement == \"SELECT * FROM test.workflow_definitions WHERE workflow_def_name=?;\"\n            selectAllWorkflowDefsStatement == \"SELECT * FROM test.workflow_defs_index WHERE workflow_def_version_index=?;\"\n            selectTaskDefStatement == \"SELECT task_definition FROM test.task_definitions WHERE task_defs='task_defs' AND task_def_name=?;\"\n            selectAllTaskDefsStatement == \"SELECT * FROM test.task_definitions WHERE task_defs=?;\"\n            updateWorkflowDefStatement == \"UPDATE test.workflow_definitions SET workflow_definition=? WHERE workflow_def_name=? AND version=?;\"\n            deleteWorkflowDefStatement == \"DELETE FROM test.workflow_definitions WHERE workflow_def_name=? AND version=?;\"\n            deleteWorkflowDefIndexStatement == \"DELETE FROM test.workflow_defs_index WHERE workflow_def_version_index=? AND workflow_def_name_version=?;\"\n            deleteTaskDefStatement == \"DELETE FROM test.task_definitions WHERE task_defs='task_defs' AND task_def_name=?;\"\n            insertWorkflowStatement == \"INSERT INTO test.workflows (workflow_id,shard_id,task_id,entity,payload,total_tasks,total_partitions) VALUES (?,?,?,'workflow',?,?,?);\"\n            insertTaskStatement == \"INSERT INTO test.workflows (workflow_id,shard_id,task_id,entity,payload) VALUES (?,?,?,'task',?);\"\n            insertEventExecutionStatement == \"INSERT INTO test.event_executions (message_id,event_handler_name,event_execution_id,payload) VALUES (?,?,?,?) IF NOT EXISTS;\"\n            selectTotalStatement == \"SELECT total_tasks,total_partitions FROM test.workflows WHERE workflow_id=? AND shard_id=1;\"\n            selectTaskStatement == \"SELECT payload FROM test.workflows WHERE workflow_id=? AND shard_id=? AND entity='task' AND task_id=?;\"\n            selectWorkflowStatement == \"SELECT payload FROM test.workflows WHERE workflow_id=? AND shard_id=1 AND entity='workflow';\"\n            selectWorkflowWithTasksStatement == \"SELECT * FROM test.workflows WHERE workflow_id=? AND shard_id=?;\"\n            selectTaskFromLookupTableStatement == \"SELECT workflow_id FROM test.task_lookup WHERE task_id=?;\"\n            selectTasksFromTaskDefLimitStatement == \"SELECT * FROM test.task_def_limit WHERE task_def_name=?;\"\n            selectAllEventExecutionsForMessageFromEventExecutionsStatement == \"SELECT * FROM test.event_executions WHERE message_id=? AND event_handler_name=?;\"\n            updateWorkflowStatement == \"UPDATE test.workflows SET payload=? WHERE workflow_id=? AND shard_id=1 AND entity='workflow' AND task_id='';\"\n            updateTotalTasksStatement == \"UPDATE test.workflows SET total_tasks=? WHERE workflow_id=? AND shard_id=?;\"\n            updateTotalPartitionsStatement == \"UPDATE test.workflows SET total_partitions=?,total_tasks=? WHERE workflow_id=? AND shard_id=1;\"\n            updateTaskLookupStatement == \"UPDATE test.task_lookup SET workflow_id=? WHERE task_id=?;\"\n            updateTaskDefLimitStatement == \"UPDATE test.task_def_limit SET workflow_id=? WHERE task_def_name=? AND task_id=?;\"\n            updateEventExecutionStatement == \"UPDATE test.event_executions USING TTL ? SET payload=? WHERE message_id=? AND event_handler_name=? AND event_execution_id=?;\"\n            deleteWorkflowStatement == \"DELETE FROM test.workflows WHERE workflow_id=? AND shard_id=?;\"\n            deleteTaskLookupStatement == \"DELETE FROM test.task_lookup WHERE task_id=?;\"\n            deleteTaskStatement == \"DELETE FROM test.workflows WHERE workflow_id=? AND shard_id=? AND entity='task' AND task_id=?;\"\n            deleteTaskDefLimitStatement == \"DELETE FROM test.task_def_limit WHERE task_def_name=? AND task_id=?;\"\n            deleteEventExecutionsStatement == \"DELETE FROM test.event_executions WHERE message_id=? AND event_handler_name=? AND event_execution_id=?;\"\n            insertEventHandlerStatement == \"INSERT INTO test.event_handlers (handlers,event_handler_name,event_handler) VALUES ('handlers',?,?);\"\n            selectAllEventHandlersStatement == \"SELECT * FROM test.event_handlers WHERE handlers=?;\"\n            deleteEventHandlerStatement == \"DELETE FROM test.event_handlers WHERE handlers='handlers' AND event_handler_name=?;\"\n        }\n    }\n}\n"
  },
  {
    "path": "client/build.gradle",
    "content": "buildscript {\n    repositories {\n        maven {\n            url \"https://plugins.gradle.org/m2/\"\n        }\n    }\n    dependencies {\n        classpath \"gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.5\"\n    }\n}\n\napply plugin: 'groovy'\n\nconfigurations.all {\n    exclude group: 'amazon', module: 'aws-java-sdk'\n}\n\ndependencies {\n    compileOnly 'org.jetbrains:annotations:23.0.0'\n\n    implementation project(':conductor-common')\n    implementation \"com.sun.jersey:jersey-client:${revJersey}\"\n    implementation \"javax.ws.rs:javax.ws.rs-api:${revJAXRS}\"\n    implementation \"org.glassfish.jersey.core:jersey-common:${revJerseyCommon}\"\n\n    implementation \"com.netflix.spectator:spectator-api:${revSpectator}\"\n    implementation (\"com.netflix.eureka:eureka-client:${revEurekaClient}\") {\n        exclude group: 'com.google.guava', module: 'guava'\n    }\n    implementation \"com.amazonaws:aws-java-sdk-core:${revAwsSdk}\"\n\n    implementation \"com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider\"\n    implementation \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\"\n\n    implementation \"org.apache.commons:commons-lang3\"\n    implementation \"commons-io:commons-io:${revCommonsIo}\"\n\n    implementation \"org.slf4j:slf4j-api\"\n\n    testImplementation \"org.powermock:powermock-module-junit4:${revPowerMock}\"\n    testImplementation \"org.powermock:powermock-api-mockito2:${revPowerMock}\"\n\n    testImplementation \"org.codehaus.groovy:groovy-all:${revGroovy}\"\n    testImplementation \"org.spockframework:spock-core:${revSpock}\"\n    testImplementation \"org.spockframework:spock-spring:${revSpock}\"\n}\n"
  },
  {
    "path": "client/spotbugsExclude.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FindBugsFilter\n        xmlns=\"https://github.com/spotbugs/filter/3.0.0\"\n        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:schemaLocation=\"https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd\">\n    <Match>\n        <Bug pattern=\"EI_EXPOSE_REP,EI_EXPOSE_REP2\" />\n    </Match>\n</FindBugsFilter>\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/automator/PollingSemaphore.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.automator;\n\nimport java.util.concurrent.Semaphore;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * A class wrapping a semaphore which holds the number of permits available for polling and\n * executing tasks.\n */\nclass PollingSemaphore {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(PollingSemaphore.class);\n    private final Semaphore semaphore;\n\n    PollingSemaphore(int numSlots) {\n        LOGGER.debug(\"Polling semaphore initialized with {} permits\", numSlots);\n        semaphore = new Semaphore(numSlots);\n    }\n\n    /** Signals that processing is complete and the specified number of permits can be released. */\n    void complete(int numSlots) {\n        LOGGER.debug(\"Completed execution; releasing permit\");\n        semaphore.release(numSlots);\n    }\n\n    /**\n     * Gets the number of threads available for processing.\n     *\n     * @return number of available permits\n     */\n    int availableSlots() {\n        int available = semaphore.availablePermits();\n        LOGGER.debug(\"Number of available permits: {}\", available);\n        return available;\n    }\n\n    /**\n     * Signals if processing is allowed based on whether specified number of permits can be\n     * acquired.\n     *\n     * @param numSlots the number of permits to acquire\n     * @return {@code true} - if permit is acquired {@code false} - if permit could not be acquired\n     */\n    public boolean acquireSlots(int numSlots) {\n        boolean acquired = semaphore.tryAcquire(numSlots);\n        LOGGER.debug(\"Trying to acquire {} permit: {}\", numSlots, acquired);\n        return acquired;\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/automator/TaskPollExecutor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.automator;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.*;\nimport java.util.function.Function;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.concurrent.BasicThreadFactory;\nimport org.apache.commons.lang3.time.StopWatch;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.appinfo.InstanceInfo.InstanceStatus;\nimport com.netflix.conductor.client.config.PropertyFactory;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.telemetry.MetricsContainer;\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.discovery.EurekaClient;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.patterns.ThreadPoolMonitor;\n\n/**\n * Manages the threadpool used by the workers for execution and server communication (polling and\n * task update).\n */\nclass TaskPollExecutor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskPollExecutor.class);\n\n    private static final Registry REGISTRY = Spectator.globalRegistry();\n\n    private final EurekaClient eurekaClient;\n    private final TaskClient taskClient;\n    private final int updateRetryCount;\n    private final ExecutorService executorService;\n    private final Map<String, PollingSemaphore> pollingSemaphoreMap;\n    private final Map<String /*taskType*/, String /*domain*/> taskToDomain;\n\n    private static final String DOMAIN = \"domain\";\n    private static final String OVERRIDE_DISCOVERY = \"pollOutOfDiscovery\";\n    private static final String ALL_WORKERS = \"all\";\n\n    private static final int LEASE_EXTEND_RETRY_COUNT = 3;\n    private static final double LEASE_EXTEND_DURATION_FACTOR = 0.8;\n    private ScheduledExecutorService leaseExtendExecutorService;\n    Map<String /* ID of the task*/, ScheduledFuture<?>> leaseExtendMap = new HashMap<>();\n\n    TaskPollExecutor(\n            EurekaClient eurekaClient,\n            TaskClient taskClient,\n            int updateRetryCount,\n            Map<String, String> taskToDomain,\n            String workerNamePrefix,\n            Map<String, Integer> taskThreadCount) {\n        this.eurekaClient = eurekaClient;\n        this.taskClient = taskClient;\n        this.updateRetryCount = updateRetryCount;\n        this.taskToDomain = taskToDomain;\n\n        this.pollingSemaphoreMap = new HashMap<>();\n        int totalThreadCount = 0;\n        for (Map.Entry<String, Integer> entry : taskThreadCount.entrySet()) {\n            String taskType = entry.getKey();\n            int count = entry.getValue();\n            totalThreadCount += count;\n            pollingSemaphoreMap.put(taskType, new PollingSemaphore(count));\n        }\n\n        LOGGER.info(\"Initialized the TaskPollExecutor with {} threads\", totalThreadCount);\n        this.executorService =\n                Executors.newFixedThreadPool(\n                        totalThreadCount,\n                        new BasicThreadFactory.Builder()\n                                .namingPattern(workerNamePrefix)\n                                .uncaughtExceptionHandler(uncaughtExceptionHandler)\n                                .build());\n        ThreadPoolMonitor.attach(REGISTRY, (ThreadPoolExecutor) executorService, workerNamePrefix);\n\n        LOGGER.info(\"Initialized the task lease extend executor\");\n        leaseExtendExecutorService =\n                Executors.newSingleThreadScheduledExecutor(\n                        new BasicThreadFactory.Builder()\n                                .namingPattern(\"workflow-lease-extend-%d\")\n                                .daemon(true)\n                                .uncaughtExceptionHandler(uncaughtExceptionHandler)\n                                .build());\n    }\n\n    void pollAndExecute(Worker worker) {\n        Boolean discoveryOverride =\n                Optional.ofNullable(\n                                PropertyFactory.getBoolean(\n                                        worker.getTaskDefName(), OVERRIDE_DISCOVERY, null))\n                        .orElseGet(\n                                () ->\n                                        PropertyFactory.getBoolean(\n                                                ALL_WORKERS, OVERRIDE_DISCOVERY, false));\n\n        if (eurekaClient != null\n                && !eurekaClient.getInstanceRemoteStatus().equals(InstanceStatus.UP)\n                && !discoveryOverride) {\n            LOGGER.debug(\"Instance is NOT UP in discovery - will not poll\");\n            return;\n        }\n\n        if (worker.paused()) {\n            MetricsContainer.incrementTaskPausedCount(worker.getTaskDefName());\n            LOGGER.debug(\"Worker {} has been paused. Not polling anymore!\", worker.getClass());\n            return;\n        }\n\n        String taskType = worker.getTaskDefName();\n        PollingSemaphore pollingSemaphore = getPollingSemaphore(taskType);\n\n        int slotsToAcquire = pollingSemaphore.availableSlots();\n        if (slotsToAcquire <= 0 || !pollingSemaphore.acquireSlots(slotsToAcquire)) {\n            return;\n        }\n        int acquiredTasks = 0;\n        try {\n            String domain =\n                    Optional.ofNullable(PropertyFactory.getString(taskType, DOMAIN, null))\n                            .orElseGet(\n                                    () ->\n                                            Optional.ofNullable(\n                                                            PropertyFactory.getString(\n                                                                    ALL_WORKERS, DOMAIN, null))\n                                                    .orElse(taskToDomain.get(taskType)));\n\n            LOGGER.debug(\"Polling task of type: {} in domain: '{}'\", taskType, domain);\n\n            List<Task> tasks =\n                    MetricsContainer.getPollTimer(taskType)\n                            .record(\n                                    () ->\n                                            taskClient.batchPollTasksInDomain(\n                                                    taskType,\n                                                    domain,\n                                                    worker.getIdentity(),\n                                                    slotsToAcquire,\n                                                    worker.getBatchPollTimeoutInMS()));\n            acquiredTasks = tasks.size();\n            for (Task task : tasks) {\n                if (Objects.nonNull(task) && StringUtils.isNotBlank(task.getTaskId())) {\n                    MetricsContainer.incrementTaskPollCount(taskType, 1);\n                    LOGGER.debug(\n                            \"Polled task: {} of type: {} in domain: '{}', from worker: {}\",\n                            task.getTaskId(),\n                            taskType,\n                            domain,\n                            worker.getIdentity());\n\n                    CompletableFuture<Task> taskCompletableFuture =\n                            CompletableFuture.supplyAsync(\n                                    () -> processTask(task, worker, pollingSemaphore),\n                                    executorService);\n\n                    if (task.getResponseTimeoutSeconds() > 0 && worker.leaseExtendEnabled()) {\n                        ScheduledFuture<?> leaseExtendFuture =\n                                leaseExtendExecutorService.scheduleWithFixedDelay(\n                                        extendLease(task, taskCompletableFuture),\n                                        Math.round(\n                                                task.getResponseTimeoutSeconds()\n                                                        * LEASE_EXTEND_DURATION_FACTOR),\n                                        Math.round(\n                                                task.getResponseTimeoutSeconds()\n                                                        * LEASE_EXTEND_DURATION_FACTOR),\n                                        TimeUnit.SECONDS);\n                        leaseExtendMap.put(task.getTaskId(), leaseExtendFuture);\n                    }\n\n                    taskCompletableFuture.whenComplete(this::finalizeTask);\n                } else {\n                    // no task was returned in the poll, release the permit\n                    pollingSemaphore.complete(1);\n                }\n            }\n        } catch (Exception e) {\n            MetricsContainer.incrementTaskPollErrorCount(worker.getTaskDefName(), e);\n            LOGGER.error(\"Error when polling for tasks\", e);\n        }\n\n        // immediately release unused permits\n        pollingSemaphore.complete(slotsToAcquire - acquiredTasks);\n    }\n\n    void shutdown(int timeout) {\n        shutdownAndAwaitTermination(executorService, timeout);\n        shutdownAndAwaitTermination(leaseExtendExecutorService, timeout);\n        leaseExtendMap.clear();\n    }\n\n    void shutdownAndAwaitTermination(ExecutorService executorService, int timeout) {\n        try {\n            executorService.shutdown();\n            if (executorService.awaitTermination(timeout, TimeUnit.SECONDS)) {\n                LOGGER.debug(\"tasks completed, shutting down\");\n            } else {\n                LOGGER.warn(String.format(\"forcing shutdown after waiting for %s second\", timeout));\n                executorService.shutdownNow();\n            }\n        } catch (InterruptedException ie) {\n            LOGGER.warn(\"shutdown interrupted, invoking shutdownNow\");\n            executorService.shutdownNow();\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler =\n            (thread, error) -> {\n                // JVM may be in unstable state, try to send metrics then exit\n                MetricsContainer.incrementUncaughtExceptionCount();\n                LOGGER.error(\"Uncaught exception. Thread {} will exit now\", thread, error);\n            };\n\n    private Task processTask(Task task, Worker worker, PollingSemaphore pollingSemaphore) {\n        LOGGER.debug(\n                \"Executing task: {} of type: {} in worker: {} at {}\",\n                task.getTaskId(),\n                task.getTaskDefName(),\n                worker.getClass().getSimpleName(),\n                worker.getIdentity());\n        try {\n            executeTask(worker, task);\n        } catch (Throwable t) {\n            task.setStatus(Task.Status.FAILED);\n            TaskResult result = new TaskResult(task);\n            handleException(t, result, worker, task);\n        } finally {\n            pollingSemaphore.complete(1);\n        }\n        return task;\n    }\n\n    private void executeTask(Worker worker, Task task) {\n        StopWatch stopwatch = new StopWatch();\n        stopwatch.start();\n        TaskResult result = null;\n        try {\n            LOGGER.debug(\n                    \"Executing task: {} in worker: {} at {}\",\n                    task.getTaskId(),\n                    worker.getClass().getSimpleName(),\n                    worker.getIdentity());\n            result = worker.execute(task);\n            result.setWorkflowInstanceId(task.getWorkflowInstanceId());\n            result.setTaskId(task.getTaskId());\n            result.setWorkerId(worker.getIdentity());\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Unable to execute task: {} of type: {}\",\n                    task.getTaskId(),\n                    task.getTaskDefName(),\n                    e);\n            if (result == null) {\n                task.setStatus(Task.Status.FAILED);\n                result = new TaskResult(task);\n            }\n            handleException(e, result, worker, task);\n        } finally {\n            stopwatch.stop();\n            MetricsContainer.getExecutionTimer(worker.getTaskDefName())\n                    .record(stopwatch.getTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);\n        }\n\n        LOGGER.debug(\n                \"Task: {} executed by worker: {} at {} with status: {}\",\n                task.getTaskId(),\n                worker.getClass().getSimpleName(),\n                worker.getIdentity(),\n                result.getStatus());\n        updateTaskResult(updateRetryCount, task, result, worker);\n    }\n\n    private void finalizeTask(Task task, Throwable throwable) {\n        if (throwable != null) {\n            LOGGER.error(\n                    \"Error processing task: {} of type: {}\",\n                    task.getTaskId(),\n                    task.getTaskType(),\n                    throwable);\n            MetricsContainer.incrementTaskExecutionErrorCount(task.getTaskType(), throwable);\n        } else {\n            LOGGER.debug(\n                    \"Task:{} of type:{} finished processing with status:{}\",\n                    task.getTaskId(),\n                    task.getTaskDefName(),\n                    task.getStatus());\n            String taskId = task.getTaskId();\n            ScheduledFuture<?> leaseExtendFuture = leaseExtendMap.get(taskId);\n            if (leaseExtendFuture != null) {\n                leaseExtendFuture.cancel(true);\n                leaseExtendMap.remove(taskId);\n            }\n        }\n    }\n\n    private void updateTaskResult(int count, Task task, TaskResult result, Worker worker) {\n        try {\n            // upload if necessary\n            Optional<String> optionalExternalStorageLocation =\n                    retryOperation(\n                            (TaskResult taskResult) -> upload(taskResult, task.getTaskType()),\n                            count,\n                            result,\n                            \"evaluateAndUploadLargePayload\");\n\n            if (optionalExternalStorageLocation.isPresent()) {\n                result.setExternalOutputPayloadStoragePath(optionalExternalStorageLocation.get());\n                result.setOutputData(null);\n            }\n\n            retryOperation(\n                    (TaskResult taskResult) -> {\n                        taskClient.updateTask(taskResult);\n                        return null;\n                    },\n                    count,\n                    result,\n                    \"updateTask\");\n        } catch (Exception e) {\n            worker.onErrorUpdate(task);\n            MetricsContainer.incrementTaskUpdateErrorCount(worker.getTaskDefName(), e);\n            LOGGER.error(\n                    String.format(\n                            \"Failed to update result: %s for task: %s in worker: %s\",\n                            result.toString(), task.getTaskDefName(), worker.getIdentity()),\n                    e);\n        }\n    }\n\n    private Optional<String> upload(TaskResult result, String taskType) {\n        try {\n            return taskClient.evaluateAndUploadLargePayload(result.getOutputData(), taskType);\n        } catch (IllegalArgumentException iae) {\n            result.setReasonForIncompletion(iae.getMessage());\n            result.setOutputData(null);\n            result.setStatus(TaskResult.Status.FAILED_WITH_TERMINAL_ERROR);\n            return Optional.empty();\n        }\n    }\n\n    private <T, R> R retryOperation(Function<T, R> operation, int count, T input, String opName) {\n        int index = 0;\n        while (index < count) {\n            try {\n                return operation.apply(input);\n            } catch (Exception e) {\n                index++;\n                try {\n                    Thread.sleep(500L);\n                } catch (InterruptedException ie) {\n                    LOGGER.error(\"Retry interrupted\", ie);\n                }\n            }\n        }\n        throw new RuntimeException(\"Exhausted retries performing \" + opName);\n    }\n\n    private void handleException(Throwable t, TaskResult result, Worker worker, Task task) {\n        LOGGER.error(String.format(\"Error while executing task %s\", task.toString()), t);\n        MetricsContainer.incrementTaskExecutionErrorCount(worker.getTaskDefName(), t);\n        result.setStatus(TaskResult.Status.FAILED);\n        result.setReasonForIncompletion(\"Error while executing the task: \" + t);\n\n        StringWriter stringWriter = new StringWriter();\n        t.printStackTrace(new PrintWriter(stringWriter));\n        result.log(stringWriter.toString());\n\n        updateTaskResult(updateRetryCount, task, result, worker);\n    }\n\n    private PollingSemaphore getPollingSemaphore(String taskType) {\n        return pollingSemaphoreMap.get(taskType);\n    }\n\n    private Runnable extendLease(Task task, CompletableFuture<Task> taskCompletableFuture) {\n        return () -> {\n            if (taskCompletableFuture.isDone()) {\n                LOGGER.warn(\n                        \"Task processing for {} completed, but its lease extend was not cancelled\",\n                        task.getTaskId());\n                return;\n            }\n            LOGGER.info(\"Attempting to extend lease for {}\", task.getTaskId());\n            try {\n                TaskResult result = new TaskResult(task);\n                result.setExtendLease(true);\n                retryOperation(\n                        (TaskResult taskResult) -> {\n                            taskClient.updateTask(taskResult);\n                            return null;\n                        },\n                        LEASE_EXTEND_RETRY_COUNT,\n                        result,\n                        \"extend lease\");\n                MetricsContainer.incrementTaskLeaseExtendCount(task.getTaskDefName(), 1);\n            } catch (Exception e) {\n                MetricsContainer.incrementTaskLeaseExtendErrorCount(task.getTaskDefName(), e);\n                LOGGER.error(\"Failed to extend lease for {}\", task.getTaskId(), e);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/automator/TaskRunnerConfigurer.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.automator;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.Validate;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.discovery.EurekaClient;\n\n/** Configures automated polling of tasks and execution via the registered {@link Worker}s. */\npublic class TaskRunnerConfigurer {\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskRunnerConfigurer.class);\n    private static final String INVALID_THREAD_COUNT =\n            \"Invalid worker thread count specified, use either shared thread pool or config thread count per task\";\n    private static final String MISSING_TASK_THREAD_COUNT =\n            \"Missing task thread count config for %s\";\n\n    private ScheduledExecutorService scheduledExecutorService;\n\n    private final EurekaClient eurekaClient;\n    private final TaskClient taskClient;\n    private final List<Worker> workers = new LinkedList<>();\n    private final int sleepWhenRetry;\n    private final int updateRetryCount;\n    @Deprecated private final int threadCount;\n    private final int shutdownGracePeriodSeconds;\n    private final String workerNamePrefix;\n    private final Map<String /*taskType*/, String /*domain*/> taskToDomain;\n    private final Map<String /*taskType*/, Integer /*threadCount*/> taskThreadCount;\n\n    private TaskPollExecutor taskPollExecutor;\n\n    /**\n     * @see TaskRunnerConfigurer.Builder\n     * @see TaskRunnerConfigurer#init()\n     */\n    private TaskRunnerConfigurer(Builder builder) {\n        // only allow either shared thread pool or per task thread pool\n        if (builder.threadCount != -1 && !builder.taskThreadCount.isEmpty()) {\n            LOGGER.error(INVALID_THREAD_COUNT);\n            throw new ConductorClientException(INVALID_THREAD_COUNT);\n        } else if (!builder.taskThreadCount.isEmpty()) {\n            for (Worker worker : builder.workers) {\n                if (!builder.taskThreadCount.containsKey(worker.getTaskDefName())) {\n                    LOGGER.info(\n                            \"No thread count specified for task type {}, default to 1 thread\",\n                            worker.getTaskDefName());\n                    builder.taskThreadCount.put(worker.getTaskDefName(), 1);\n                }\n                workers.add(worker);\n            }\n            this.taskThreadCount = builder.taskThreadCount;\n            this.threadCount = -1;\n        } else {\n            Set<String> taskTypes = new HashSet<>();\n            for (Worker worker : builder.workers) {\n                taskTypes.add(worker.getTaskDefName());\n                workers.add(worker);\n            }\n            this.threadCount = (builder.threadCount == -1) ? workers.size() : builder.threadCount;\n            // shared thread pool will be evenly split between task types\n            int splitThreadCount = threadCount / taskTypes.size();\n            this.taskThreadCount =\n                    taskTypes.stream().collect(Collectors.toMap(v -> v, v -> splitThreadCount));\n        }\n\n        this.eurekaClient = builder.eurekaClient;\n        this.taskClient = builder.taskClient;\n        this.sleepWhenRetry = builder.sleepWhenRetry;\n        this.updateRetryCount = builder.updateRetryCount;\n        this.workerNamePrefix = builder.workerNamePrefix;\n        this.taskToDomain = builder.taskToDomain;\n        this.shutdownGracePeriodSeconds = builder.shutdownGracePeriodSeconds;\n    }\n\n    /** Builder used to create the instances of TaskRunnerConfigurer */\n    public static class Builder {\n\n        private String workerNamePrefix = \"workflow-worker-%d\";\n        private int sleepWhenRetry = 500;\n        private int updateRetryCount = 3;\n        @Deprecated private int threadCount = -1;\n        private int shutdownGracePeriodSeconds = 10;\n        private final Iterable<Worker> workers;\n        private EurekaClient eurekaClient;\n        private final TaskClient taskClient;\n        private Map<String /*taskType*/, String /*domain*/> taskToDomain = new HashMap<>();\n        private Map<String /*taskType*/, Integer /*threadCount*/> taskThreadCount = new HashMap<>();\n\n        public Builder(TaskClient taskClient, Iterable<Worker> workers) {\n            Validate.notNull(taskClient, \"TaskClient cannot be null\");\n            Validate.notNull(workers, \"Workers cannot be null\");\n            this.taskClient = taskClient;\n            this.workers = workers;\n        }\n\n        /**\n         * @param workerNamePrefix prefix to be used for worker names, defaults to workflow-worker-\n         *     if not supplied.\n         * @return Returns the current instance.\n         */\n        public Builder withWorkerNamePrefix(String workerNamePrefix) {\n            this.workerNamePrefix = workerNamePrefix;\n            return this;\n        }\n\n        /**\n         * @param sleepWhenRetry time in milliseconds, for which the thread should sleep when task\n         *     update call fails, before retrying the operation.\n         * @return Returns the current instance.\n         */\n        public Builder withSleepWhenRetry(int sleepWhenRetry) {\n            this.sleepWhenRetry = sleepWhenRetry;\n            return this;\n        }\n\n        /**\n         * @param updateRetryCount number of times to retry the failed updateTask operation\n         * @return Builder instance\n         * @see #withSleepWhenRetry(int)\n         */\n        public Builder withUpdateRetryCount(int updateRetryCount) {\n            this.updateRetryCount = updateRetryCount;\n            return this;\n        }\n\n        /**\n         * @param threadCount # of threads assigned to the workers. Should be at-least the size of\n         *     taskWorkers to avoid starvation in a busy system.\n         * @return Builder instance\n         * @deprecated Use {@link TaskRunnerConfigurer.Builder#withTaskThreadCount(Map)} instead.\n         */\n        @Deprecated\n        public Builder withThreadCount(int threadCount) {\n            if (threadCount < 1) {\n                throw new IllegalArgumentException(\"No. of threads cannot be less than 1\");\n            }\n            this.threadCount = threadCount;\n            return this;\n        }\n\n        /**\n         * @param shutdownGracePeriodSeconds waiting seconds before forcing shutdown of your worker\n         * @return Builder instance\n         */\n        public Builder withShutdownGracePeriodSeconds(int shutdownGracePeriodSeconds) {\n            if (shutdownGracePeriodSeconds < 1) {\n                throw new IllegalArgumentException(\n                        \"Seconds of shutdownGracePeriod cannot be less than 1\");\n            }\n            this.shutdownGracePeriodSeconds = shutdownGracePeriodSeconds;\n            return this;\n        }\n\n        /**\n         * @param eurekaClient Eureka client - used to identify if the server is in discovery or\n         *     not. When the server goes out of discovery, the polling is terminated. If passed\n         *     null, discovery check is not done.\n         * @return Builder instance\n         */\n        public Builder withEurekaClient(EurekaClient eurekaClient) {\n            this.eurekaClient = eurekaClient;\n            return this;\n        }\n\n        public Builder withTaskToDomain(Map<String, String> taskToDomain) {\n            this.taskToDomain = taskToDomain;\n            return this;\n        }\n\n        public Builder withTaskThreadCount(Map<String, Integer> taskThreadCount) {\n            this.taskThreadCount = taskThreadCount;\n            return this;\n        }\n\n        /**\n         * Builds an instance of the TaskRunnerConfigurer.\n         *\n         * <p>Please see {@link TaskRunnerConfigurer#init()} method. The method must be called after\n         * this constructor for the polling to start.\n         */\n        public TaskRunnerConfigurer build() {\n            return new TaskRunnerConfigurer(this);\n        }\n    }\n\n    /**\n     * @return Thread Count for the shared executor pool\n     */\n    @Deprecated\n    public int getThreadCount() {\n        return threadCount;\n    }\n\n    /**\n     * @return Thread Count for individual task type\n     */\n    public Map<String, Integer> getTaskThreadCount() {\n        return taskThreadCount;\n    }\n\n    /**\n     * @return seconds before forcing shutdown of worker\n     */\n    public int getShutdownGracePeriodSeconds() {\n        return shutdownGracePeriodSeconds;\n    }\n\n    /**\n     * @return sleep time in millisecond before task update retry is done when receiving error from\n     *     the Conductor server\n     */\n    public int getSleepWhenRetry() {\n        return sleepWhenRetry;\n    }\n\n    /**\n     * @return Number of times updateTask should be retried when receiving error from Conductor\n     *     server\n     */\n    public int getUpdateRetryCount() {\n        return updateRetryCount;\n    }\n\n    /**\n     * @return prefix used for worker names\n     */\n    public String getWorkerNamePrefix() {\n        return workerNamePrefix;\n    }\n\n    /**\n     * Starts the polling. Must be called after {@link TaskRunnerConfigurer.Builder#build()} method.\n     */\n    public synchronized void init() {\n        this.taskPollExecutor =\n                new TaskPollExecutor(\n                        eurekaClient,\n                        taskClient,\n                        updateRetryCount,\n                        taskToDomain,\n                        workerNamePrefix,\n                        taskThreadCount);\n\n        this.scheduledExecutorService = Executors.newScheduledThreadPool(workers.size());\n        workers.forEach(\n                worker ->\n                        scheduledExecutorService.scheduleWithFixedDelay(\n                                () -> taskPollExecutor.pollAndExecute(worker),\n                                worker.getPollingInterval(),\n                                worker.getPollingInterval(),\n                                TimeUnit.MILLISECONDS));\n    }\n\n    /**\n     * Invoke this method within a PreDestroy block within your application to facilitate a graceful\n     * shutdown of your worker, during process termination.\n     */\n    public void shutdown() {\n        taskPollExecutor.shutdownAndAwaitTermination(\n                scheduledExecutorService, shutdownGracePeriodSeconds);\n        taskPollExecutor.shutdown(shutdownGracePeriodSeconds);\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/config/ConductorClientConfiguration.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.config;\n\npublic interface ConductorClientConfiguration {\n\n    /**\n     * @return the workflow input payload size threshold in KB, beyond which the payload will be\n     *     processed based on {@link\n     *     ConductorClientConfiguration#isExternalPayloadStorageEnabled()}.\n     */\n    int getWorkflowInputPayloadThresholdKB();\n\n    /**\n     * @return the max value of workflow input payload size threshold in KB, beyond which the\n     *     payload will be rejected regardless external payload storage is enabled.\n     */\n    int getWorkflowInputMaxPayloadThresholdKB();\n\n    /**\n     * @return the task output payload size threshold in KB, beyond which the payload will be\n     *     processed based on {@link\n     *     ConductorClientConfiguration#isExternalPayloadStorageEnabled()}.\n     */\n    int getTaskOutputPayloadThresholdKB();\n\n    /**\n     * @return the max value of task output payload size threshold in KB, beyond which the payload\n     *     will be rejected regardless external payload storage is enabled.\n     */\n    int getTaskOutputMaxPayloadThresholdKB();\n\n    /**\n     * @return the flag which controls the use of external storage for storing workflow/task input\n     *     and output JSON payloads with size greater than threshold. If it is set to true, the\n     *     payload is stored in external location. If it is set to false, the payload is rejected\n     *     and the task/workflow execution fails.\n     */\n    boolean isExternalPayloadStorageEnabled();\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/config/DefaultConductorClientConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.config;\n\n/**\n * A default implementation of {@link ConductorClientConfiguration} where external payload storage\n * is disabled.\n */\npublic class DefaultConductorClientConfiguration implements ConductorClientConfiguration {\n\n    @Override\n    public int getWorkflowInputPayloadThresholdKB() {\n        return 5120;\n    }\n\n    @Override\n    public int getWorkflowInputMaxPayloadThresholdKB() {\n        return 10240;\n    }\n\n    @Override\n    public int getTaskOutputPayloadThresholdKB() {\n        return 3072;\n    }\n\n    @Override\n    public int getTaskOutputMaxPayloadThresholdKB() {\n        return 10240;\n    }\n\n    @Override\n    public boolean isExternalPayloadStorageEnabled() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/config/PropertyFactory.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.config;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport com.netflix.config.DynamicProperty;\n\n/** Used to configure the Conductor workers using properties. */\npublic class PropertyFactory {\n\n    private final DynamicProperty global;\n    private final DynamicProperty local;\n\n    private static final String PROPERTY_PREFIX = \"conductor.worker\";\n\n    private static final ConcurrentHashMap<String, PropertyFactory> PROPERTY_FACTORY_MAP =\n            new ConcurrentHashMap<>();\n\n    private PropertyFactory(String prefix, String propName, String workerName) {\n        this.global = DynamicProperty.getInstance(prefix + \".\" + propName);\n        this.local = DynamicProperty.getInstance(prefix + \".\" + workerName + \".\" + propName);\n    }\n\n    /**\n     * @param defaultValue Default Value\n     * @return Returns the value as integer. If not value is set (either global or worker specific),\n     *     then returns the default value.\n     */\n    public Integer getInteger(int defaultValue) {\n        Integer value = local.getInteger();\n        if (value == null) {\n            value = global.getInteger(defaultValue);\n        }\n        return value;\n    }\n\n    /**\n     * @param defaultValue Default Value\n     * @return Returns the value as String. If not value is set (either global or worker specific),\n     *     then returns the default value.\n     */\n    public String getString(String defaultValue) {\n        String value = local.getString();\n        if (value == null) {\n            value = global.getString(defaultValue);\n        }\n        return value;\n    }\n\n    /**\n     * @param defaultValue Default Value\n     * @return Returns the value as Boolean. If not value is set (either global or worker specific),\n     *     then returns the default value.\n     */\n    public Boolean getBoolean(Boolean defaultValue) {\n        Boolean value = local.getBoolean();\n        if (value == null) {\n            value = global.getBoolean(defaultValue);\n        }\n        return value;\n    }\n\n    public static Integer getInteger(String workerName, String property, Integer defaultValue) {\n        return getPropertyFactory(workerName, property).getInteger(defaultValue);\n    }\n\n    public static Boolean getBoolean(String workerName, String property, Boolean defaultValue) {\n        return getPropertyFactory(workerName, property).getBoolean(defaultValue);\n    }\n\n    public static String getString(String workerName, String property, String defaultValue) {\n        return getPropertyFactory(workerName, property).getString(defaultValue);\n    }\n\n    private static PropertyFactory getPropertyFactory(String workerName, String property) {\n        String key = property + \".\" + workerName;\n        return PROPERTY_FACTORY_MAP.computeIfAbsent(\n                key, t -> new PropertyFactory(PROPERTY_PREFIX, property, workerName));\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/exception/ConductorClientException.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.exception;\n\nimport java.util.List;\n\nimport com.netflix.conductor.common.validation.ErrorResponse;\nimport com.netflix.conductor.common.validation.ValidationError;\n\n/** Client exception thrown from Conductor api clients. */\npublic class ConductorClientException extends RuntimeException {\n\n    private int status;\n    private String message;\n    private String instance;\n    private String code;\n    private boolean retryable;\n\n    public List<ValidationError> getValidationErrors() {\n        return validationErrors;\n    }\n\n    public void setValidationErrors(List<ValidationError> validationErrors) {\n        this.validationErrors = validationErrors;\n    }\n\n    private List<ValidationError> validationErrors;\n\n    public ConductorClientException() {\n        super();\n    }\n\n    public ConductorClientException(String message) {\n        super(message);\n        this.message = message;\n    }\n\n    public ConductorClientException(String message, Throwable cause) {\n        super(message, cause);\n        this.message = message;\n    }\n\n    public ConductorClientException(int status, String message) {\n        super(message);\n        this.status = status;\n        this.message = message;\n    }\n\n    public ConductorClientException(int status, ErrorResponse errorResponse) {\n        super(errorResponse.getMessage());\n        this.status = status;\n        this.retryable = errorResponse.isRetryable();\n        this.message = errorResponse.getMessage();\n        this.code = errorResponse.getCode();\n        this.instance = errorResponse.getInstance();\n        this.validationErrors = errorResponse.getValidationErrors();\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder builder = new StringBuilder();\n\n        builder.append(getClass().getName()).append(\": \");\n\n        if (this.message != null) {\n            builder.append(message);\n        }\n\n        if (status > 0) {\n            builder.append(\" {status=\").append(status);\n            if (this.code != null) {\n                builder.append(\", code='\").append(code).append(\"'\");\n            }\n\n            builder.append(\", retryable: \").append(retryable);\n        }\n\n        if (this.instance != null) {\n            builder.append(\", instance: \").append(instance);\n        }\n\n        if (this.validationErrors != null) {\n            builder.append(\", validationErrors: \").append(validationErrors.toString());\n        }\n\n        builder.append(\"}\");\n        return builder.toString();\n    }\n\n    public String getCode() {\n        return code;\n    }\n\n    public void setCode(String code) {\n        this.code = code;\n    }\n\n    public void setStatus(int status) {\n        this.status = status;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n\n    public String getInstance() {\n        return instance;\n    }\n\n    public void setInstance(String instance) {\n        this.instance = instance;\n    }\n\n    public boolean isRetryable() {\n        return retryable;\n    }\n\n    public void setRetryable(boolean retryable) {\n        this.retryable = retryable;\n    }\n\n    @Override\n    public String getMessage() {\n        return this.message;\n    }\n\n    public int getStatus() {\n        return this.status;\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/http/ClientBase.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.function.Function;\n\nimport javax.ws.rs.core.UriBuilder;\n\nimport org.apache.commons.lang3.ObjectUtils;\nimport org.apache.commons.lang3.Validate;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.config.ConductorClientConfiguration;\nimport com.netflix.conductor.client.config.DefaultConductorClientConfiguration;\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.common.config.ObjectMapperProvider;\nimport com.netflix.conductor.common.model.BulkResponse;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.common.validation.ErrorResponse;\n\nimport com.fasterxml.jackson.core.Version;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport com.sun.jersey.api.client.ClientHandlerException;\nimport com.sun.jersey.api.client.ClientResponse;\nimport com.sun.jersey.api.client.GenericType;\nimport com.sun.jersey.api.client.UniformInterfaceException;\nimport com.sun.jersey.api.client.WebResource.Builder;\n\n/** Abstract client for the REST server */\npublic abstract class ClientBase {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ClientBase.class);\n\n    protected ClientRequestHandler requestHandler;\n\n    protected String root = \"\";\n\n    protected ObjectMapper objectMapper;\n\n    protected PayloadStorage payloadStorage;\n\n    protected ConductorClientConfiguration conductorClientConfiguration;\n\n    protected ClientBase(\n            ClientRequestHandler requestHandler, ConductorClientConfiguration clientConfiguration) {\n        this.objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n        // https://github.com/FasterXML/jackson-databind/issues/2683\n        if (isNewerJacksonVersion()) {\n            objectMapper.registerModule(new JavaTimeModule());\n        }\n\n        this.requestHandler = requestHandler;\n        this.conductorClientConfiguration =\n                ObjectUtils.defaultIfNull(\n                        clientConfiguration, new DefaultConductorClientConfiguration());\n        this.payloadStorage = new PayloadStorage(this);\n    }\n\n    public void setRootURI(String root) {\n        this.root = root;\n    }\n\n    protected void delete(String url, Object... uriVariables) {\n        deleteWithUriVariables(null, url, uriVariables);\n    }\n\n    protected void deleteWithUriVariables(\n            Object[] queryParams, String url, Object... uriVariables) {\n        delete(queryParams, url, uriVariables, null);\n    }\n\n    protected BulkResponse deleteWithRequestBody(Object[] queryParams, String url, Object body) {\n        return delete(queryParams, url, null, body);\n    }\n\n    private BulkResponse delete(\n            Object[] queryParams, String url, Object[] uriVariables, Object body) {\n        URI uri = null;\n        BulkResponse response = null;\n        try {\n            uri = getURIBuilder(root + url, queryParams).build(uriVariables);\n            response = requestHandler.delete(uri, body);\n        } catch (UniformInterfaceException e) {\n            handleUniformInterfaceException(e, uri);\n        } catch (RuntimeException e) {\n            handleRuntimeException(e, uri);\n        }\n        return response;\n    }\n\n    protected void put(String url, Object[] queryParams, Object request, Object... uriVariables) {\n        URI uri = null;\n        try {\n            uri = getURIBuilder(root + url, queryParams).build(uriVariables);\n            requestHandler.getWebResourceBuilder(uri, request).put();\n        } catch (RuntimeException e) {\n            handleException(uri, e);\n        }\n    }\n\n    protected void postForEntityWithRequestOnly(String url, Object request) {\n        Class<?> type = null;\n        postForEntity(url, request, null, type);\n    }\n\n    protected void postForEntityWithUriVariablesOnly(String url, Object... uriVariables) {\n        Class<?> type = null;\n        postForEntity(url, null, null, type, uriVariables);\n    }\n\n    protected <T> T postForEntity(\n            String url,\n            Object request,\n            Object[] queryParams,\n            Class<T> responseType,\n            Object... uriVariables) {\n        return postForEntity(\n                url,\n                request,\n                queryParams,\n                responseType,\n                builder -> builder.post(responseType),\n                uriVariables);\n    }\n\n    protected <T> T postForEntity(\n            String url,\n            Object request,\n            Object[] queryParams,\n            GenericType<T> responseType,\n            Object... uriVariables) {\n        return postForEntity(\n                url,\n                request,\n                queryParams,\n                responseType,\n                builder -> builder.post(responseType),\n                uriVariables);\n    }\n\n    private <T> T postForEntity(\n            String url,\n            Object request,\n            Object[] queryParams,\n            Object responseType,\n            Function<Builder, T> postWithEntity,\n            Object... uriVariables) {\n        URI uri = null;\n        try {\n            uri = getURIBuilder(root + url, queryParams).build(uriVariables);\n            Builder webResourceBuilder = requestHandler.getWebResourceBuilder(uri, request);\n            if (responseType == null) {\n                webResourceBuilder.post();\n                return null;\n            }\n            return postWithEntity.apply(webResourceBuilder);\n        } catch (UniformInterfaceException e) {\n            handleUniformInterfaceException(e, uri);\n        } catch (RuntimeException e) {\n            handleRuntimeException(e, uri);\n        }\n        return null;\n    }\n\n    protected <T> T getForEntity(\n            String url, Object[] queryParams, Class<T> responseType, Object... uriVariables) {\n        return getForEntity(\n                url, queryParams, response -> response.getEntity(responseType), uriVariables);\n    }\n\n    protected <T> T getForEntity(\n            String url, Object[] queryParams, GenericType<T> responseType, Object... uriVariables) {\n        return getForEntity(\n                url, queryParams, response -> response.getEntity(responseType), uriVariables);\n    }\n\n    private <T> T getForEntity(\n            String url,\n            Object[] queryParams,\n            Function<ClientResponse, T> entityProvider,\n            Object... uriVariables) {\n        URI uri = null;\n        ClientResponse clientResponse;\n        try {\n            uri = getURIBuilder(root + url, queryParams).build(uriVariables);\n            clientResponse = requestHandler.get(uri);\n            if (clientResponse.getStatus() < 300) {\n                return entityProvider.apply(clientResponse);\n            } else {\n                throw new UniformInterfaceException(clientResponse);\n            }\n        } catch (UniformInterfaceException e) {\n            handleUniformInterfaceException(e, uri);\n        } catch (RuntimeException e) {\n            handleRuntimeException(e, uri);\n        }\n        return null;\n    }\n\n    /**\n     * Uses the {@link PayloadStorage} for storing large payloads. Gets the uri for storing the\n     * payload from the server and then uploads to this location\n     *\n     * @param payloadType the {@link\n     *     com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType} to be uploaded\n     * @param payloadBytes the byte array containing the payload\n     * @param payloadSize the size of the payload\n     * @return the path where the payload is stored in external storage\n     */\n    protected String uploadToExternalPayloadStorage(\n            ExternalPayloadStorage.PayloadType payloadType, byte[] payloadBytes, long payloadSize) {\n        Validate.isTrue(\n                payloadType.equals(ExternalPayloadStorage.PayloadType.WORKFLOW_INPUT)\n                        || payloadType.equals(ExternalPayloadStorage.PayloadType.TASK_OUTPUT),\n                \"Payload type must be workflow input or task output\");\n        ExternalStorageLocation externalStorageLocation =\n                payloadStorage.getLocation(ExternalPayloadStorage.Operation.WRITE, payloadType, \"\");\n        payloadStorage.upload(\n                externalStorageLocation.getUri(),\n                new ByteArrayInputStream(payloadBytes),\n                payloadSize);\n        return externalStorageLocation.getPath();\n    }\n\n    /**\n     * Uses the {@link PayloadStorage} for downloading large payloads to be used by the client. Gets\n     * the uri of the payload fom the server and then downloads from this location.\n     *\n     * @param payloadType the {@link\n     *     com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType} to be downloaded\n     * @param path the relative of the payload in external storage\n     * @return the payload object that is stored in external storage\n     */\n    @SuppressWarnings(\"unchecked\")\n    protected Map<String, Object> downloadFromExternalStorage(\n            ExternalPayloadStorage.PayloadType payloadType, String path) {\n        Validate.notBlank(path, \"uri cannot be blank\");\n        ExternalStorageLocation externalStorageLocation =\n                payloadStorage.getLocation(\n                        ExternalPayloadStorage.Operation.READ, payloadType, path);\n        try (InputStream inputStream = payloadStorage.download(externalStorageLocation.getUri())) {\n            return objectMapper.readValue(inputStream, Map.class);\n        } catch (IOException e) {\n            String errorMsg =\n                    String.format(\n                            \"Unable to download payload from external storage location: %s\", path);\n            LOGGER.error(errorMsg, e);\n            throw new ConductorClientException(errorMsg, e);\n        }\n    }\n\n    private UriBuilder getURIBuilder(String path, Object[] queryParams) {\n        if (path == null) {\n            path = \"\";\n        }\n        UriBuilder builder = UriBuilder.fromPath(path);\n        if (queryParams != null) {\n            for (int i = 0; i < queryParams.length; i += 2) {\n                String param = queryParams[i].toString();\n                Object value = queryParams[i + 1];\n                if (value != null) {\n                    if (value instanceof Collection) {\n                        Object[] values = ((Collection<?>) value).toArray();\n                        builder.queryParam(param, values);\n                    } else {\n                        builder.queryParam(param, value);\n                    }\n                }\n            }\n        }\n        return builder;\n    }\n\n    protected boolean isNewerJacksonVersion() {\n        Version version = com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;\n        return version.getMajorVersion() == 2 && version.getMinorVersion() >= 12;\n    }\n\n    private void handleClientHandlerException(ClientHandlerException exception, URI uri) {\n        String errorMessage =\n                String.format(\n                        \"Unable to invoke Conductor API with uri: %s, failure to process request or response\",\n                        uri);\n        LOGGER.error(errorMessage, exception);\n        throw new ConductorClientException(errorMessage, exception);\n    }\n\n    private void handleRuntimeException(RuntimeException exception, URI uri) {\n        String errorMessage =\n                String.format(\n                        \"Unable to invoke Conductor API with uri: %s, runtime exception occurred\",\n                        uri);\n        LOGGER.error(errorMessage, exception);\n        throw new ConductorClientException(errorMessage, exception);\n    }\n\n    private void handleUniformInterfaceException(UniformInterfaceException exception, URI uri) {\n        ClientResponse clientResponse = exception.getResponse();\n        if (clientResponse == null) {\n            throw new ConductorClientException(\n                    String.format(\"Unable to invoke Conductor API with uri: %s\", uri));\n        }\n        try {\n            if (clientResponse.getStatus() < 300) {\n                return;\n            }\n            String errorMessage = clientResponse.getEntity(String.class);\n            LOGGER.warn(\n                    \"Unable to invoke Conductor API with uri: {}, unexpected response from server: statusCode={}, responseBody='{}'.\",\n                    uri,\n                    clientResponse.getStatus(),\n                    errorMessage);\n            ErrorResponse errorResponse;\n            try {\n                errorResponse = objectMapper.readValue(errorMessage, ErrorResponse.class);\n            } catch (IOException e) {\n                throw new ConductorClientException(clientResponse.getStatus(), errorMessage);\n            }\n            throw new ConductorClientException(clientResponse.getStatus(), errorResponse);\n        } catch (ConductorClientException e) {\n            throw e;\n        } catch (ClientHandlerException e) {\n            handleClientHandlerException(e, uri);\n        } catch (RuntimeException e) {\n            handleRuntimeException(e, uri);\n        } finally {\n            clientResponse.close();\n        }\n    }\n\n    private void handleException(URI uri, RuntimeException e) {\n        if (e instanceof UniformInterfaceException) {\n            handleUniformInterfaceException(((UniformInterfaceException) e), uri);\n        } else if (e instanceof ClientHandlerException) {\n            handleClientHandlerException((ClientHandlerException) e, uri);\n        } else {\n            handleRuntimeException(e, uri);\n        }\n    }\n\n    /**\n     * Converts ClientResponse object to string with detailed debug information including status\n     * code, media type, response headers, and response body if exists.\n     */\n    private String clientResponseToString(ClientResponse response) {\n        if (response == null) {\n            return null;\n        }\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"[status: \").append(response.getStatus());\n        builder.append(\", media type: \").append(response.getType());\n        if (response.getStatus() != 404) {\n            try {\n                String responseBody = response.getEntity(String.class);\n                if (responseBody != null) {\n                    builder.append(\", response body: \").append(responseBody);\n                }\n            } catch (RuntimeException ignore) {\n                // Ignore if there is no response body, or IO error - it may have already been read\n                // in certain scenario.\n            }\n        }\n        builder.append(\", response headers: \").append(response.getHeaders());\n        builder.append(\"]\");\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/http/ClientRequestHandler.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http;\n\nimport java.net.URI;\n\nimport javax.ws.rs.core.MediaType;\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider;\nimport com.netflix.conductor.common.model.BulkResponse;\n\nimport com.fasterxml.jackson.core.Version;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;\nimport com.sun.jersey.api.client.Client;\nimport com.sun.jersey.api.client.ClientHandler;\nimport com.sun.jersey.api.client.ClientResponse;\nimport com.sun.jersey.api.client.WebResource;\nimport com.sun.jersey.api.client.config.ClientConfig;\nimport com.sun.jersey.api.client.filter.ClientFilter;\n\npublic class ClientRequestHandler {\n    private final Client client;\n\n    public ClientRequestHandler(\n            ClientConfig config, ClientHandler handler, ClientFilter... filters) {\n        ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n        // https://github.com/FasterXML/jackson-databind/issues/2683\n        if (isNewerJacksonVersion()) {\n            objectMapper.registerModule(new JavaTimeModule());\n        }\n\n        JacksonJsonProvider provider = new JacksonJsonProvider(objectMapper);\n        config.getSingletons().add(provider);\n\n        if (handler == null) {\n            this.client = Client.create(config);\n        } else {\n            this.client = new Client(handler, config);\n        }\n\n        for (ClientFilter filter : filters) {\n            this.client.addFilter(filter);\n        }\n    }\n\n    public BulkResponse delete(URI uri, Object body) {\n        if (body != null) {\n            return client.resource(uri)\n                    .type(MediaType.APPLICATION_JSON_TYPE)\n                    .delete(BulkResponse.class, body);\n        } else {\n            client.resource(uri).delete();\n        }\n        return null;\n    }\n\n    public ClientResponse get(URI uri) {\n        return client.resource(uri)\n                .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)\n                .get(ClientResponse.class);\n    }\n\n    public WebResource.Builder getWebResourceBuilder(URI URI, Object entity) {\n        return client.resource(URI)\n                .type(MediaType.APPLICATION_JSON)\n                .entity(entity)\n                .accept(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON);\n    }\n\n    private boolean isNewerJacksonVersion() {\n        Version version = com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;\n        return version.getMajorVersion() == 2 && version.getMinorVersion() >= 12;\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/http/EventClient.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http;\n\nimport java.util.List;\n\nimport org.apache.commons.lang3.Validate;\n\nimport com.netflix.conductor.client.config.ConductorClientConfiguration;\nimport com.netflix.conductor.client.config.DefaultConductorClientConfiguration;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\n\nimport com.sun.jersey.api.client.ClientHandler;\nimport com.sun.jersey.api.client.GenericType;\nimport com.sun.jersey.api.client.config.ClientConfig;\nimport com.sun.jersey.api.client.config.DefaultClientConfig;\nimport com.sun.jersey.api.client.filter.ClientFilter;\n\n// Client class for all Event Handler operations\npublic class EventClient extends ClientBase {\n    private static final GenericType<List<EventHandler>> eventHandlerList =\n            new GenericType<List<EventHandler>>() {};\n\n    /** Creates a default metadata client */\n    public EventClient() {\n        this(new DefaultClientConfig(), new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param clientConfig REST Client configuration\n     */\n    public EventClient(ClientConfig clientConfig) {\n        this(clientConfig, new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param clientConfig REST Client configuration\n     * @param clientHandler Jersey client handler. Useful when plugging in various http client\n     *     interaction modules (e.g. ribbon)\n     */\n    public EventClient(ClientConfig clientConfig, ClientHandler clientHandler) {\n        this(clientConfig, new DefaultConductorClientConfiguration(), clientHandler);\n    }\n\n    /**\n     * @param config config REST Client configuration\n     * @param handler handler Jersey client handler. Useful when plugging in various http client\n     *     interaction modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public EventClient(ClientConfig config, ClientHandler handler, ClientFilter... filters) {\n        this(config, new DefaultConductorClientConfiguration(), handler, filters);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param clientConfiguration Specific properties configured for the client, see {@link\n     *     ConductorClientConfiguration}\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public EventClient(\n            ClientConfig config,\n            ConductorClientConfiguration clientConfiguration,\n            ClientHandler handler,\n            ClientFilter... filters) {\n        super(new ClientRequestHandler(config, handler, filters), clientConfiguration);\n    }\n\n    EventClient(ClientRequestHandler requestHandler) {\n        super(requestHandler, null);\n    }\n\n    /**\n     * Register an event handler with the server\n     *\n     * @param eventHandler the eventHandler definition\n     */\n    public void registerEventHandler(EventHandler eventHandler) {\n        Validate.notNull(eventHandler, \"Event Handler definition cannot be null\");\n        postForEntityWithRequestOnly(\"event\", eventHandler);\n    }\n\n    /**\n     * Updates an event handler with the server\n     *\n     * @param eventHandler the eventHandler definition\n     */\n    public void updateEventHandler(EventHandler eventHandler) {\n        Validate.notNull(eventHandler, \"Event Handler definition cannot be null\");\n        put(\"event\", null, eventHandler);\n    }\n\n    /**\n     * @param event name of the event\n     * @param activeOnly if true, returns only the active handlers\n     * @return Returns the list of all the event handlers for a given event\n     */\n    public List<EventHandler> getEventHandlers(String event, boolean activeOnly) {\n        Validate.notBlank(event, \"Event cannot be blank\");\n\n        return getForEntity(\n                \"event/{event}\", new Object[] {\"activeOnly\", activeOnly}, eventHandlerList, event);\n    }\n\n    /**\n     * Removes the event handler definition from the conductor server\n     *\n     * @param name the name of the event handler to be unregistered\n     */\n    public void unregisterEventHandler(String name) {\n        Validate.notBlank(name, \"Event handler name cannot be blank\");\n        delete(\"event/{name}\", name);\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/http/MetadataClient.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http;\n\nimport java.util.List;\n\nimport org.apache.commons.lang3.Validate;\n\nimport com.netflix.conductor.client.config.ConductorClientConfiguration;\nimport com.netflix.conductor.client.config.DefaultConductorClientConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\n\nimport com.sun.jersey.api.client.ClientHandler;\nimport com.sun.jersey.api.client.GenericType;\nimport com.sun.jersey.api.client.config.ClientConfig;\nimport com.sun.jersey.api.client.config.DefaultClientConfig;\nimport com.sun.jersey.api.client.filter.ClientFilter;\n\npublic class MetadataClient extends ClientBase {\n\n    private static final GenericType<List<WorkflowDef>> workflowDefList =\n            new GenericType<List<WorkflowDef>>() {};\n\n    /** Creates a default metadata client */\n    public MetadataClient() {\n        this(new DefaultClientConfig(), new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param clientConfig REST Client configuration\n     */\n    public MetadataClient(ClientConfig clientConfig) {\n        this(clientConfig, new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param clientConfig REST Client configuration\n     * @param clientHandler Jersey client handler. Useful when plugging in various http client\n     *     interaction modules (e.g. ribbon)\n     */\n    public MetadataClient(ClientConfig clientConfig, ClientHandler clientHandler) {\n        this(clientConfig, new DefaultConductorClientConfiguration(), clientHandler);\n    }\n\n    /**\n     * @param config config REST Client configuration\n     * @param handler handler Jersey client handler. Useful when plugging in various http client\n     *     interaction modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public MetadataClient(ClientConfig config, ClientHandler handler, ClientFilter... filters) {\n        this(config, new DefaultConductorClientConfiguration(), handler, filters);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param clientConfiguration Specific properties configured for the client, see {@link\n     *     ConductorClientConfiguration}\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public MetadataClient(\n            ClientConfig config,\n            ConductorClientConfiguration clientConfiguration,\n            ClientHandler handler,\n            ClientFilter... filters) {\n        super(new ClientRequestHandler(config, handler, filters), clientConfiguration);\n    }\n\n    MetadataClient(ClientRequestHandler requestHandler) {\n        super(requestHandler, null);\n    }\n\n    // Workflow Metadata Operations\n\n    /**\n     * Register a workflow definition with the server\n     *\n     * @param workflowDef the workflow definition\n     */\n    public void registerWorkflowDef(WorkflowDef workflowDef) {\n        Validate.notNull(workflowDef, \"Workflow definition cannot be null\");\n        postForEntityWithRequestOnly(\"metadata/workflow\", workflowDef);\n    }\n\n    public void validateWorkflowDef(WorkflowDef workflowDef) {\n        Validate.notNull(workflowDef, \"Workflow definition cannot be null\");\n        postForEntityWithRequestOnly(\"metadata/workflow/validate\", workflowDef);\n    }\n\n    /**\n     * Updates a list of existing workflow definitions\n     *\n     * @param workflowDefs List of workflow definitions to be updated\n     */\n    public void updateWorkflowDefs(List<WorkflowDef> workflowDefs) {\n        Validate.notNull(workflowDefs, \"Workflow defs list cannot be null\");\n        put(\"metadata/workflow\", null, workflowDefs);\n    }\n\n    /**\n     * Retrieve the workflow definition\n     *\n     * @param name the name of the workflow\n     * @param version the version of the workflow def\n     * @return Workflow definition for the given workflow and version\n     */\n    public WorkflowDef getWorkflowDef(String name, Integer version) {\n        Validate.notBlank(name, \"name cannot be blank\");\n        return getForEntity(\n                \"metadata/workflow/{name}\",\n                new Object[] {\"version\", version},\n                WorkflowDef.class,\n                name);\n    }\n\n    /** */\n    public List<WorkflowDef> getAllWorkflowsWithLatestVersions() {\n        return getForEntity(\n                \"metadata/workflow/latest-versions\", null, workflowDefList, (Object) null);\n    }\n\n    /**\n     * Removes the workflow definition of a workflow from the conductor server. It does not remove\n     * associated workflows. Use with caution.\n     *\n     * @param name Name of the workflow to be unregistered.\n     * @param version Version of the workflow definition to be unregistered.\n     */\n    public void unregisterWorkflowDef(String name, Integer version) {\n        Validate.notBlank(name, \"Workflow name cannot be blank\");\n        Validate.notNull(version, \"Version cannot be null\");\n        delete(\"metadata/workflow/{name}/{version}\", name, version);\n    }\n\n    // Task Metadata Operations\n\n    /**\n     * Registers a list of task types with the conductor server\n     *\n     * @param taskDefs List of task types to be registered.\n     */\n    public void registerTaskDefs(List<TaskDef> taskDefs) {\n        Validate.notNull(taskDefs, \"Task defs list cannot be null\");\n        postForEntityWithRequestOnly(\"metadata/taskdefs\", taskDefs);\n    }\n\n    /**\n     * Updates an existing task definition\n     *\n     * @param taskDef the task definition to be updated\n     */\n    public void updateTaskDef(TaskDef taskDef) {\n        Validate.notNull(taskDef, \"Task definition cannot be null\");\n        put(\"metadata/taskdefs\", null, taskDef);\n    }\n\n    /**\n     * Retrieve the task definition of a given task type\n     *\n     * @param taskType type of task for which to retrieve the definition\n     * @return Task Definition for the given task type\n     */\n    public TaskDef getTaskDef(String taskType) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n        return getForEntity(\"metadata/taskdefs/{tasktype}\", null, TaskDef.class, taskType);\n    }\n\n    /**\n     * Removes the task definition of a task type from the conductor server. Use with caution.\n     *\n     * @param taskType Task type to be unregistered.\n     */\n    public void unregisterTaskDef(String taskType) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n        delete(\"metadata/taskdefs/{tasktype}\", taskType);\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/http/PayloadStorage.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http;\n\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\n\nimport javax.ws.rs.core.Response;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\n\nimport com.amazonaws.util.IOUtils;\n\n/** An implementation of {@link ExternalPayloadStorage} for storing large JSON payload data. */\nclass PayloadStorage implements ExternalPayloadStorage {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(PayloadStorage.class);\n\n    private final ClientBase clientBase;\n\n    PayloadStorage(ClientBase clientBase) {\n        this.clientBase = clientBase;\n    }\n\n    /**\n     * This method is not intended to be used in the client. The client makes a request to the\n     * server to get the {@link ExternalStorageLocation}\n     */\n    @Override\n    public ExternalStorageLocation getLocation(\n            Operation operation, PayloadType payloadType, String path) {\n        String uri;\n        switch (payloadType) {\n            case WORKFLOW_INPUT:\n            case WORKFLOW_OUTPUT:\n                uri = \"workflow\";\n                break;\n            case TASK_INPUT:\n            case TASK_OUTPUT:\n                uri = \"tasks\";\n                break;\n            default:\n                throw new ConductorClientException(\n                        String.format(\n                                \"Invalid payload type: %s for operation: %s\",\n                                payloadType.toString(), operation.toString()));\n        }\n        return clientBase.getForEntity(\n                String.format(\"%s/externalstoragelocation\", uri),\n                new Object[] {\n                    \"path\",\n                    path,\n                    \"operation\",\n                    operation.toString(),\n                    \"payloadType\",\n                    payloadType.toString()\n                },\n                ExternalStorageLocation.class);\n    }\n\n    /**\n     * Uploads the payload to the uri specified.\n     *\n     * @param uri the location to which the object is to be uploaded\n     * @param payload an {@link InputStream} containing the json payload which is to be uploaded\n     * @param payloadSize the size of the json payload in bytes\n     * @throws ConductorClientException if the upload fails due to an invalid path or an error from\n     *     external storage\n     */\n    @Override\n    public void upload(String uri, InputStream payload, long payloadSize) {\n        HttpURLConnection connection = null;\n        try {\n            URL url = new URI(uri).toURL();\n\n            connection = (HttpURLConnection) url.openConnection();\n            connection.setDoOutput(true);\n            connection.setRequestMethod(\"PUT\");\n\n            try (BufferedOutputStream bufferedOutputStream =\n                    new BufferedOutputStream(connection.getOutputStream())) {\n                long count = IOUtils.copy(payload, bufferedOutputStream);\n                bufferedOutputStream.flush();\n                // Check the HTTP response code\n                int responseCode = connection.getResponseCode();\n                if (Response.Status.fromStatusCode(responseCode).getFamily()\n                        != Response.Status.Family.SUCCESSFUL) {\n                    String errorMsg =\n                            String.format(\"Unable to upload. Response code: %d\", responseCode);\n                    LOGGER.error(errorMsg);\n                    throw new ConductorClientException(errorMsg);\n                }\n                LOGGER.debug(\n                        \"Uploaded {} bytes to uri: {}, with HTTP response code: {}\",\n                        count,\n                        uri,\n                        responseCode);\n            }\n        } catch (URISyntaxException | MalformedURLException e) {\n            String errorMsg = String.format(\"Invalid path specified: %s\", uri);\n            LOGGER.error(errorMsg, e);\n            throw new ConductorClientException(errorMsg, e);\n        } catch (IOException e) {\n            String errorMsg = String.format(\"Error uploading to path: %s\", uri);\n            LOGGER.error(errorMsg, e);\n            throw new ConductorClientException(errorMsg, e);\n        } finally {\n            if (connection != null) {\n                connection.disconnect();\n            }\n            try {\n                if (payload != null) {\n                    payload.close();\n                }\n            } catch (IOException e) {\n                LOGGER.warn(\"Unable to close inputstream when uploading to uri: {}\", uri);\n            }\n        }\n    }\n\n    /**\n     * Downloads the payload from the given uri.\n     *\n     * @param uri the location from where the object is to be downloaded\n     * @return an inputstream of the payload in the external storage\n     * @throws ConductorClientException if the download fails due to an invalid path or an error\n     *     from external storage\n     */\n    @Override\n    public InputStream download(String uri) {\n        HttpURLConnection connection = null;\n        String errorMsg;\n        try {\n            URL url = new URI(uri).toURL();\n            connection = (HttpURLConnection) url.openConnection();\n            connection.setDoOutput(false);\n\n            // Check the HTTP response code\n            int responseCode = connection.getResponseCode();\n            if (responseCode == HttpURLConnection.HTTP_OK) {\n                LOGGER.debug(\n                        \"Download completed with HTTP response code: {}\",\n                        connection.getResponseCode());\n                return org.apache.commons.io.IOUtils.toBufferedInputStream(\n                        connection.getInputStream());\n            }\n            errorMsg = String.format(\"Unable to download. Response code: %d\", responseCode);\n            LOGGER.error(errorMsg);\n            throw new ConductorClientException(errorMsg);\n        } catch (URISyntaxException | MalformedURLException e) {\n            errorMsg = String.format(\"Invalid uri specified: %s\", uri);\n            LOGGER.error(errorMsg, e);\n            throw new ConductorClientException(errorMsg, e);\n        } catch (IOException e) {\n            errorMsg = String.format(\"Error downloading from uri: %s\", uri);\n            LOGGER.error(errorMsg, e);\n            throw new ConductorClientException(errorMsg, e);\n        } finally {\n            if (connection != null) {\n                connection.disconnect();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/http/TaskClient.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.apache.commons.lang3.ObjectUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Validate;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.config.ConductorClientConfiguration;\nimport com.netflix.conductor.client.config.DefaultConductorClientConfiguration;\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.client.telemetry.MetricsContainer;\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType;\n\nimport com.sun.jersey.api.client.ClientHandler;\nimport com.sun.jersey.api.client.GenericType;\nimport com.sun.jersey.api.client.config.ClientConfig;\nimport com.sun.jersey.api.client.config.DefaultClientConfig;\nimport com.sun.jersey.api.client.filter.ClientFilter;\n\n/** Client for conductor task management including polling for task, updating task status etc. */\npublic class TaskClient extends ClientBase {\n\n    private static final GenericType<List<Task>> taskList = new GenericType<List<Task>>() {};\n\n    private static final GenericType<List<TaskExecLog>> taskExecLogList =\n            new GenericType<List<TaskExecLog>>() {};\n\n    private static final GenericType<List<PollData>> pollDataList =\n            new GenericType<List<PollData>>() {};\n\n    private static final GenericType<SearchResult<TaskSummary>> searchResultTaskSummary =\n            new GenericType<SearchResult<TaskSummary>>() {};\n\n    private static final GenericType<SearchResult<Task>> searchResultTask =\n            new GenericType<SearchResult<Task>>() {};\n\n    private static final GenericType<Map<String, Integer>> queueSizeMap =\n            new GenericType<Map<String, Integer>>() {};\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskClient.class);\n\n    /** Creates a default task client */\n    public TaskClient() {\n        this(new DefaultClientConfig(), new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param config REST Client configuration\n     */\n    public TaskClient(ClientConfig config) {\n        this(config, new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     */\n    public TaskClient(ClientConfig config, ClientHandler handler) {\n        this(config, new DefaultConductorClientConfiguration(), handler);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public TaskClient(ClientConfig config, ClientHandler handler, ClientFilter... filters) {\n        this(config, new DefaultConductorClientConfiguration(), handler, filters);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param clientConfiguration Specific properties configured for the client, see {@link\n     *     ConductorClientConfiguration}\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public TaskClient(\n            ClientConfig config,\n            ConductorClientConfiguration clientConfiguration,\n            ClientHandler handler,\n            ClientFilter... filters) {\n        super(new ClientRequestHandler(config, handler, filters), clientConfiguration);\n    }\n\n    TaskClient(ClientRequestHandler requestHandler) {\n        super(requestHandler, null);\n    }\n\n    /**\n     * Perform a poll for a task of a specific task type.\n     *\n     * @param taskType The taskType to poll for\n     * @param domain The domain of the task type\n     * @param workerId Name of the client worker. Used for logging.\n     * @return Task waiting to be executed.\n     */\n    public Task pollTask(String taskType, String workerId, String domain) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n        Validate.notBlank(workerId, \"Worker id cannot be blank\");\n\n        Object[] params = new Object[] {\"workerid\", workerId, \"domain\", domain};\n        Task task =\n                ObjectUtils.defaultIfNull(\n                        getForEntity(\"tasks/poll/{taskType}\", params, Task.class, taskType),\n                        new Task());\n        populateTaskPayloads(task);\n        return task;\n    }\n\n    /**\n     * Perform a batch poll for tasks by task type. Batch size is configurable by count.\n     *\n     * @param taskType Type of task to poll for\n     * @param workerId Name of the client worker. Used for logging.\n     * @param count Maximum number of tasks to be returned. Actual number of tasks returned can be\n     *     less than this number.\n     * @param timeoutInMillisecond Long poll wait timeout.\n     * @return List of tasks awaiting to be executed.\n     */\n    public List<Task> batchPollTasksByTaskType(\n            String taskType, String workerId, int count, int timeoutInMillisecond) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n        Validate.notBlank(workerId, \"Worker id cannot be blank\");\n        Validate.isTrue(count > 0, \"Count must be greater than 0\");\n\n        Object[] params =\n                new Object[] {\n                    \"workerid\", workerId, \"count\", count, \"timeout\", timeoutInMillisecond\n                };\n        List<Task> tasks = getForEntity(\"tasks/poll/batch/{taskType}\", params, taskList, taskType);\n        tasks.forEach(this::populateTaskPayloads);\n        return tasks;\n    }\n\n    /**\n     * Batch poll for tasks in a domain. Batch size is configurable by count.\n     *\n     * @param taskType Type of task to poll for\n     * @param domain The domain of the task type\n     * @param workerId Name of the client worker. Used for logging.\n     * @param count Maximum number of tasks to be returned. Actual number of tasks returned can be\n     *     less than this number.\n     * @param timeoutInMillisecond Long poll wait timeout.\n     * @return List of tasks awaiting to be executed.\n     */\n    public List<Task> batchPollTasksInDomain(\n            String taskType, String domain, String workerId, int count, int timeoutInMillisecond) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n        Validate.notBlank(workerId, \"Worker id cannot be blank\");\n        Validate.isTrue(count > 0, \"Count must be greater than 0\");\n\n        Object[] params =\n                new Object[] {\n                    \"workerid\",\n                    workerId,\n                    \"count\",\n                    count,\n                    \"timeout\",\n                    timeoutInMillisecond,\n                    \"domain\",\n                    domain\n                };\n        List<Task> tasks = getForEntity(\"tasks/poll/batch/{taskType}\", params, taskList, taskType);\n        tasks.forEach(this::populateTaskPayloads);\n        return tasks;\n    }\n\n    /**\n     * Populates the task input/output from external payload storage if the external storage path is\n     * specified.\n     *\n     * @param task the task for which the input is to be populated.\n     */\n    private void populateTaskPayloads(Task task) {\n        if (StringUtils.isNotBlank(task.getExternalInputPayloadStoragePath())) {\n            MetricsContainer.incrementExternalPayloadUsedCount(\n                    task.getTaskDefName(),\n                    ExternalPayloadStorage.Operation.READ.name(),\n                    ExternalPayloadStorage.PayloadType.TASK_INPUT.name());\n            task.setInputData(\n                    downloadFromExternalStorage(\n                            ExternalPayloadStorage.PayloadType.TASK_INPUT,\n                            task.getExternalInputPayloadStoragePath()));\n            task.setExternalInputPayloadStoragePath(null);\n        }\n        if (StringUtils.isNotBlank(task.getExternalOutputPayloadStoragePath())) {\n            MetricsContainer.incrementExternalPayloadUsedCount(\n                    task.getTaskDefName(),\n                    ExternalPayloadStorage.Operation.READ.name(),\n                    PayloadType.TASK_OUTPUT.name());\n            task.setOutputData(\n                    downloadFromExternalStorage(\n                            ExternalPayloadStorage.PayloadType.TASK_OUTPUT,\n                            task.getExternalOutputPayloadStoragePath()));\n            task.setExternalOutputPayloadStoragePath(null);\n        }\n    }\n\n    /**\n     * Updates the result of a task execution. If the size of the task output payload is bigger than\n     * {@link ConductorClientConfiguration#getTaskOutputPayloadThresholdKB()}, it is uploaded to\n     * {@link ExternalPayloadStorage}, if enabled, else the task is marked as\n     * FAILED_WITH_TERMINAL_ERROR.\n     *\n     * @param taskResult the {@link TaskResult} of the executed task to be updated.\n     */\n    public void updateTask(TaskResult taskResult) {\n        Validate.notNull(taskResult, \"Task result cannot be null\");\n        postForEntityWithRequestOnly(\"tasks\", taskResult);\n    }\n\n    public Optional<String> evaluateAndUploadLargePayload(\n            Map<String, Object> taskOutputData, String taskType) {\n        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {\n            objectMapper.writeValue(byteArrayOutputStream, taskOutputData);\n            byte[] taskOutputBytes = byteArrayOutputStream.toByteArray();\n            long taskResultSize = taskOutputBytes.length;\n            MetricsContainer.recordTaskResultPayloadSize(taskType, taskResultSize);\n\n            long payloadSizeThreshold =\n                    conductorClientConfiguration.getTaskOutputPayloadThresholdKB() * 1024L;\n            if (taskResultSize > payloadSizeThreshold) {\n                if (!conductorClientConfiguration.isExternalPayloadStorageEnabled()\n                        || taskResultSize\n                                > conductorClientConfiguration.getTaskOutputMaxPayloadThresholdKB()\n                                        * 1024L) {\n                    throw new IllegalArgumentException(\n                            String.format(\n                                    \"The TaskResult payload size: %d is greater than the permissible %d bytes\",\n                                    taskResultSize, payloadSizeThreshold));\n                }\n                MetricsContainer.incrementExternalPayloadUsedCount(\n                        taskType,\n                        ExternalPayloadStorage.Operation.WRITE.name(),\n                        ExternalPayloadStorage.PayloadType.TASK_OUTPUT.name());\n                return Optional.of(\n                        uploadToExternalPayloadStorage(\n                                PayloadType.TASK_OUTPUT, taskOutputBytes, taskResultSize));\n            }\n            return Optional.empty();\n        } catch (IOException e) {\n            String errorMsg = String.format(\"Unable to update task: %s with task result\", taskType);\n            LOGGER.error(errorMsg, e);\n            throw new ConductorClientException(errorMsg, e);\n        }\n    }\n\n    /**\n     * Ack for the task poll.\n     *\n     * @param taskId Id of the task to be polled\n     * @param workerId user identified worker.\n     * @return true if the task was found with the given ID and acknowledged. False otherwise. If\n     *     the server returns false, the client should NOT attempt to ack again.\n     */\n    public Boolean ack(String taskId, String workerId) {\n        Validate.notBlank(taskId, \"Task id cannot be blank\");\n\n        String response =\n                postForEntity(\n                        \"tasks/{taskId}/ack\",\n                        null,\n                        new Object[] {\"workerid\", workerId},\n                        String.class,\n                        taskId);\n        return Boolean.valueOf(response);\n    }\n\n    /**\n     * Log execution messages for a task.\n     *\n     * @param taskId id of the task\n     * @param logMessage the message to be logged\n     */\n    public void logMessageForTask(String taskId, String logMessage) {\n        Validate.notBlank(taskId, \"Task id cannot be blank\");\n        postForEntityWithRequestOnly(\"tasks/\" + taskId + \"/log\", logMessage);\n    }\n\n    /**\n     * Fetch execution logs for a task.\n     *\n     * @param taskId id of the task.\n     */\n    public List<TaskExecLog> getTaskLogs(String taskId) {\n        Validate.notBlank(taskId, \"Task id cannot be blank\");\n        return getForEntity(\"tasks/{taskId}/log\", null, taskExecLogList, taskId);\n    }\n\n    /**\n     * Retrieve information about the task\n     *\n     * @param taskId ID of the task\n     * @return Task details\n     */\n    public Task getTaskDetails(String taskId) {\n        Validate.notBlank(taskId, \"Task id cannot be blank\");\n        return getForEntity(\"tasks/{taskId}\", null, Task.class, taskId);\n    }\n\n    /**\n     * Removes a task from a taskType queue\n     *\n     * @param taskType the taskType to identify the queue\n     * @param taskId the id of the task to be removed\n     */\n    public void removeTaskFromQueue(String taskType, String taskId) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n        Validate.notBlank(taskId, \"Task id cannot be blank\");\n\n        delete(\"tasks/queue/{taskType}/{taskId}\", taskType, taskId);\n    }\n\n    public int getQueueSizeForTask(String taskType) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n\n        Integer queueSize =\n                getForEntity(\n                        \"tasks/queue/size\",\n                        new Object[] {\"taskType\", taskType},\n                        new GenericType<Integer>() {});\n        return queueSize != null ? queueSize : 0;\n    }\n\n    public int getQueueSizeForTask(\n            String taskType, String domain, String isolationGroupId, String executionNamespace) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n\n        List<Object> params = new LinkedList<>();\n        params.add(\"taskType\");\n        params.add(taskType);\n\n        if (StringUtils.isNotBlank(domain)) {\n            params.add(\"domain\");\n            params.add(domain);\n        }\n\n        if (StringUtils.isNotBlank(isolationGroupId)) {\n            params.add(\"isolationGroupId\");\n            params.add(isolationGroupId);\n        }\n\n        if (StringUtils.isNotBlank(executionNamespace)) {\n            params.add(\"executionNamespace\");\n            params.add(executionNamespace);\n        }\n\n        Integer queueSize =\n                getForEntity(\n                        \"tasks/queue/size\",\n                        params.toArray(new Object[0]),\n                        new GenericType<Integer>() {});\n        return queueSize != null ? queueSize : 0;\n    }\n\n    /**\n     * Get last poll data for a given task type\n     *\n     * @param taskType the task type for which poll data is to be fetched\n     * @return returns the list of poll data for the task type\n     */\n    public List<PollData> getPollData(String taskType) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n\n        Object[] params = new Object[] {\"taskType\", taskType};\n        return getForEntity(\"tasks/queue/polldata\", params, pollDataList);\n    }\n\n    /**\n     * Get the last poll data for all task types\n     *\n     * @return returns a list of poll data for all task types\n     */\n    public List<PollData> getAllPollData() {\n        return getForEntity(\"tasks/queue/polldata/all\", null, pollDataList);\n    }\n\n    /**\n     * Requeue pending tasks for all running workflows\n     *\n     * @return returns the number of tasks that have been requeued\n     */\n    public String requeueAllPendingTasks() {\n        return postForEntity(\"tasks/queue/requeue\", null, null, String.class);\n    }\n\n    /**\n     * Requeue pending tasks of a specific task type\n     *\n     * @return returns the number of tasks that have been requeued\n     */\n    public String requeuePendingTasksByTaskType(String taskType) {\n        Validate.notBlank(taskType, \"Task type cannot be blank\");\n        return postForEntity(\"tasks/queue/requeue/{taskType}\", null, null, String.class, taskType);\n    }\n\n    /**\n     * Search for tasks based on payload\n     *\n     * @param query the search string\n     * @return returns the {@link SearchResult} containing the {@link TaskSummary} matching the\n     *     query\n     */\n    public SearchResult<TaskSummary> search(String query) {\n        return getForEntity(\"tasks/search\", new Object[] {\"query\", query}, searchResultTaskSummary);\n    }\n\n    /**\n     * Search for tasks based on payload\n     *\n     * @param query the search string\n     * @return returns the {@link SearchResult} containing the {@link Task} matching the query\n     */\n    public SearchResult<Task> searchV2(String query) {\n        return getForEntity(\"tasks/search-v2\", new Object[] {\"query\", query}, searchResultTask);\n    }\n\n    /**\n     * Paginated search for tasks based on payload\n     *\n     * @param start start value of page\n     * @param size number of tasks to be returned\n     * @param sort sort order\n     * @param freeText additional free text query\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link TaskSummary} that match the query\n     */\n    public SearchResult<TaskSummary> search(\n            Integer start, Integer size, String sort, String freeText, String query) {\n        Object[] params =\n                new Object[] {\n                    \"start\", start, \"size\", size, \"sort\", sort, \"freeText\", freeText, \"query\", query\n                };\n        return getForEntity(\"tasks/search\", params, searchResultTaskSummary);\n    }\n\n    /**\n     * Paginated search for tasks based on payload\n     *\n     * @param start start value of page\n     * @param size number of tasks to be returned\n     * @param sort sort order\n     * @param freeText additional free text query\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link Task} that match the query\n     */\n    public SearchResult<Task> searchV2(\n            Integer start, Integer size, String sort, String freeText, String query) {\n        Object[] params =\n                new Object[] {\n                    \"start\", start, \"size\", size, \"sort\", sort, \"freeText\", freeText, \"query\", query\n                };\n        return getForEntity(\"tasks/search-v2\", params, searchResultTask);\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/http/WorkflowClient.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.List;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.Validate;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.config.ConductorClientConfiguration;\nimport com.netflix.conductor.client.config.DefaultConductorClientConfiguration;\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.client.telemetry.MetricsContainer;\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.model.BulkResponse;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.common.run.WorkflowTestRequest;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\n\nimport com.sun.jersey.api.client.ClientHandler;\nimport com.sun.jersey.api.client.GenericType;\nimport com.sun.jersey.api.client.config.ClientConfig;\nimport com.sun.jersey.api.client.config.DefaultClientConfig;\nimport com.sun.jersey.api.client.filter.ClientFilter;\n\npublic class WorkflowClient extends ClientBase {\n\n    private static final GenericType<SearchResult<WorkflowSummary>> searchResultWorkflowSummary =\n            new GenericType<SearchResult<WorkflowSummary>>() {};\n\n    private static final GenericType<SearchResult<Workflow>> searchResultWorkflow =\n            new GenericType<SearchResult<Workflow>>() {};\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowClient.class);\n\n    /** Creates a default workflow client */\n    public WorkflowClient() {\n        this(new DefaultClientConfig(), new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param config REST Client configuration\n     */\n    public WorkflowClient(ClientConfig config) {\n        this(config, new DefaultConductorClientConfiguration(), null);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     */\n    public WorkflowClient(ClientConfig config, ClientHandler handler) {\n        this(config, new DefaultConductorClientConfiguration(), handler);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public WorkflowClient(ClientConfig config, ClientHandler handler, ClientFilter... filters) {\n        this(config, new DefaultConductorClientConfiguration(), handler, filters);\n    }\n\n    /**\n     * @param config REST Client configuration\n     * @param clientConfiguration Specific properties configured for the client, see {@link\n     *     ConductorClientConfiguration}\n     * @param handler Jersey client handler. Useful when plugging in various http client interaction\n     *     modules (e.g. ribbon)\n     * @param filters Chain of client side filters to be applied per request\n     */\n    public WorkflowClient(\n            ClientConfig config,\n            ConductorClientConfiguration clientConfiguration,\n            ClientHandler handler,\n            ClientFilter... filters) {\n        super(new ClientRequestHandler(config, handler, filters), clientConfiguration);\n    }\n\n    WorkflowClient(ClientRequestHandler requestHandler) {\n        super(requestHandler, null);\n    }\n\n    /**\n     * Starts a workflow. If the size of the workflow input payload is bigger than {@link\n     * ConductorClientConfiguration#getWorkflowInputPayloadThresholdKB()}, it is uploaded to {@link\n     * ExternalPayloadStorage}, if enabled, else the workflow is rejected.\n     *\n     * @param startWorkflowRequest the {@link StartWorkflowRequest} object to start the workflow.\n     * @return the id of the workflow instance that can be used for tracking.\n     * @throws ConductorClientException if {@link ExternalPayloadStorage} is disabled or if the\n     *     payload size is greater than {@link\n     *     ConductorClientConfiguration#getWorkflowInputMaxPayloadThresholdKB()}.\n     * @throws NullPointerException if {@link StartWorkflowRequest} is null or {@link\n     *     StartWorkflowRequest#getName()} is null.\n     * @throws IllegalArgumentException if {@link StartWorkflowRequest#getName()} is empty.\n     */\n    public String startWorkflow(StartWorkflowRequest startWorkflowRequest) {\n        Validate.notNull(startWorkflowRequest, \"StartWorkflowRequest cannot be null\");\n        Validate.notBlank(startWorkflowRequest.getName(), \"Workflow name cannot be null or empty\");\n        Validate.isTrue(\n                StringUtils.isBlank(startWorkflowRequest.getExternalInputPayloadStoragePath()),\n                \"External Storage Path must not be set\");\n\n        String version =\n                startWorkflowRequest.getVersion() != null\n                        ? startWorkflowRequest.getVersion().toString()\n                        : \"latest\";\n        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {\n            objectMapper.writeValue(byteArrayOutputStream, startWorkflowRequest.getInput());\n            byte[] workflowInputBytes = byteArrayOutputStream.toByteArray();\n            long workflowInputSize = workflowInputBytes.length;\n            MetricsContainer.recordWorkflowInputPayloadSize(\n                    startWorkflowRequest.getName(), version, workflowInputSize);\n            if (workflowInputSize\n                    > conductorClientConfiguration.getWorkflowInputPayloadThresholdKB() * 1024L) {\n                if (!conductorClientConfiguration.isExternalPayloadStorageEnabled()\n                        || (workflowInputSize\n                                > conductorClientConfiguration\n                                                .getWorkflowInputMaxPayloadThresholdKB()\n                                        * 1024L)) {\n                    String errorMsg =\n                            String.format(\n                                    \"Input payload larger than the allowed threshold of: %d KB\",\n                                    conductorClientConfiguration\n                                            .getWorkflowInputPayloadThresholdKB());\n                    throw new ConductorClientException(errorMsg);\n                } else {\n                    MetricsContainer.incrementExternalPayloadUsedCount(\n                            startWorkflowRequest.getName(),\n                            ExternalPayloadStorage.Operation.WRITE.name(),\n                            ExternalPayloadStorage.PayloadType.WORKFLOW_INPUT.name());\n                    String externalStoragePath =\n                            uploadToExternalPayloadStorage(\n                                    ExternalPayloadStorage.PayloadType.WORKFLOW_INPUT,\n                                    workflowInputBytes,\n                                    workflowInputSize);\n                    startWorkflowRequest.setExternalInputPayloadStoragePath(externalStoragePath);\n                    startWorkflowRequest.setInput(null);\n                }\n            }\n        } catch (IOException e) {\n            String errorMsg =\n                    String.format(\n                            \"Unable to start workflow:%s, version:%s\",\n                            startWorkflowRequest.getName(), version);\n            LOGGER.error(errorMsg, e);\n            MetricsContainer.incrementWorkflowStartErrorCount(startWorkflowRequest.getName(), e);\n            throw new ConductorClientException(errorMsg, e);\n        }\n        try {\n            return postForEntity(\n                    \"workflow\",\n                    startWorkflowRequest,\n                    null,\n                    String.class,\n                    startWorkflowRequest.getName());\n        } catch (ConductorClientException e) {\n            String errorMsg =\n                    String.format(\n                            \"Unable to send start workflow request:%s, version:%s\",\n                            startWorkflowRequest.getName(), version);\n            LOGGER.error(errorMsg, e);\n            MetricsContainer.incrementWorkflowStartErrorCount(startWorkflowRequest.getName(), e);\n            throw e;\n        }\n    }\n\n    /**\n     * Retrieve a workflow by workflow id\n     *\n     * @param workflowId the id of the workflow\n     * @param includeTasks specify if the tasks in the workflow need to be returned\n     * @return the requested workflow\n     */\n    public Workflow getWorkflow(String workflowId, boolean includeTasks) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        Workflow workflow =\n                getForEntity(\n                        \"workflow/{workflowId}\",\n                        new Object[] {\"includeTasks\", includeTasks},\n                        Workflow.class,\n                        workflowId);\n        populateWorkflowOutput(workflow);\n        return workflow;\n    }\n\n    /**\n     * Retrieve all workflows for a given correlation id and name\n     *\n     * @param name the name of the workflow\n     * @param correlationId the correlation id\n     * @param includeClosed specify if all workflows are to be returned or only running workflows\n     * @param includeTasks specify if the tasks in the workflow need to be returned\n     * @return list of workflows for the given correlation id and name\n     */\n    public List<Workflow> getWorkflows(\n            String name, String correlationId, boolean includeClosed, boolean includeTasks) {\n        Validate.notBlank(name, \"name cannot be blank\");\n        Validate.notBlank(correlationId, \"correlationId cannot be blank\");\n\n        Object[] params =\n                new Object[] {\"includeClosed\", includeClosed, \"includeTasks\", includeTasks};\n        List<Workflow> workflows =\n                getForEntity(\n                        \"workflow/{name}/correlated/{correlationId}\",\n                        params,\n                        new GenericType<List<Workflow>>() {},\n                        name,\n                        correlationId);\n        workflows.forEach(this::populateWorkflowOutput);\n        return workflows;\n    }\n\n    /**\n     * Populates the workflow output from external payload storage if the external storage path is\n     * specified.\n     *\n     * @param workflow the workflow for which the output is to be populated.\n     */\n    private void populateWorkflowOutput(Workflow workflow) {\n        if (StringUtils.isNotBlank(workflow.getExternalOutputPayloadStoragePath())) {\n            MetricsContainer.incrementExternalPayloadUsedCount(\n                    workflow.getWorkflowName(),\n                    ExternalPayloadStorage.Operation.READ.name(),\n                    ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT.name());\n            workflow.setOutput(\n                    downloadFromExternalStorage(\n                            ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT,\n                            workflow.getExternalOutputPayloadStoragePath()));\n        }\n    }\n\n    /**\n     * Removes a workflow from the system\n     *\n     * @param workflowId the id of the workflow to be deleted\n     * @param archiveWorkflow flag to indicate if the workflow and associated tasks should be\n     *     archived before deletion\n     */\n    public void deleteWorkflow(String workflowId, boolean archiveWorkflow) {\n        Validate.notBlank(workflowId, \"Workflow id cannot be blank\");\n\n        Object[] params = new Object[] {\"archiveWorkflow\", archiveWorkflow};\n        deleteWithUriVariables(params, \"workflow/{workflowId}/remove\", workflowId);\n    }\n\n    /**\n     * Terminates the execution of all given workflows instances\n     *\n     * @param workflowIds the ids of the workflows to be terminated\n     * @param reason the reason to be logged and displayed\n     * @return the {@link BulkResponse} contains bulkErrorResults and bulkSuccessfulResults\n     */\n    public BulkResponse terminateWorkflows(List<String> workflowIds, String reason) {\n        Validate.isTrue(!workflowIds.isEmpty(), \"workflow id cannot be blank\");\n        return postForEntity(\n                \"workflow/bulk/terminate\",\n                workflowIds,\n                new Object[] {\"reason\", reason},\n                BulkResponse.class);\n    }\n\n    /**\n     * Retrieve all running workflow instances for a given name and version\n     *\n     * @param workflowName the name of the workflow\n     * @param version the version of the wokflow definition. Defaults to 1.\n     * @return the list of running workflow instances\n     */\n    public List<String> getRunningWorkflow(String workflowName, Integer version) {\n        Validate.notBlank(workflowName, \"Workflow name cannot be blank\");\n        return getForEntity(\n                \"workflow/running/{name}\",\n                new Object[] {\"version\", version},\n                new GenericType<List<String>>() {},\n                workflowName);\n    }\n\n    /**\n     * Retrieve all workflow instances for a given workflow name between a specific time period\n     *\n     * @param workflowName the name of the workflow\n     * @param version the version of the workflow definition. Defaults to 1.\n     * @param startTime the start time of the period\n     * @param endTime the end time of the period\n     * @return returns a list of workflows created during the specified during the time period\n     */\n    public List<String> getWorkflowsByTimePeriod(\n            String workflowName, int version, Long startTime, Long endTime) {\n        Validate.notBlank(workflowName, \"Workflow name cannot be blank\");\n        Validate.notNull(startTime, \"Start time cannot be null\");\n        Validate.notNull(endTime, \"End time cannot be null\");\n\n        Object[] params =\n                new Object[] {\"version\", version, \"startTime\", startTime, \"endTime\", endTime};\n        return getForEntity(\n                \"workflow/running/{name}\",\n                params,\n                new GenericType<List<String>>() {},\n                workflowName);\n    }\n\n    /**\n     * Starts the decision task for the given workflow instance\n     *\n     * @param workflowId the id of the workflow instance\n     */\n    public void runDecider(String workflowId) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        put(\"workflow/decide/{workflowId}\", null, null, workflowId);\n    }\n\n    /**\n     * Pause a workflow by workflow id\n     *\n     * @param workflowId the workflow id of the workflow to be paused\n     */\n    public void pauseWorkflow(String workflowId) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        put(\"workflow/{workflowId}/pause\", null, null, workflowId);\n    }\n\n    /**\n     * Resume a paused workflow by workflow id\n     *\n     * @param workflowId the workflow id of the paused workflow\n     */\n    public void resumeWorkflow(String workflowId) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        put(\"workflow/{workflowId}/resume\", null, null, workflowId);\n    }\n\n    /**\n     * Skips a given task from a current RUNNING workflow\n     *\n     * @param workflowId the id of the workflow instance\n     * @param taskReferenceName the reference name of the task to be skipped\n     */\n    public void skipTaskFromWorkflow(String workflowId, String taskReferenceName) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        Validate.notBlank(taskReferenceName, \"Task reference name cannot be blank\");\n\n        put(\n                \"workflow/{workflowId}/skiptask/{taskReferenceName}\",\n                null,\n                null,\n                workflowId,\n                taskReferenceName);\n    }\n\n    /**\n     * Reruns the workflow from a specific task\n     *\n     * @param workflowId the id of the workflow\n     * @param rerunWorkflowRequest the request containing the task to rerun from\n     * @return the id of the workflow\n     */\n    public String rerunWorkflow(String workflowId, RerunWorkflowRequest rerunWorkflowRequest) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        Validate.notNull(rerunWorkflowRequest, \"RerunWorkflowRequest cannot be null\");\n\n        return postForEntity(\n                \"workflow/{workflowId}/rerun\",\n                rerunWorkflowRequest,\n                null,\n                String.class,\n                workflowId);\n    }\n\n    /**\n     * Restart a completed workflow\n     *\n     * @param workflowId the workflow id of the workflow to be restarted\n     * @param useLatestDefinitions if true, use the latest workflow and task definitions when\n     *     restarting the workflow if false, use the workflow and task definitions embedded in the\n     *     workflow execution when restarting the workflow\n     */\n    public void restart(String workflowId, boolean useLatestDefinitions) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        Object[] params = new Object[] {\"useLatestDefinitions\", useLatestDefinitions};\n        postForEntity(\"workflow/{workflowId}/restart\", null, params, Void.TYPE, workflowId);\n    }\n\n    /**\n     * Retries the last failed task in a workflow\n     *\n     * @param workflowId the workflow id of the workflow with the failed task\n     */\n    public void retryLastFailedTask(String workflowId) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        postForEntityWithUriVariablesOnly(\"workflow/{workflowId}/retry\", workflowId);\n    }\n\n    /**\n     * Resets the callback times of all IN PROGRESS tasks to 0 for the given workflow\n     *\n     * @param workflowId the id of the workflow\n     */\n    public void resetCallbacksForInProgressTasks(String workflowId) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        postForEntityWithUriVariablesOnly(\"workflow/{workflowId}/resetcallbacks\", workflowId);\n    }\n\n    /**\n     * Terminates the execution of the given workflow instance\n     *\n     * @param workflowId the id of the workflow to be terminated\n     * @param reason the reason to be logged and displayed\n     */\n    public void terminateWorkflow(String workflowId, String reason) {\n        Validate.notBlank(workflowId, \"workflow id cannot be blank\");\n        deleteWithUriVariables(\n                new Object[] {\"reason\", reason}, \"workflow/{workflowId}\", workflowId);\n    }\n\n    /**\n     * Search for workflows based on payload\n     *\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link WorkflowSummary} that match the query\n     */\n    public SearchResult<WorkflowSummary> search(String query) {\n        return getForEntity(\n                \"workflow/search\", new Object[] {\"query\", query}, searchResultWorkflowSummary);\n    }\n\n    /**\n     * Search for workflows based on payload\n     *\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link Workflow} that match the query\n     */\n    public SearchResult<Workflow> searchV2(String query) {\n        return getForEntity(\n                \"workflow/search-v2\", new Object[] {\"query\", query}, searchResultWorkflow);\n    }\n\n    /**\n     * Paginated search for workflows based on payload\n     *\n     * @param start start value of page\n     * @param size number of workflows to be returned\n     * @param sort sort order\n     * @param freeText additional free text query\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link WorkflowSummary} that match the query\n     */\n    public SearchResult<WorkflowSummary> search(\n            Integer start, Integer size, String sort, String freeText, String query) {\n        Object[] params =\n                new Object[] {\n                    \"start\", start, \"size\", size, \"sort\", sort, \"freeText\", freeText, \"query\", query\n                };\n        return getForEntity(\"workflow/search\", params, searchResultWorkflowSummary);\n    }\n\n    /**\n     * Paginated search for workflows based on payload\n     *\n     * @param start start value of page\n     * @param size number of workflows to be returned\n     * @param sort sort order\n     * @param freeText additional free text query\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link Workflow} that match the query\n     */\n    public SearchResult<Workflow> searchV2(\n            Integer start, Integer size, String sort, String freeText, String query) {\n        Object[] params =\n                new Object[] {\n                    \"start\", start, \"size\", size, \"sort\", sort, \"freeText\", freeText, \"query\", query\n                };\n        return getForEntity(\"workflow/search-v2\", params, searchResultWorkflow);\n    }\n\n    public Workflow testWorkflow(WorkflowTestRequest testRequest) {\n        Validate.notNull(testRequest, \"testRequest cannot be null\");\n        if (testRequest.getWorkflowDef() != null) {\n            testRequest.setName(testRequest.getWorkflowDef().getName());\n            testRequest.setVersion(testRequest.getWorkflowDef().getVersion());\n        }\n        return postForEntity(\"workflow/test\", testRequest, null, Workflow.class);\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/telemetry/MetricsContainer.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.telemetry;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport com.netflix.spectator.api.BasicTag;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.Tag;\nimport com.netflix.spectator.api.Timer;\nimport com.netflix.spectator.api.patterns.PolledMeter;\n\npublic class MetricsContainer {\n\n    private static final String TASK_TYPE = \"taskType\";\n    private static final String WORKFLOW_TYPE = \"workflowType\";\n    private static final String WORKFLOW_VERSION = \"version\";\n    private static final String EXCEPTION = \"exception\";\n    private static final String ENTITY_NAME = \"entityName\";\n    private static final String OPERATION = \"operation\";\n    private static final String PAYLOAD_TYPE = \"payload_type\";\n\n    private static final String TASK_EXECUTION_QUEUE_FULL = \"task_execution_queue_full\";\n    private static final String TASK_POLL_ERROR = \"task_poll_error\";\n    private static final String TASK_PAUSED = \"task_paused\";\n    private static final String TASK_EXECUTE_ERROR = \"task_execute_error\";\n    private static final String TASK_ACK_FAILED = \"task_ack_failed\";\n    private static final String TASK_ACK_ERROR = \"task_ack_error\";\n    private static final String TASK_UPDATE_ERROR = \"task_update_error\";\n    private static final String TASK_LEASE_EXTEND_ERROR = \"task_lease_extend_error\";\n    private static final String TASK_LEASE_EXTEND_COUNTER = \"task_lease_extend_counter\";\n    private static final String TASK_POLL_COUNTER = \"task_poll_counter\";\n    private static final String TASK_EXECUTE_TIME = \"task_execute_time\";\n    private static final String TASK_POLL_TIME = \"task_poll_time\";\n    private static final String TASK_RESULT_SIZE = \"task_result_size\";\n    private static final String WORKFLOW_INPUT_SIZE = \"workflow_input_size\";\n    private static final String EXTERNAL_PAYLOAD_USED = \"external_payload_used\";\n    private static final String WORKFLOW_START_ERROR = \"workflow_start_error\";\n    private static final String THREAD_UNCAUGHT_EXCEPTION = \"thread_uncaught_exceptions\";\n\n    private static final Registry REGISTRY = Spectator.globalRegistry();\n    private static final Map<String, Timer> TIMERS = new ConcurrentHashMap<>();\n    private static final Map<String, Counter> COUNTERS = new ConcurrentHashMap<>();\n    private static final Map<String, AtomicLong> GAUGES = new ConcurrentHashMap<>();\n    private static final String CLASS_NAME = MetricsContainer.class.getSimpleName();\n\n    private MetricsContainer() {}\n\n    public static Timer getPollTimer(String taskType) {\n        return getTimer(TASK_POLL_TIME, TASK_TYPE, taskType);\n    }\n\n    public static Timer getExecutionTimer(String taskType) {\n        return getTimer(TASK_EXECUTE_TIME, TASK_TYPE, taskType);\n    }\n\n    private static Timer getTimer(String name, String... additionalTags) {\n        String key = CLASS_NAME + \".\" + name + \".\" + String.join(\",\", additionalTags);\n        return TIMERS.computeIfAbsent(\n                key,\n                k -> {\n                    List<Tag> tagList = getTags(additionalTags);\n                    tagList.add(new BasicTag(\"unit\", TimeUnit.MILLISECONDS.name()));\n                    return REGISTRY.timer(name, tagList);\n                });\n    }\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    private static List<Tag> getTags(String[] additionalTags) {\n        List<Tag> tagList = new ArrayList();\n        tagList.add(new BasicTag(\"class\", CLASS_NAME));\n        for (int j = 0; j < additionalTags.length - 1; j++) {\n            tagList.add(new BasicTag(additionalTags[j], additionalTags[j + 1]));\n            j++;\n        }\n        return tagList;\n    }\n\n    private static void incrementCount(String name, String... additionalTags) {\n        getCounter(name, additionalTags).increment();\n    }\n\n    private static Counter getCounter(String name, String... additionalTags) {\n        String key = CLASS_NAME + \".\" + name + \".\" + String.join(\",\", additionalTags);\n        return COUNTERS.computeIfAbsent(\n                key,\n                k -> {\n                    List<Tag> tags = getTags(additionalTags);\n                    return REGISTRY.counter(name, tags);\n                });\n    }\n\n    private static AtomicLong getGauge(String name, String... additionalTags) {\n        String key = CLASS_NAME + \".\" + name + \".\" + String.join(\",\", additionalTags);\n        return GAUGES.computeIfAbsent(\n                key,\n                pollTimer -> {\n                    Id id = REGISTRY.createId(name, getTags(additionalTags));\n                    return PolledMeter.using(REGISTRY).withId(id).monitorValue(new AtomicLong(0));\n                });\n    }\n\n    public static void incrementTaskExecutionQueueFullCount(String taskType) {\n        incrementCount(TASK_EXECUTION_QUEUE_FULL, TASK_TYPE, taskType);\n    }\n\n    public static void incrementUncaughtExceptionCount() {\n        incrementCount(THREAD_UNCAUGHT_EXCEPTION);\n    }\n\n    public static void incrementTaskPollErrorCount(String taskType, Exception e) {\n        incrementCount(\n                TASK_POLL_ERROR, TASK_TYPE, taskType, EXCEPTION, e.getClass().getSimpleName());\n    }\n\n    public static void incrementTaskPausedCount(String taskType) {\n        incrementCount(TASK_PAUSED, TASK_TYPE, taskType);\n    }\n\n    public static void incrementTaskExecutionErrorCount(String taskType, Throwable e) {\n        incrementCount(\n                TASK_EXECUTE_ERROR, TASK_TYPE, taskType, EXCEPTION, e.getClass().getSimpleName());\n    }\n\n    public static void incrementTaskAckFailedCount(String taskType) {\n        incrementCount(TASK_ACK_FAILED, TASK_TYPE, taskType);\n    }\n\n    public static void incrementTaskAckErrorCount(String taskType, Exception e) {\n        incrementCount(\n                TASK_ACK_ERROR, TASK_TYPE, taskType, EXCEPTION, e.getClass().getSimpleName());\n    }\n\n    public static void recordTaskResultPayloadSize(String taskType, long payloadSize) {\n        getGauge(TASK_RESULT_SIZE, TASK_TYPE, taskType).getAndSet(payloadSize);\n    }\n\n    public static void incrementTaskUpdateErrorCount(String taskType, Throwable t) {\n        incrementCount(\n                TASK_UPDATE_ERROR, TASK_TYPE, taskType, EXCEPTION, t.getClass().getSimpleName());\n    }\n\n    public static void incrementTaskLeaseExtendErrorCount(String taskType, Throwable t) {\n        incrementCount(\n                TASK_LEASE_EXTEND_ERROR,\n                TASK_TYPE,\n                taskType,\n                EXCEPTION,\n                t.getClass().getSimpleName());\n    }\n\n    public static void incrementTaskLeaseExtendCount(String taskType, int taskCount) {\n        getCounter(TASK_LEASE_EXTEND_COUNTER, TASK_TYPE, taskType).increment(taskCount);\n    }\n\n    public static void incrementTaskPollCount(String taskType, int taskCount) {\n        getCounter(TASK_POLL_COUNTER, TASK_TYPE, taskType).increment(taskCount);\n    }\n\n    public static void recordWorkflowInputPayloadSize(\n            String workflowType, String version, long payloadSize) {\n        getGauge(WORKFLOW_INPUT_SIZE, WORKFLOW_TYPE, workflowType, WORKFLOW_VERSION, version)\n                .getAndSet(payloadSize);\n    }\n\n    public static void incrementExternalPayloadUsedCount(\n            String name, String operation, String payloadType) {\n        incrementCount(\n                EXTERNAL_PAYLOAD_USED,\n                ENTITY_NAME,\n                name,\n                OPERATION,\n                operation,\n                PAYLOAD_TYPE,\n                payloadType);\n    }\n\n    public static void incrementWorkflowStartErrorCount(String workflowType, Throwable t) {\n        incrementCount(\n                WORKFLOW_START_ERROR,\n                WORKFLOW_TYPE,\n                workflowType,\n                EXCEPTION,\n                t.getClass().getSimpleName());\n    }\n}\n"
  },
  {
    "path": "client/src/main/java/com/netflix/conductor/client/worker/Worker.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.worker;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.function.Function;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.config.PropertyFactory;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\n\nimport com.amazonaws.util.EC2MetadataUtils;\n\npublic interface Worker {\n\n    /**\n     * Retrieve the name of the task definition the worker is currently working on.\n     *\n     * @return the name of the task definition.\n     */\n    String getTaskDefName();\n\n    /**\n     * Executes a task and returns the updated task.\n     *\n     * @param task Task to be executed.\n     * @return the {@link TaskResult} object If the task is not completed yet, return with the\n     *     status as IN_PROGRESS.\n     */\n    TaskResult execute(Task task);\n\n    /**\n     * Called when the task coordinator fails to update the task to the server. Client should store\n     * the task id (in a database) and retry the update later\n     *\n     * @param task Task which cannot be updated back to the server.\n     */\n    default void onErrorUpdate(Task task) {}\n\n    /**\n     * Override this method to pause the worker from polling.\n     *\n     * @return true if the worker is paused and no more tasks should be polled from server.\n     */\n    default boolean paused() {\n        return PropertyFactory.getBoolean(getTaskDefName(), \"paused\", false);\n    }\n\n    /**\n     * Override this method to app specific rules.\n     *\n     * @return returns the serverId as the id of the instance that the worker is running.\n     */\n    default String getIdentity() {\n        String serverId;\n        try {\n            serverId = InetAddress.getLocalHost().getHostName();\n        } catch (UnknownHostException e) {\n            serverId = System.getenv(\"HOSTNAME\");\n        }\n        if (serverId == null) {\n            serverId =\n                    (EC2MetadataUtils.getInstanceId() == null)\n                            ? System.getProperty(\"user.name\")\n                            : EC2MetadataUtils.getInstanceId();\n        }\n        LoggerHolder.logger.debug(\"Setting worker id to {}\", serverId);\n        return serverId;\n    }\n\n    /**\n     * Override this method to change the interval between polls.\n     *\n     * @return interval in millisecond at which the server should be polled for worker tasks.\n     */\n    default int getPollingInterval() {\n        return PropertyFactory.getInteger(getTaskDefName(), \"pollInterval\", 1000);\n    }\n\n    default boolean leaseExtendEnabled() {\n        return PropertyFactory.getBoolean(getTaskDefName(), \"leaseExtendEnabled\", false);\n    }\n\n    default int getBatchPollTimeoutInMS() {\n        return PropertyFactory.getInteger(getTaskDefName(), \"batchPollTimeoutInMS\", 1000);\n    }\n\n    static Worker create(String taskType, Function<Task, TaskResult> executor) {\n        return new Worker() {\n\n            @Override\n            public String getTaskDefName() {\n                return taskType;\n            }\n\n            @Override\n            public TaskResult execute(Task task) {\n                return executor.apply(task);\n            }\n\n            @Override\n            public boolean paused() {\n                return Worker.super.paused();\n            }\n        };\n    }\n}\n\nfinal class LoggerHolder {\n\n    static final Logger logger = LoggerFactory.getLogger(Worker.class);\n}\n"
  },
  {
    "path": "client/src/test/groovy/com/netflix/conductor/client/http/ClientSpecification.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider\n\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport spock.lang.Specification\n\nabstract class ClientSpecification extends Specification {\n\n    protected static final String ROOT_URL = \"dummyroot/\"\n\n    protected static URI createURI(String path) {\n        URI.create(ROOT_URL + path)\n    }\n\n    protected ClientRequestHandler requestHandler\n    protected ObjectMapper objectMapper\n\n    def setup() {\n        requestHandler = Mock(ClientRequestHandler.class)\n\n        objectMapper = new ObjectMapperProvider().getObjectMapper()\n    }\n}\n"
  },
  {
    "path": "client/src/test/groovy/com/netflix/conductor/client/http/EventClientSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http\n\nimport com.netflix.conductor.common.metadata.events.EventHandler\n\nimport com.sun.jersey.api.client.ClientResponse\nimport com.sun.jersey.api.client.WebResource\nimport spock.lang.Subject\nimport spock.lang.Unroll\n\nclass EventClientSpec extends ClientSpecification {\n\n    @Subject\n    EventClient eventClient\n\n    def setup() {\n        eventClient = new EventClient(requestHandler)\n        eventClient.setRootURI(ROOT_URL)\n    }\n\n    def \"register event handler\"() {\n        given:\n        EventHandler handler = new EventHandler()\n        URI uri = createURI(\"event\")\n\n        when:\n        eventClient.registerEventHandler(handler)\n\n        then:\n        1 * requestHandler.getWebResourceBuilder(uri, handler) >> Mock(WebResource.Builder.class)\n    }\n\n    def \"update event handler\"() {\n        given:\n        EventHandler handler = new EventHandler()\n        URI uri = createURI(\"event\")\n\n        when:\n        eventClient.updateEventHandler(handler)\n\n        then:\n        1 * requestHandler.getWebResourceBuilder(uri, handler) >> Mock(WebResource.Builder.class)\n    }\n\n    def \"unregister event handler\"() {\n        given:\n        String eventName = \"test\"\n        URI uri = createURI(\"event/$eventName\")\n\n        when:\n        eventClient.unregisterEventHandler(eventName)\n\n        then:\n        1 * requestHandler.delete(uri, null)\n    }\n\n    @Unroll\n    def \"get event handlers activeOnly=#activeOnly\"() {\n        given:\n        def handlers = [new EventHandler(), new EventHandler()]\n        String eventName = \"test\"\n        URI uri = createURI(\"event/$eventName?activeOnly=$activeOnly\")\n\n        when:\n        def eventHandlers = eventClient.getEventHandlers(eventName, activeOnly)\n\n        then:\n        eventHandlers && eventHandlers.size() == 2\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> handlers\n        }\n\n        where:\n        activeOnly << [true, false]\n    }\n\n}\n"
  },
  {
    "path": "client/src/test/groovy/com/netflix/conductor/client/http/MetadataClientSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http\n\nimport com.netflix.conductor.client.exception.ConductorClientException\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\n\nimport com.sun.jersey.api.client.ClientResponse\nimport spock.lang.Subject\n\nclass MetadataClientSpec extends ClientSpecification {\n\n    @Subject\n    MetadataClient metadataClient\n\n    def setup() {\n        metadataClient = new MetadataClient(requestHandler)\n        metadataClient.setRootURI(ROOT_URL)\n    }\n\n    def \"workflow delete\"() {\n        given:\n        String workflowName = 'test'\n        int version = 1\n        URI uri = createURI(\"metadata/workflow/$workflowName/$version\")\n\n        when:\n        metadataClient.unregisterWorkflowDef(workflowName, version)\n\n        then:\n        1 * requestHandler.delete(uri, null)\n    }\n\n    def \"workflow delete throws exception\"() {\n        given:\n        String workflowName = 'test'\n        int version = 1\n        URI uri = createURI(\"metadata/workflow/$workflowName/$version\")\n\n        when:\n        metadataClient.unregisterWorkflowDef(workflowName, version)\n\n        then:\n        1 * requestHandler.delete(uri, null) >> { throw new RuntimeException(clientResponse) }\n        def ex = thrown(ConductorClientException.class)\n        ex.message == \"Unable to invoke Conductor API with uri: $uri, runtime exception occurred\"\n    }\n\n    def \"workflow delete version missing\"() {\n        when:\n        metadataClient.unregisterWorkflowDef(\"some name\", null)\n\n        then:\n        thrown(NullPointerException.class)\n    }\n\n    def \"workflow delete name missing\"() {\n        when:\n        metadataClient.unregisterWorkflowDef(null, 1)\n\n        then:\n        thrown(NullPointerException.class)\n\n        when:\n        metadataClient.unregisterWorkflowDef(\"   \", 1)\n\n        then:\n        thrown(IllegalArgumentException.class)\n    }\n\n    def \"workflow get all definitions latest version\"() {\n        given:\n        List<WorkflowDef> result = new ArrayList<WorkflowDef>()\n        URI uri = createURI(\"metadata/workflow/latest-versions\")\n\n        when:\n        metadataClient.getAllWorkflowsWithLatestVersions()\n\n        then:\n        1 * requestHandler.get(uri) >>  Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n    }\n}\n"
  },
  {
    "path": "client/src/test/groovy/com/netflix/conductor/client/http/TaskClientSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.run.SearchResult\nimport com.netflix.conductor.common.run.TaskSummary\n\nimport com.sun.jersey.api.client.ClientResponse\nimport spock.lang.Subject\n\nclass TaskClientSpec extends ClientSpecification {\n\n    @Subject\n    TaskClient taskClient\n\n    def setup() {\n        taskClient = new TaskClient(requestHandler)\n        taskClient.setRootURI(ROOT_URL)\n    }\n\n    def \"search\"() {\n        given:\n        String query = 'my_complex_query'\n        SearchResult<TaskSummary> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new TaskSummary()]\n\n        URI uri = createURI(\"tasks/search?query=$query\")\n\n        when:\n        SearchResult<TaskSummary> searchResult = taskClient.search(query)\n\n        then:\n        1 * requestHandler.get(uri) >>  Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof TaskSummary\n    }\n\n    def \"searchV2\"() {\n        given:\n        String query = 'my_complex_query'\n        SearchResult<Task> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new Task()]\n\n        URI uri = createURI(\"tasks/search-v2?query=$query\")\n\n        when:\n        SearchResult<Task> searchResult = taskClient.searchV2('my_complex_query')\n\n        then:\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof Task\n    }\n\n    def \"search with params\"() {\n        given:\n        String query = 'my_complex_query'\n        int start = 0\n        int size = 10\n        String sort = 'sort'\n        String freeText = 'text'\n        SearchResult<TaskSummary> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new TaskSummary()]\n\n        URI uri = createURI(\"tasks/search?start=$start&size=$size&sort=$sort&freeText=$freeText&query=$query\")\n\n        when:\n        SearchResult<TaskSummary> searchResult = taskClient.search(start, size, sort, freeText, query)\n\n        then:\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof TaskSummary\n    }\n\n    def \"searchV2 with params\"() {\n        given:\n        String query = 'my_complex_query'\n        int start = 0\n        int size = 10\n        String sort = 'sort'\n        String freeText = 'text'\n        SearchResult<Task> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new Task()]\n\n        URI uri = createURI(\"tasks/search-v2?start=$start&size=$size&sort=$sort&freeText=$freeText&query=$query\")\n\n        when:\n        SearchResult<Task> searchResult = taskClient.searchV2(start, size, sort, freeText, query)\n\n        then:\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof Task\n    }\n}\n"
  },
  {
    "path": "client/src/test/groovy/com/netflix/conductor/client/http/WorkflowClientSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.http\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.run.SearchResult\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.common.run.WorkflowSummary\n\nimport com.sun.jersey.api.client.ClientResponse\nimport spock.lang.Subject\n\nclass WorkflowClientSpec extends ClientSpecification {\n\n    @Subject\n    WorkflowClient workflowClient\n\n    def setup() {\n        workflowClient = new WorkflowClient(requestHandler)\n        workflowClient.setRootURI(ROOT_URL)\n    }\n\n    def \"search\"() {\n        given:\n        String query = 'my_complex_query'\n        SearchResult<WorkflowSummary> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new WorkflowSummary()]\n\n        URI uri = createURI(\"workflow/search?query=$query\")\n\n        when:\n        SearchResult<WorkflowSummary> searchResult = workflowClient.search(query)\n\n        then:\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof WorkflowSummary\n    }\n\n    def \"searchV2\"() {\n        given:\n        String query = 'my_complex_query'\n        SearchResult<Workflow> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new Workflow(workflowDefinition: new WorkflowDef(), createTime: System.currentTimeMillis() )]\n\n        URI uri = createURI(\"workflow/search-v2?query=$query\")\n\n        when:\n        SearchResult<Workflow> searchResult = workflowClient.searchV2('my_complex_query')\n\n        then:\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof Workflow\n    }\n\n    def \"search with params\"() {\n        given:\n        String query = 'my_complex_query'\n        int start = 0\n        int size = 10\n        String sort = 'sort'\n        String freeText = 'text'\n        SearchResult<WorkflowSummary> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new WorkflowSummary()]\n\n        URI uri = createURI(\"workflow/search?start=$start&size=$size&sort=$sort&freeText=$freeText&query=$query\")\n\n        when:\n        SearchResult<WorkflowSummary> searchResult = workflowClient.search(start, size, sort, freeText, query)\n\n        then:\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof WorkflowSummary\n    }\n\n    def \"searchV2 with params\"() {\n        given:\n        String query = 'my_complex_query'\n        int start = 0\n        int size = 10\n        String sort = 'sort'\n        String freeText = 'text'\n        SearchResult<Workflow> result = new SearchResult<>()\n        result.totalHits = 1\n        result.results = [new Workflow(workflowDefinition: new WorkflowDef(), createTime: System.currentTimeMillis() )]\n\n        URI uri = createURI(\"workflow/search-v2?start=$start&size=$size&sort=$sort&freeText=$freeText&query=$query\")\n\n        when:\n        SearchResult<Workflow> searchResult = workflowClient.searchV2(start, size, sort, freeText, query)\n\n        then:\n        1 * requestHandler.get(uri) >> Mock(ClientResponse.class) {\n            getEntity(_) >> result\n        }\n\n        searchResult.totalHits == result.totalHits\n        searchResult.results && searchResult.results.size() == 1\n        searchResult.results[0] instanceof Workflow\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/automator/PollingSemaphoreTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.automator;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.stream.IntStream;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class PollingSemaphoreTest {\n\n    @Test\n    public void testBlockAfterAvailablePermitsExhausted() throws Exception {\n        int threads = 5;\n        ExecutorService executorService = Executors.newFixedThreadPool(threads);\n        PollingSemaphore pollingSemaphore = new PollingSemaphore(threads);\n\n        List<CompletableFuture<Void>> futuresList = new ArrayList<>();\n        IntStream.range(0, threads)\n                .forEach(\n                        t ->\n                                futuresList.add(\n                                        CompletableFuture.runAsync(\n                                                () -> pollingSemaphore.acquireSlots(1),\n                                                executorService)));\n\n        CompletableFuture<Void> allFutures =\n                CompletableFuture.allOf(\n                        futuresList.toArray(new CompletableFuture[futuresList.size()]));\n\n        allFutures.get();\n\n        assertEquals(0, pollingSemaphore.availableSlots());\n        assertFalse(pollingSemaphore.acquireSlots(1));\n\n        executorService.shutdown();\n    }\n\n    @Test\n    public void testAllowsPollingWhenPermitBecomesAvailable() throws Exception {\n        int threads = 5;\n        ExecutorService executorService = Executors.newFixedThreadPool(threads);\n        PollingSemaphore pollingSemaphore = new PollingSemaphore(threads);\n\n        List<CompletableFuture<Void>> futuresList = new ArrayList<>();\n        IntStream.range(0, threads)\n                .forEach(\n                        t ->\n                                futuresList.add(\n                                        CompletableFuture.runAsync(\n                                                () -> pollingSemaphore.acquireSlots(1),\n                                                executorService)));\n\n        CompletableFuture<Void> allFutures =\n                CompletableFuture.allOf(\n                        futuresList.toArray(new CompletableFuture[futuresList.size()]));\n        allFutures.get();\n\n        assertEquals(0, pollingSemaphore.availableSlots());\n        pollingSemaphore.complete(1);\n\n        assertTrue(pollingSemaphore.availableSlots() > 0);\n        assertTrue(pollingSemaphore.acquireSlots(1));\n\n        executorService.shutdown();\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/automator/TaskPollExecutorTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.automator;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.*;\n\nimport org.junit.Test;\nimport org.mockito.Mockito;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.discovery.EurekaClient;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskResult.Status.COMPLETED;\nimport static com.netflix.conductor.common.metadata.tasks.TaskResult.Status.IN_PROGRESS;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.*;\n\npublic class TaskPollExecutorTest {\n\n    private static final String TEST_TASK_DEF_NAME = \"test\";\n\n    private static final Map<String, Integer> TASK_THREAD_MAP =\n            Collections.singletonMap(TEST_TASK_DEF_NAME, 1);\n\n    @Test\n    public void testTaskExecutionException() throws InterruptedException {\n        Worker worker =\n                Worker.create(\n                        TEST_TASK_DEF_NAME,\n                        task -> {\n                            throw new NoSuchMethodError();\n                        });\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-%d\", TASK_THREAD_MAP);\n\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(testTask()));\n        when(taskClient.ack(any(), any())).thenReturn(true);\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            assertEquals(\"test-worker-1\", Thread.currentThread().getName());\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(TaskResult.Status.FAILED, result.getStatus());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n\n        latch.await();\n        verify(taskClient).updateTask(any());\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    @Test\n    public void testMultipleTasksExecution() throws InterruptedException {\n        String outputKey = \"KEY\";\n        Task task = testTask();\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(TEST_TASK_DEF_NAME);\n        when(worker.execute(any()))\n                .thenAnswer(\n                        new Answer() {\n                            private int count = 0;\n                            Map<String, Object> outputMap = new HashMap<>();\n\n                            public TaskResult answer(InvocationOnMock invocation)\n                                    throws InterruptedException {\n                                // Sleep for 2 seconds to simulate task execution\n                                Thread.sleep(2000L);\n                                TaskResult taskResult = new TaskResult(task);\n                                outputMap.put(outputKey, count++);\n                                taskResult.setOutputData(outputMap);\n                                return taskResult;\n                            }\n                        });\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n        when(taskClient.ack(any(), any())).thenReturn(true);\n        CountDownLatch latch = new CountDownLatch(3);\n        doAnswer(\n                        new Answer() {\n                            private int count = 0;\n\n                            public TaskResult answer(InvocationOnMock invocation) {\n                                Object[] args = invocation.getArguments();\n                                TaskResult result = (TaskResult) args[0];\n                                assertEquals(IN_PROGRESS, result.getStatus());\n                                assertEquals(count, result.getOutputData().get(outputKey));\n                                count++;\n                                latch.countDown();\n                                return null;\n                            }\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n        latch.await();\n\n        // execute() is called 3 times on the worker (once for each task)\n        verify(worker, times(3)).execute(any());\n        verify(taskClient, times(3)).updateTask(any());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testLargePayloadCanFailUpdateWithRetry() throws InterruptedException {\n        Task task = testTask();\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(TEST_TASK_DEF_NAME);\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n        when(taskClient.ack(any(), any())).thenReturn(true);\n\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertNull(result.getReasonForIncompletion());\n                            result.setReasonForIncompletion(\"some_reason_1\");\n                            throw new ConductorClientException();\n                        })\n                .when(taskClient)\n                .evaluateAndUploadLargePayload(any(Map.class), any());\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            latch.countDown();\n                            return null;\n                        })\n                .when(worker)\n                .onErrorUpdate(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n        latch.await();\n\n        // When evaluateAndUploadLargePayload fails indefinitely, task update shouldn't be called.\n        verify(taskClient, times(0)).updateTask(any());\n    }\n\n    @Test\n    public void testLargePayloadLocationUpdate() throws InterruptedException {\n        Task task = testTask();\n        String largePayloadLocation = \"large_payload_location\";\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(TEST_TASK_DEF_NAME);\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n        when(taskClient.ack(any(), any())).thenReturn(true);\n        //noinspection unchecked\n        when(taskClient.evaluateAndUploadLargePayload(any(Map.class), any()))\n                .thenReturn(Optional.of(largePayloadLocation));\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        CountDownLatch latch = new CountDownLatch(1);\n\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertNull(result.getOutputData());\n                            assertEquals(\n                                    largePayloadLocation,\n                                    result.getExternalOutputPayloadStoragePath());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n        latch.await();\n\n        verify(taskClient, times(1)).updateTask(any());\n    }\n\n    @Test\n    public void testTaskPollException() throws InterruptedException {\n        Task task = testTask();\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(TEST_TASK_DEF_NAME);\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenThrow(ConductorClientException.class)\n                .thenReturn(Arrays.asList(task));\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(IN_PROGRESS, result.getStatus());\n                            assertEquals(task.getTaskId(), result.getTaskId());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n\n        latch.await();\n        verify(taskClient).updateTask(any());\n    }\n\n    @Test\n    public void testTaskPoll() throws InterruptedException {\n        Task task = testTask();\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(\"test\");\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(IN_PROGRESS, result.getStatus());\n                            assertEquals(task.getTaskId(), result.getTaskId());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n\n        latch.await();\n        verify(taskClient).updateTask(any());\n    }\n\n    @Test\n    public void testTaskPollDomain() throws InterruptedException {\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        String testDomain = \"foo\";\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(TEST_TASK_DEF_NAME, testDomain);\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, taskToDomain, \"test-worker-\", TASK_THREAD_MAP);\n\n        String workerName = \"test-worker\";\n        Worker worker = mock(Worker.class);\n        when(worker.getTaskDefName()).thenReturn(TEST_TASK_DEF_NAME);\n        when(worker.getIdentity()).thenReturn(workerName);\n\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n\n        latch.await();\n        verify(taskClient).batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt());\n    }\n\n    @Test\n    public void testPollOutOfDiscoveryForTask() throws InterruptedException {\n        Task task = testTask();\n\n        EurekaClient client = mock(EurekaClient.class);\n        when(client.getInstanceRemoteStatus()).thenReturn(InstanceInfo.InstanceStatus.UNKNOWN);\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(\"task_run_always\");\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(new Task()))\n                .thenReturn(Arrays.asList(task));\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        client,\n                        taskClient,\n                        1,\n                        new HashMap<>(),\n                        \"test-worker-\",\n                        Collections.singletonMap(\"task_run_always\", 1));\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(IN_PROGRESS, result.getStatus());\n                            assertEquals(task.getTaskId(), result.getTaskId());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n\n        latch.await();\n        verify(taskClient).updateTask(any());\n    }\n\n    @Test\n    public void testPollOutOfDiscoveryAsDefaultFalseForTask()\n            throws ExecutionException, InterruptedException {\n        Task task = testTask();\n\n        EurekaClient client = mock(EurekaClient.class);\n        when(client.getInstanceRemoteStatus()).thenReturn(InstanceInfo.InstanceStatus.UNKNOWN);\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(\"task_do_not_run_always\");\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        client, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(IN_PROGRESS, result.getStatus());\n                            assertEquals(task.getTaskId(), result.getTaskId());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        ScheduledFuture f =\n                Executors.newSingleThreadScheduledExecutor()\n                        .schedule(\n                                () -> taskPollExecutor.pollAndExecute(worker), 0, TimeUnit.SECONDS);\n\n        f.get();\n        verify(taskClient, times(0)).updateTask(any());\n    }\n\n    @Test\n    public void testPollOutOfDiscoveryAsExplicitFalseForTask()\n            throws ExecutionException, InterruptedException {\n        Task task = testTask();\n\n        EurekaClient client = mock(EurekaClient.class);\n        when(client.getInstanceRemoteStatus()).thenReturn(InstanceInfo.InstanceStatus.UNKNOWN);\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(\"task_explicit_do_not_run_always\");\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        client, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(IN_PROGRESS, result.getStatus());\n                            assertEquals(task.getTaskId(), result.getTaskId());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        ScheduledFuture f =\n                Executors.newSingleThreadScheduledExecutor()\n                        .schedule(\n                                () -> taskPollExecutor.pollAndExecute(worker), 0, TimeUnit.SECONDS);\n\n        f.get();\n        verify(taskClient, times(0)).updateTask(any());\n    }\n\n    @Test\n    public void testPollOutOfDiscoveryIsIgnoredWhenDiscoveryIsUp() throws InterruptedException {\n        Task task = testTask();\n\n        EurekaClient client = mock(EurekaClient.class);\n        when(client.getInstanceRemoteStatus()).thenReturn(InstanceInfo.InstanceStatus.UP);\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(\"task_ignore_override\");\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        client,\n                        taskClient,\n                        1,\n                        new HashMap<>(),\n                        \"test-worker-\",\n                        Collections.singletonMap(\"task_ignore_override\", 1));\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(IN_PROGRESS, result.getStatus());\n                            assertEquals(task.getTaskId(), result.getTaskId());\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n\n        latch.await();\n        verify(taskClient).updateTask(any());\n    }\n\n    @Test\n    public void testTaskThreadCount() throws InterruptedException {\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n\n        Map<String, Integer> taskThreadCount = new HashMap<>();\n        taskThreadCount.put(TEST_TASK_DEF_NAME, 1);\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, -1, new HashMap<>(), \"test-worker-\", taskThreadCount);\n\n        String workerName = \"test-worker\";\n        Worker worker = mock(Worker.class);\n        when(worker.getTaskDefName()).thenReturn(TEST_TASK_DEF_NAME);\n        when(worker.getIdentity()).thenReturn(workerName);\n\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n\n        latch.await();\n        verify(taskClient).batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt());\n    }\n\n    @Test\n    public void testTaskLeaseExtend() throws InterruptedException {\n        Task task = testTask();\n        task.setResponseTimeoutSeconds(1);\n\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getTaskDefName()).thenReturn(\"test\");\n        when(worker.execute(any())).thenReturn(new TaskResult(task));\n        when(worker.leaseExtendEnabled()).thenReturn(true);\n\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenReturn(Arrays.asList(task));\n\n        TaskResult result = new TaskResult(task);\n        result.getLogs().add(new TaskExecLog(\"lease extend\"));\n        result.setExtendLease(true);\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-\", TASK_THREAD_MAP);\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            assertTrue(\n                                    taskPollExecutor.leaseExtendMap.containsKey(task.getTaskId()));\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 5, TimeUnit.SECONDS);\n\n        latch.await();\n    }\n\n    @Test\n    public void testBatchTasksExecution() throws InterruptedException {\n        int threadCount = 10;\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        Map<String, Integer> taskThreadCount = new HashMap<>();\n        taskThreadCount.put(TEST_TASK_DEF_NAME, threadCount);\n\n        String workerName = \"test-worker\";\n        Worker worker = mock(Worker.class);\n        when(worker.getPollingInterval()).thenReturn(3000);\n        when(worker.getBatchPollTimeoutInMS()).thenReturn(1000);\n        when(worker.getTaskDefName()).thenReturn(TEST_TASK_DEF_NAME);\n        when(worker.getIdentity()).thenReturn(workerName);\n\n        List<Task> tasks = new ArrayList<>();\n        for (int i = 0; i < threadCount; i++) {\n            Task task = testTask();\n            tasks.add(task);\n\n            when(worker.execute(task))\n                    .thenAnswer(\n                            new Answer() {\n                                Map<String, Object> outputMap = new HashMap<>();\n\n                                public TaskResult answer(InvocationOnMock invocation)\n                                        throws InterruptedException {\n                                    // Sleep for 1 seconds to simulate task execution\n                                    Thread.sleep(1000L);\n                                    TaskResult taskResult = new TaskResult(task);\n                                    outputMap.put(\"key\", \"value\");\n                                    taskResult.setOutputData(outputMap);\n                                    taskResult.setStatus(COMPLETED);\n                                    return taskResult;\n                                }\n                            });\n        }\n        when(taskClient.batchPollTasksInDomain(\n                        TEST_TASK_DEF_NAME, null, workerName, threadCount, 1000))\n                .thenReturn(tasks);\n        when(taskClient.ack(any(), any())).thenReturn(true);\n\n        TaskPollExecutor taskPollExecutor =\n                new TaskPollExecutor(\n                        null, taskClient, 1, new HashMap<>(), \"test-worker-\", taskThreadCount);\n\n        CountDownLatch latch = new CountDownLatch(threadCount);\n        doAnswer(\n                        new Answer() {\n                            public TaskResult answer(InvocationOnMock invocation) {\n                                Object[] args = invocation.getArguments();\n                                TaskResult result = (TaskResult) args[0];\n                                assertEquals(COMPLETED, result.getStatus());\n                                assertEquals(\"value\", result.getOutputData().get(\"key\"));\n                                latch.countDown();\n                                return null;\n                            }\n                        })\n                .when(taskClient)\n                .updateTask(any());\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(\n                        () -> taskPollExecutor.pollAndExecute(worker), 0, 1, TimeUnit.SECONDS);\n        latch.await();\n\n        // execute() is called 10 times on the worker (once for each task)\n        verify(worker, times(threadCount)).execute(any());\n        verify(taskClient, times(threadCount)).updateTask(any());\n    }\n\n    private Task testTask() {\n        Task task = new Task();\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setTaskDefName(TEST_TASK_DEF_NAME);\n        return task;\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/automator/TaskRunnerConfigurerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.automator;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskResult.Status.COMPLETED;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class TaskRunnerConfigurerTest {\n\n    private static final String TEST_TASK_DEF_NAME = \"test\";\n\n    private TaskClient client;\n\n    @Before\n    public void setup() {\n        client = Mockito.mock(TaskClient.class);\n    }\n\n    @Test(expected = NullPointerException.class)\n    public void testNoWorkersException() {\n        new TaskRunnerConfigurer.Builder(null, null).build();\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testInvalidThreadConfig() {\n        Worker worker1 = Worker.create(\"task1\", TaskResult::new);\n        Worker worker2 = Worker.create(\"task2\", TaskResult::new);\n        Map<String, Integer> taskThreadCount = new HashMap<>();\n        taskThreadCount.put(worker1.getTaskDefName(), 2);\n        taskThreadCount.put(worker2.getTaskDefName(), 3);\n        new TaskRunnerConfigurer.Builder(client, Arrays.asList(worker1, worker2))\n                .withThreadCount(10)\n                .withTaskThreadCount(taskThreadCount)\n                .build();\n    }\n\n    @Test\n    public void testMissingTaskThreadConfig() {\n        Worker worker1 = Worker.create(\"task1\", TaskResult::new);\n        Worker worker2 = Worker.create(\"task2\", TaskResult::new);\n        Map<String, Integer> taskThreadCount = new HashMap<>();\n        taskThreadCount.put(worker1.getTaskDefName(), 2);\n        TaskRunnerConfigurer configurer =\n                new TaskRunnerConfigurer.Builder(client, Arrays.asList(worker1, worker2))\n                        .withTaskThreadCount(taskThreadCount)\n                        .build();\n\n        assertFalse(configurer.getTaskThreadCount().isEmpty());\n        assertEquals(2, configurer.getTaskThreadCount().size());\n        assertEquals(2, configurer.getTaskThreadCount().get(\"task1\").intValue());\n        assertEquals(1, configurer.getTaskThreadCount().get(\"task2\").intValue());\n    }\n\n    @Test\n    public void testPerTaskThreadPool() {\n        Worker worker1 = Worker.create(\"task1\", TaskResult::new);\n        Worker worker2 = Worker.create(\"task2\", TaskResult::new);\n        Map<String, Integer> taskThreadCount = new HashMap<>();\n        taskThreadCount.put(worker1.getTaskDefName(), 2);\n        taskThreadCount.put(worker2.getTaskDefName(), 3);\n        TaskRunnerConfigurer configurer =\n                new TaskRunnerConfigurer.Builder(client, Arrays.asList(worker1, worker2))\n                        .withTaskThreadCount(taskThreadCount)\n                        .build();\n        configurer.init();\n        assertEquals(-1, configurer.getThreadCount());\n        assertEquals(2, configurer.getTaskThreadCount().get(\"task1\").intValue());\n        assertEquals(3, configurer.getTaskThreadCount().get(\"task2\").intValue());\n    }\n\n    @Test\n    public void testSharedThreadPool() {\n        Worker worker = Worker.create(TEST_TASK_DEF_NAME, TaskResult::new);\n        TaskRunnerConfigurer configurer =\n                new TaskRunnerConfigurer.Builder(client, Arrays.asList(worker, worker, worker))\n                        .build();\n        configurer.init();\n        assertEquals(3, configurer.getThreadCount());\n        assertEquals(500, configurer.getSleepWhenRetry());\n        assertEquals(3, configurer.getUpdateRetryCount());\n        assertEquals(10, configurer.getShutdownGracePeriodSeconds());\n        assertFalse(configurer.getTaskThreadCount().isEmpty());\n        assertEquals(1, configurer.getTaskThreadCount().size());\n        assertEquals(3, configurer.getTaskThreadCount().get(TEST_TASK_DEF_NAME).intValue());\n\n        configurer =\n                new TaskRunnerConfigurer.Builder(client, Collections.singletonList(worker))\n                        .withThreadCount(100)\n                        .withSleepWhenRetry(100)\n                        .withUpdateRetryCount(10)\n                        .withShutdownGracePeriodSeconds(15)\n                        .withWorkerNamePrefix(\"test-worker-\")\n                        .build();\n        assertEquals(100, configurer.getThreadCount());\n        configurer.init();\n        assertEquals(100, configurer.getThreadCount());\n        assertEquals(100, configurer.getSleepWhenRetry());\n        assertEquals(10, configurer.getUpdateRetryCount());\n        assertEquals(15, configurer.getShutdownGracePeriodSeconds());\n        assertEquals(\"test-worker-\", configurer.getWorkerNamePrefix());\n        assertFalse(configurer.getTaskThreadCount().isEmpty());\n        assertEquals(1, configurer.getTaskThreadCount().size());\n        assertEquals(100, configurer.getTaskThreadCount().get(TEST_TASK_DEF_NAME).intValue());\n    }\n\n    @Test\n    public void testMultipleWorkersExecution() throws Exception {\n        String task1Name = \"task1\";\n        Worker worker1 = mock(Worker.class);\n        when(worker1.getPollingInterval()).thenReturn(3000);\n        when(worker1.getTaskDefName()).thenReturn(task1Name);\n        when(worker1.getIdentity()).thenReturn(\"worker1\");\n        when(worker1.execute(any()))\n                .thenAnswer(\n                        invocation -> {\n                            // Sleep for 2 seconds to simulate task execution\n                            Thread.sleep(2000);\n                            TaskResult taskResult = new TaskResult();\n                            taskResult.setStatus(COMPLETED);\n                            return taskResult;\n                        });\n\n        String task2Name = \"task2\";\n        Worker worker2 = mock(Worker.class);\n        when(worker2.getPollingInterval()).thenReturn(3000);\n        when(worker2.getTaskDefName()).thenReturn(task2Name);\n        when(worker2.getIdentity()).thenReturn(\"worker2\");\n        when(worker2.execute(any()))\n                .thenAnswer(\n                        invocation -> {\n                            // Sleep for 2 seconds to simulate task execution\n                            Thread.sleep(2000);\n                            TaskResult taskResult = new TaskResult();\n                            taskResult.setStatus(COMPLETED);\n                            return taskResult;\n                        });\n\n        Task task1 = testTask(task1Name);\n        Task task2 = testTask(task2Name);\n        TaskClient taskClient = Mockito.mock(TaskClient.class);\n        TaskRunnerConfigurer configurer =\n                new TaskRunnerConfigurer.Builder(taskClient, Arrays.asList(worker1, worker2))\n                        .withThreadCount(2)\n                        .withSleepWhenRetry(100000)\n                        .withUpdateRetryCount(1)\n                        .withWorkerNamePrefix(\"test-worker-\")\n                        .build();\n        when(taskClient.batchPollTasksInDomain(any(), any(), any(), anyInt(), anyInt()))\n                .thenAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            String taskName = args[0].toString();\n                            if (taskName.equals(task1Name)) {\n                                return Arrays.asList(task1);\n                            } else if (taskName.equals(task2Name)) {\n                                return Arrays.asList(task2);\n                            } else {\n                                return Collections.emptyList();\n                            }\n                        });\n        when(taskClient.ack(any(), any())).thenReturn(true);\n\n        AtomicInteger task1Counter = new AtomicInteger(0);\n        AtomicInteger task2Counter = new AtomicInteger(0);\n        CountDownLatch latch = new CountDownLatch(2);\n        doAnswer(\n                        invocation -> {\n                            Object[] args = invocation.getArguments();\n                            TaskResult result = (TaskResult) args[0];\n                            assertEquals(COMPLETED, result.getStatus());\n                            if (result.getWorkerId().equals(\"worker1\")) {\n                                task1Counter.incrementAndGet();\n                            } else if (result.getWorkerId().equals(\"worker2\")) {\n                                task2Counter.incrementAndGet();\n                            }\n                            latch.countDown();\n                            return null;\n                        })\n                .when(taskClient)\n                .updateTask(any());\n        configurer.init();\n        latch.await();\n\n        assertEquals(1, task1Counter.get());\n        assertEquals(1, task2Counter.get());\n    }\n\n    private Task testTask(String taskDefName) {\n        Task task = new Task();\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setTaskDefName(taskDefName);\n        return task;\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/config/TestPropertyFactory.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.config;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class TestPropertyFactory {\n\n    @Test\n    public void testIdentity() {\n        Worker worker = Worker.create(\"Test2\", TaskResult::new);\n        assertNotNull(worker.getIdentity());\n        boolean paused = worker.paused();\n        assertFalse(\"Paused? \" + paused, paused);\n    }\n\n    @Test\n    public void test() {\n\n        int val = PropertyFactory.getInteger(\"workerB\", \"pollingInterval\", 100);\n        assertEquals(\"got: \" + val, 2, val);\n        assertEquals(\n                100, PropertyFactory.getInteger(\"workerB\", \"propWithoutValue\", 100).intValue());\n\n        assertFalse(\n                PropertyFactory.getBoolean(\n                        \"workerB\", \"paused\", true)); // Global value set to 'false'\n        assertTrue(\n                PropertyFactory.getBoolean(\n                        \"workerA\", \"paused\", false)); // WorkerA value set to 'true'\n\n        assertEquals(\n                42,\n                PropertyFactory.getInteger(\"workerA\", \"batchSize\", 42)\n                        .intValue()); // No global value set, so will return the default value\n        // supplied\n        assertEquals(\n                84,\n                PropertyFactory.getInteger(\"workerB\", \"batchSize\", 42)\n                        .intValue()); // WorkerB's value set to 84\n\n        assertEquals(\"domainA\", PropertyFactory.getString(\"workerA\", \"domain\", null));\n        assertEquals(\"domainB\", PropertyFactory.getString(\"workerB\", \"domain\", null));\n        assertNull(PropertyFactory.getString(\"workerC\", \"domain\", null)); // Non Existent\n    }\n\n    @Test\n    public void testProperty() {\n        Worker worker = Worker.create(\"Test\", TaskResult::new);\n        boolean paused = worker.paused();\n        assertTrue(\"Paused? \" + paused, paused);\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/sample/Main.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.sample;\n\nimport java.util.Arrays;\n\nimport com.netflix.conductor.client.automator.TaskRunnerConfigurer;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.worker.Worker;\n\npublic class Main {\n\n    public static void main(String[] args) {\n\n        TaskClient taskClient = new TaskClient();\n        taskClient.setRootURI(\"http://localhost:8080/api/\"); // Point this to the server API\n\n        int threadCount =\n                2; // number of threads used to execute workers.  To avoid starvation, should be\n        // same or more than number of workers\n\n        Worker worker1 = new SampleWorker(\"task_1\");\n        Worker worker2 = new SampleWorker(\"task_5\");\n\n        // Create TaskRunnerConfigurer\n        TaskRunnerConfigurer configurer =\n                new TaskRunnerConfigurer.Builder(taskClient, Arrays.asList(worker1, worker2))\n                        .withThreadCount(threadCount)\n                        .build();\n\n        // Start the polling and execution of tasks\n        configurer.init();\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/sample/SampleWorker.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.sample;\n\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult.Status;\n\npublic class SampleWorker implements Worker {\n\n    private final String taskDefName;\n\n    public SampleWorker(String taskDefName) {\n        this.taskDefName = taskDefName;\n    }\n\n    @Override\n    public String getTaskDefName() {\n        return taskDefName;\n    }\n\n    @Override\n    public TaskResult execute(Task task) {\n        TaskResult result = new TaskResult(task);\n        result.setStatus(Status.COMPLETED);\n\n        // Register the output of the task\n        result.getOutputData().put(\"outputKey1\", \"value\");\n        result.getOutputData().put(\"oddEven\", 1);\n        result.getOutputData().put(\"mod\", 4);\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/testing/AbstractWorkflowTests.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.testing;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.TestInstance;\n\nimport com.netflix.conductor.client.http.MetadataClient;\nimport com.netflix.conductor.client.http.WorkflowClient;\nimport com.netflix.conductor.common.config.ObjectMapperProvider;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowTestRequest;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n@TestInstance(TestInstance.Lifecycle.PER_CLASS)\npublic abstract class AbstractWorkflowTests {\n\n    protected static ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    protected static TypeReference<Map<String, List<WorkflowTestRequest.TaskMock>>> mockType =\n            new TypeReference<Map<String, List<WorkflowTestRequest.TaskMock>>>() {};\n\n    protected MetadataClient metadataClient;\n\n    protected WorkflowClient workflowClient;\n\n    @BeforeAll\n    public void setup() {\n\n        String baseURL = \"http://localhost:8080/api/\";\n        metadataClient = new MetadataClient();\n        metadataClient.setRootURI(baseURL);\n\n        workflowClient = new WorkflowClient();\n        workflowClient.setRootURI(baseURL);\n    }\n\n    protected WorkflowTestRequest getWorkflowTestRequest(WorkflowDef def) throws IOException {\n        WorkflowTestRequest testRequest = new WorkflowTestRequest();\n        testRequest.setInput(new HashMap<>());\n        testRequest.setName(def.getName());\n        testRequest.setVersion(def.getVersion());\n        testRequest.setWorkflowDef(def);\n\n        Map<String, List<WorkflowTestRequest.TaskMock>> taskRefToMockOutput = new HashMap<>();\n        for (WorkflowTask task : def.collectTasks()) {\n            List<WorkflowTestRequest.TaskMock> taskRuns = new LinkedList<>();\n            WorkflowTestRequest.TaskMock mock = new WorkflowTestRequest.TaskMock();\n            mock.setStatus(TaskResult.Status.COMPLETED);\n            Map<String, Object> output = new HashMap<>();\n\n            output.put(\"response\", Map.of());\n            mock.setOutput(output);\n            taskRuns.add(mock);\n            taskRefToMockOutput.put(task.getTaskReferenceName(), taskRuns);\n\n            if (task.getType().equals(TaskType.SUB_WORKFLOW.name())) {\n                Object inlineSubWorkflowDefObj = task.getSubWorkflowParam().getWorkflowDefinition();\n                if (inlineSubWorkflowDefObj != null) {\n                    // If not null, it represents WorkflowDef object\n                    WorkflowDef inlineSubWorkflowDef = (WorkflowDef) inlineSubWorkflowDefObj;\n                    WorkflowTestRequest subWorkflowTestRequest =\n                            getWorkflowTestRequest(inlineSubWorkflowDef);\n                    testRequest\n                            .getSubWorkflowTestRequest()\n                            .put(task.getTaskReferenceName(), subWorkflowTestRequest);\n                } else {\n                    // Inline definition is null\n                    String subWorkflowName = task.getSubWorkflowParam().getName();\n                    // Load up the sub workflow from the JSON\n                    WorkflowDef subWorkflowDef =\n                            getWorkflowDef(\"/workflows/\" + subWorkflowName + \".json\");\n                    assertNotNull(subWorkflowDef);\n                    WorkflowTestRequest subWorkflowTestRequest =\n                            getWorkflowTestRequest(subWorkflowDef);\n                    testRequest\n                            .getSubWorkflowTestRequest()\n                            .put(task.getTaskReferenceName(), subWorkflowTestRequest);\n                }\n            }\n        }\n        testRequest.setTaskRefToMockOutput(taskRefToMockOutput);\n        return testRequest;\n    }\n\n    protected WorkflowDef getWorkflowDef(String path) throws IOException {\n        InputStream inputStream = AbstractWorkflowTests.class.getResourceAsStream(path);\n        if (inputStream == null) {\n            throw new IOException(\"No file found at \" + path);\n        }\n        return objectMapper.readValue(new InputStreamReader(inputStream), WorkflowDef.class);\n    }\n\n    protected Workflow getWorkflow(String path) throws IOException {\n        InputStream inputStream = AbstractWorkflowTests.class.getResourceAsStream(path);\n        if (inputStream == null) {\n            throw new IOException(\"No file found at \" + path);\n        }\n        return objectMapper.readValue(new InputStreamReader(inputStream), Workflow.class);\n    }\n\n    protected Map<String, List<WorkflowTestRequest.TaskMock>> getTestInputs(String path)\n            throws IOException {\n        InputStream inputStream = AbstractWorkflowTests.class.getResourceAsStream(path);\n        if (inputStream == null) {\n            throw new IOException(\"No file found at \" + path);\n        }\n        return objectMapper.readValue(new InputStreamReader(inputStream), mockType);\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/testing/LoanWorkflowInput.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.testing;\n\nimport java.math.BigDecimal;\n\npublic class LoanWorkflowInput {\n\n    private String userEmail;\n\n    private BigDecimal loanAmount;\n\n    public String getUserEmail() {\n        return userEmail;\n    }\n\n    public void setUserEmail(String userEmail) {\n        this.userEmail = userEmail;\n    }\n\n    public BigDecimal getLoanAmount() {\n        return loanAmount;\n    }\n\n    public void setLoanAmount(BigDecimal loanAmount) {\n        this.loanAmount = loanAmount;\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/testing/LoanWorkflowTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.testing;\n\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.util.List;\nimport java.util.Map;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowTestRequest;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n/** Unit test a workflow with inputs read from a file. */\npublic class LoanWorkflowTest extends AbstractWorkflowTests {\n\n    /** Uses mock inputs to verify the workflow execution and input/outputs of the tasks */\n    // Tests are commented out since it requires a running server\n    // @Test\n    public void verifyWorkflowExecutionWithMockInputs() throws IOException {\n        WorkflowDef def = getWorkflowDef(\"/workflows/calculate_loan_workflow.json\");\n        assertNotNull(def);\n        Map<String, List<WorkflowTestRequest.TaskMock>> testInputs =\n                getTestInputs(\"/test_data/loan_workflow_input.json\");\n        assertNotNull(testInputs);\n\n        WorkflowTestRequest testRequest = new WorkflowTestRequest();\n        testRequest.setWorkflowDef(def);\n\n        LoanWorkflowInput workflowInput = new LoanWorkflowInput();\n        workflowInput.setUserEmail(\"user@example.com\");\n        workflowInput.setLoanAmount(new BigDecimal(11_000));\n        testRequest.setInput(objectMapper.convertValue(workflowInput, Map.class));\n\n        testRequest.setTaskRefToMockOutput(testInputs);\n        testRequest.setName(def.getName());\n        testRequest.setVersion(def.getVersion());\n\n        Workflow execution = workflowClient.testWorkflow(testRequest);\n        assertNotNull(execution);\n\n        // Assert that the workflow completed successfully\n        assertEquals(Workflow.WorkflowStatus.COMPLETED, execution.getStatus());\n\n        // Ensure the inputs were captured correctly\n        assertEquals(\n                workflowInput.getLoanAmount().toString(),\n                String.valueOf(execution.getInput().get(\"loanAmount\")));\n        assertEquals(workflowInput.getUserEmail(), execution.getInput().get(\"userEmail\"));\n\n        // A total of 3 tasks were executed\n        assertEquals(3, execution.getTasks().size());\n\n        Task fetchUserDetails = execution.getTasks().get(0);\n        Task getCreditScore = execution.getTasks().get(1);\n        Task calculateLoanAmount = execution.getTasks().get(2);\n\n        // fetch user details received the correct input from the workflow\n        assertEquals(\n                workflowInput.getUserEmail(), fetchUserDetails.getInputData().get(\"userEmail\"));\n\n        // And that the task produced the right output\n        int userAccountNo = 12345;\n        assertEquals(userAccountNo, fetchUserDetails.getOutputData().get(\"userAccount\"));\n\n        // get credit score received the right account number from the output of the fetch user\n        // details\n        assertEquals(userAccountNo, getCreditScore.getInputData().get(\"userAccountNumber\"));\n        int expectedCreditRating = 750;\n\n        // The task produced the right output\n        assertEquals(expectedCreditRating, getCreditScore.getOutputData().get(\"creditRating\"));\n\n        // Calculate loan amount gets the right loan amount from workflow input\n        assertEquals(\n                workflowInput.getLoanAmount().toString(),\n                String.valueOf(calculateLoanAmount.getInputData().get(\"loanAmount\")));\n\n        // Calculate loan amount gets the right credit rating from the previous task\n        assertEquals(expectedCreditRating, calculateLoanAmount.getInputData().get(\"creditRating\"));\n\n        int authorizedLoanAmount = 10_000;\n        assertEquals(\n                authorizedLoanAmount,\n                calculateLoanAmount.getOutputData().get(\"authorizedLoanAmount\"));\n\n        // Finally, lets verify the workflow outputs\n        assertEquals(userAccountNo, execution.getOutput().get(\"accountNumber\"));\n        assertEquals(expectedCreditRating, execution.getOutput().get(\"creditRating\"));\n        assertEquals(authorizedLoanAmount, execution.getOutput().get(\"authorizedLoanAmount\"));\n\n        System.out.println(execution);\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/testing/RegressionTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.testing;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeoutException;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowTestRequest;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n/**\n * This test demonstrates how to use execution data from the previous executed workflows as golden\n * input and output and use them to regression test the workflow definition.\n *\n * <p>Regression tests are useful ensuring any changes to the workflow definition does not change\n * the behavior.\n */\npublic class RegressionTest extends AbstractWorkflowTests {\n\n    // @Test\n    // Tests are commented out since it requires a running server\n    // Uses a previously executed successful run to verify the workflow execution, and it's output.\n    public void verifyWorkflowOutput()\n            throws IOException, ExecutionException, InterruptedException, TimeoutException {\n        // Workflow Definition\n        WorkflowDef def = getWorkflowDef(\"/workflows/workflow1.json\");\n\n        // Golden output to verify against\n        Workflow workflow = getWorkflow(\"/test_data/workflow1_run.json\");\n\n        WorkflowTestRequest testRequest = new WorkflowTestRequest();\n        testRequest.setInput(new HashMap<>());\n        testRequest.setName(def.getName());\n        testRequest.setVersion(def.getVersion());\n        testRequest.setWorkflowDef(def);\n\n        Map<String, List<WorkflowTestRequest.TaskMock>> taskRefToMockOutput = new HashMap<>();\n        for (Task task : workflow.getTasks()) {\n            List<WorkflowTestRequest.TaskMock> taskRuns = new ArrayList<>();\n            WorkflowTestRequest.TaskMock mock = new WorkflowTestRequest.TaskMock();\n            mock.setStatus(TaskResult.Status.valueOf(task.getStatus().name()));\n            mock.setOutput(task.getOutputData());\n            taskRuns.add(mock);\n            taskRefToMockOutput.put(def.getTasks().get(0).getTaskReferenceName(), taskRuns);\n        }\n\n        testRequest.setTaskRefToMockOutput(taskRefToMockOutput);\n        Workflow execution = workflowClient.testWorkflow(testRequest);\n        assertNotNull(execution);\n        assertEquals(workflow.getTasks().size(), execution.getTasks().size());\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/testing/SubWorkflowTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.testing;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowTestRequest;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/** Demonstrates how to test workflows that contain sub-workflows */\npublic class SubWorkflowTest extends AbstractWorkflowTests {\n\n    // @Test\n    // Tests are commented out since it requires a running server\n    public void verifySubWorkflowExecutions() throws IOException {\n        WorkflowDef def = getWorkflowDef(\"/workflows/kitchensink.json\");\n        assertNotNull(def);\n\n        WorkflowDef subWorkflowDef = getWorkflowDef(\"/workflows/PopulationMinMax.json\");\n        metadataClient.registerWorkflowDef(subWorkflowDef);\n\n        WorkflowTestRequest testRequest = getWorkflowTestRequest(def);\n\n        // The following are the dynamic tasks which are not present in the workflow definition but\n        // are created by dynamic fork\n        testRequest\n                .getTaskRefToMockOutput()\n                .put(\"_x_test_worker_0_0\", List.of(new WorkflowTestRequest.TaskMock()));\n        testRequest\n                .getTaskRefToMockOutput()\n                .put(\"_x_test_worker_0_1\", List.of(new WorkflowTestRequest.TaskMock()));\n        testRequest\n                .getTaskRefToMockOutput()\n                .put(\"_x_test_worker_0_2\", List.of(new WorkflowTestRequest.TaskMock()));\n        testRequest\n                .getTaskRefToMockOutput()\n                .put(\"simple_task_1__1\", List.of(new WorkflowTestRequest.TaskMock()));\n        testRequest\n                .getTaskRefToMockOutput()\n                .put(\"simple_task_5\", List.of(new WorkflowTestRequest.TaskMock()));\n\n        Workflow execution = workflowClient.testWorkflow(testRequest);\n        assertNotNull(execution);\n\n        // Verfiy that the workflow COMPLETES\n        assertEquals(Workflow.WorkflowStatus.COMPLETED, execution.getStatus());\n\n        // That the workflow executes a wait task\n        assertTrue(\n                execution.getTasks().stream()\n                        .anyMatch(t -> t.getReferenceTaskName().equals(\"wait\")));\n\n        // That the call_made variable was set to True\n        assertEquals(true, execution.getVariables().get(\"call_made\"));\n\n        // Total number of tasks executed are 17\n        assertEquals(17, execution.getTasks().size());\n    }\n}\n"
  },
  {
    "path": "client/src/test/java/com/netflix/conductor/client/worker/TestWorkflowTask.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.worker;\n\nimport java.io.InputStream;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class TestWorkflowTask {\n\n    private ObjectMapper objectMapper;\n\n    @Before\n    public void setup() {\n        objectMapper = new ObjectMapperProvider().getObjectMapper();\n    }\n\n    @Test\n    public void test() throws Exception {\n        WorkflowTask task = new WorkflowTask();\n        task.setType(\"Hello\");\n        task.setName(\"name\");\n\n        String json = objectMapper.writeValueAsString(task);\n\n        WorkflowTask read = objectMapper.readValue(json, WorkflowTask.class);\n        assertNotNull(read);\n        assertEquals(task.getName(), read.getName());\n        assertEquals(task.getType(), read.getType());\n\n        task = new WorkflowTask();\n        task.setWorkflowTaskType(TaskType.SUB_WORKFLOW);\n        task.setName(\"name\");\n\n        json = objectMapper.writeValueAsString(task);\n\n        read = objectMapper.readValue(json, WorkflowTask.class);\n        assertNotNull(read);\n        assertEquals(task.getName(), read.getName());\n        assertEquals(task.getType(), read.getType());\n        assertEquals(TaskType.SUB_WORKFLOW.name(), read.getType());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testObjectMapper() throws Exception {\n        try (InputStream stream = TestWorkflowTask.class.getResourceAsStream(\"/tasks.json\")) {\n            List<Task> tasks = objectMapper.readValue(stream, List.class);\n            assertNotNull(tasks);\n            assertEquals(1, tasks.size());\n        }\n    }\n}\n"
  },
  {
    "path": "client/src/test/resources/config.properties",
    "content": "conductor.worker.pollingInterval=2\nconductor.worker.paused=false\nconductor.worker.workerA.paused=true\nconductor.worker.workerA.domain=domainA\nconductor.worker.workerB.batchSize=84\nconductor.worker.workerB.domain=domainB\nconductor.worker.Test.paused=true\nconductor.worker.domainTestTask2.domain=visinghDomain\nconductor.worker.task_run_always.pollOutOfDiscovery=true\nconductor.worker.task_explicit_do_not_run_always.pollOutOfDiscovery=false\nconductor.worker.task_ignore_override.pollOutOfDiscovery=true"
  },
  {
    "path": "client/src/test/resources/tasks.json",
    "content": "[\n  {\n    \"taskType\": \"task_1\",\n    \"status\": \"IN_PROGRESS\",\n    \"inputData\": {\n      \"mod\": null,\n      \"oddEven\": null\n    },\n    \"referenceTaskName\": \"task_1\",\n    \"retryCount\": 0,\n    \"seq\": 1,\n    \"pollCount\": 1,\n    \"taskDefName\": \"task_1\",\n    \"scheduledTime\": 1539623183131,\n    \"startTime\": 1539623436841,\n    \"endTime\": 0,\n    \"updateTime\": 1539623436841,\n    \"startDelayInSeconds\": 0,\n    \"retried\": false,\n    \"executed\": false,\n    \"callbackFromWorker\": true,\n    \"responseTimeoutSeconds\": 0,\n    \"workflowInstanceId\": \"2d525ed8-d0e5-44c8-a2df-a110b25c09ac\",\n    \"workflowType\": \"kitchensink\",\n    \"taskId\": \"bc5d9deb-cf86-443d-a1f6-59c36d2464f7\",\n    \"callbackAfterSeconds\": 0,\n    \"workerId\": \"test\",\n    \"workflowTask\": {\n      \"name\": \"task_1\",\n      \"taskReferenceName\": \"task_1\",\n      \"inputParameters\": {\n        \"mod\": \"${workflow.input.mod}\",\n        \"oddEven\": \"${workflow.input.oddEven}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"optional\": false,\n      \"taskDefinition\": {\n        \"ownerApp\": \"falguni-test\",\n        \"createTime\": 1534274994644,\n        \"createdBy\": \"CPEWORKFLOW\",\n        \"name\": \"task_1\",\n        \"description\": \"Test Task 01\",\n        \"retryCount\": 0,\n        \"timeoutSeconds\": 5,\n        \"inputKeys\": [\n          \"mod\",\n          \"oddEven\"\n        ],\n        \"outputKeys\": [\n          \"someOutput\"\n        ],\n        \"timeoutPolicy\": \"TIME_OUT_WF\",\n        \"retryLogic\": \"FIXED\",\n        \"retryDelaySeconds\": 0,\n        \"responseTimeoutSeconds\": 0,\n        \"concurrentExecLimit\": 0,\n        \"rateLimitPerFrequency\": 0,\n        \"rateLimitFrequencyInSeconds\": 1\n      }\n    },\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 0,\n    \"taskDefinition\": {\n      \"present\": true\n    },\n    \"queueWaitTime\": 253710,\n    \"taskStatus\": \"IN_PROGRESS\"\n  }\n]"
  },
  {
    "path": "client/src/test/resources/test_data/loan_workflow_input.json",
    "content": "{\n  \"fetch_user_details\": [{\n    \"status\": \"COMPLETED\",\n    \"output\": {\n      \"userAccount\": 12345\n    }\n  }],\n  \"get_credit_score\": [{\n    \"status\": \"COMPLETED\",\n    \"output\": {\n      \"creditRating\": 750\n    }\n  }],\n  \"calculate_loan_amount\": [{\n    \"status\": \"COMPLETED\",\n    \"output\": {\n      \"authorizedLoanAmount\": 10000\n    }\n  }]\n}"
  },
  {
    "path": "client/src/test/resources/test_data/workflow1_run.json",
    "content": "{\n  \"createTime\": 1675903039613,\n  \"updateTime\": 1675903040396,\n  \"createdBy\": \"\",\n  \"updatedBy\": \"\",\n  \"status\": \"COMPLETED\",\n  \"endTime\": 1675903040396,\n  \"workflowId\": \"e90bd2d6-a811-11ed-bd43-f84d89b1eac3\",\n  \"parentWorkflowId\": \"\",\n  \"parentWorkflowTaskId\": \"\",\n  \"tasks\": [\n    {\n      \"taskType\": \"HTTP\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"asyncComplete\": false,\n        \"http_request\": {\n          \"method\": \"GET\",\n          \"readTimeOut\": 3000,\n          \"uri\": \"https://catfact.ninja/fact\",\n          \"connectionTimeOut\": 3000\n        }\n      },\n      \"referenceTaskName\": \"get_random_fact\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"pollCount\": 1,\n      \"taskDefName\": \"get_random_fact\",\n      \"scheduledTime\": 1675903039616,\n      \"startTime\": 1675903039623,\n      \"endTime\": 1675903040391,\n      \"updateTime\": 1675903039623,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e90bd2d6-a811-11ed-bd43-f84d89b1eac3\",\n      \"workflowType\": \"test_http\",\n      \"taskId\": \"e90c4807-a811-11ed-bd43-f84d89b1eac3\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"n33l3\",\n      \"outputData\": {\n        \"response\": {\n          \"headers\": {\n            \"Transfer-Encoding\": [\n              \"chunked\"\n            ],\n            \"Server\": [\n              \"nginx\"\n            ],\n            \"X-Ratelimit-Remaining\": [\n              \"99\"\n            ],\n            \"Access-Control-Allow-Origin\": [\n              \"*\"\n            ],\n            \"X-Content-Type-Options\": [\n              \"nosniff\"\n            ],\n            \"Connection\": [\n              \"keep-alive\"\n            ],\n            \"Date\": [\n              \"Thu, 09 Feb 2023 00:37:20 GMT\"\n            ],\n            \"X-Frame-Options\": [\n              \"SAMEORIGIN\"\n            ],\n            \"X-Ratelimit-Limit\": [\n              \"100\"\n            ],\n            \"Cache-Control\": [\n              \"no-cache, private\"\n            ],\n            \"Vary\": [\n              \"Accept-Encoding\"\n            ],\n            \"Set-Cookie\": [\n              \"XSRF-TOKEN=eyJpdiI6IjNNRUJ4ellWaU1HSWdGMzNWWlEzdXc9PSIsInZhbHVlIjoiYUozemozNWJJUERFVzZ2QU5TTFdGdS9oY3krclg2dWlKa0oza3gwUFlrNTd4L2YydWFSYjFKTjNzdFArRmlIL2lHbGEvU2tnbm0vWjZGZDVuMWx6UEVaT1AwNlM5REp1b0dMaWFTZldYN1FSblJQSFZSalREWXhiVUVrNUpMYXAiLCJtYWMiOiI1MjFjOWY2MDFhNWZkMDFlOGNjYjE0MmU1YmU1MGEwODQ3ZTBjNTdkMzRiZWMzYWQyMjk3NzFkNGYwYTU5NWVlIiwidGFnIjoiIn0%3D; expires=Thu, 09-Feb-2023 02:37:20 GMT; path=/; samesite=lax\",\n              \"catfacts_session=eyJpdiI6IjRkMThJVWZFRnREdWExWHZ5Q0k0cEE9PSIsInZhbHVlIjoiOXZFYzJsb3IvUGZFV2tQSUVNTEVzMDJjTGRzczl2bXhtRW1PUytxTERITHp3b3dNQlBtdXlwdENMcThXTU82S1JBOHlJMU01ZlBoYUVPeG1ETmhRZEFaUDFOU0pxdHFXQ0xEWUhTQXFpcSt0SC82dmNsSDFWdmxpUFFyUjM1c3EiLCJtYWMiOiIwNzRhYTRiZjA5Nzg5M2NmMGE1NjIxMDk0NGYwNjE3MDYyZmJmMTRmYzExZGMzYWI1MTQwOWYyZjMzZGFjOGZiIiwidGFnIjoiIn0%3D; expires=Thu, 09-Feb-2023 02:37:20 GMT; path=/; httponly; samesite=lax\"\n            ],\n            \"X-XSS-Protection\": [\n              \"1; mode=block\"\n            ],\n            \"Content-Type\": [\n              \"application/json\"\n            ]\n          },\n          \"reasonPhrase\": \"OK\",\n          \"body\": {\n            \"fact\": \"Cats' hearing stops at 65 khz (kilohertz); humans' hearing stops at 20 khz.\",\n            \"length\": 75\n          },\n          \"statusCode\": 200\n        }\n      },\n      \"workflowTask\": {\n        \"name\": \"get_random_fact\",\n        \"taskReferenceName\": \"get_random_fact\",\n        \"inputParameters\": {\n          \"asyncComplete\": false,\n          \"http_request\": {\n            \"method\": \"GET\",\n            \"readTimeOut\": 3000,\n            \"uri\": \"https://catfact.ninja/fact\",\n            \"connectionTimeOut\": 3000\n          }\n        },\n        \"type\": \"HTTP\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"rateLimited\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"loopOverTask\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 7\n    }\n  ],\n  \"input\": {\n    \"_X-Request-Id\": \"9aceedeb-3b3c-4074-8cad-79edaac3809b\",\n    \"_X-Host-Id\": \"localhost\"\n  },\n  \"output\": {\n    \"data\": \"Cats' hearing stops at 65 khz (kilohertz); humans' hearing stops at 20 khz.\"\n  },\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [],\n  \"workflowDefinition\": {\n    \"name\": \"test_http\",\n    \"description\": \"v1\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"get_random_fact\",\n        \"taskReferenceName\": \"get_random_fact\",\n        \"inputParameters\": {\n          \"asyncComplete\": false,\n          \"http_request\": {\n            \"method\": \"GET\",\n            \"readTimeOut\": 3000,\n            \"uri\": \"https://catfact.ninja/fact\",\n            \"connectionTimeOut\": 3000\n          }\n        },\n        \"type\": \"HTTP\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"rateLimited\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [],\n    \"outputParameters\": {\n      \"data\": \"${get_random_fact.output.response.body.fact}\"\n    },\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": false,\n    \"ownerEmail\": \"user@orkes.io\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1675903039613,\n  \"workflowVersion\": 1,\n  \"workflowName\": \"test_http\"\n}"
  },
  {
    "path": "client/src/test/resources/workflows/PopulationMinMax.json",
    "content": "{\n  \"createTime\": 1670136356629,\n  \"updateTime\": 1670136356636,\n  \"name\": \"PopulationMinMax\",\n  \"description\": \"Edit or extend this sample workflow. Set the workflow name to get started\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"set_variable_task_jqc56h_ref\",\n      \"taskReferenceName\": \"set_variable_task_jqc56h_ref\",\n      \"inputParameters\": {\n        \"name\": \"Orkes\"\n      },\n      \"type\": \"SET_VARIABLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"data\": \"${get_random_fact.output.response.body.fact}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"ownerEmail\": \"user@orkes.io\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"variables\": {},\n  \"inputTemplate\": {}\n}"
  },
  {
    "path": "client/src/test/resources/workflows/calculate_loan_workflow.json",
    "content": "{\n  \"name\": \"test_workflow\",\n  \"description\": \"Edit or extend this sample workflow. Set the workflow name to get started\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fetch_user_details\",\n      \"taskReferenceName\": \"fetch_user_details\",\n      \"type\": \"SIMPLE\",\n      \"inputParameters\": {\n        \"userEmail\": \"${workflow.input.userEmail}\"\n      }\n    },\n    {\n      \"name\": \"get_credit_score\",\n      \"taskReferenceName\": \"get_credit_score\",\n      \"type\": \"SIMPLE\",\n      \"inputParameters\": {\n        \"userAccountNumber\": \"${fetch_user_details.output.userAccount}\"\n      }\n    },\n    {\n      \"name\": \"calculate_loan_amount\",\n      \"taskReferenceName\": \"calculate_loan_amount\",\n      \"type\": \"SIMPLE\",\n      \"inputParameters\": {\n        \"creditRating\": \"${get_credit_score.output.creditRating}\",\n        \"loanAmount\": \"${workflow.input.loanAmount}\"\n      }\n    }\n  ],\n  \"inputParameters\": [\n    \"userEmail\"\n  ],\n  \"outputParameters\": {\n    \"accountNumber\": \"${fetch_user_details.output.userAccount}\",\n    \"creditRating\": \"${get_credit_score.output.creditRating}\",\n    \"authorizedLoanAmount\": \"${calculate_loan_amount.output.authorizedLoanAmount}\"\n  },\n  \"schemaVersion\": 2\n}"
  },
  {
    "path": "client/src/test/resources/workflows/kitchensink.json",
    "content": "{\n  \"createTime\": 1670136330055,\n  \"updateTime\": 1670176591044,\n  \"name\": \"kitchensink\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"x_test_worker_2\",\n      \"taskReferenceName\": \"simple_task_0\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"jq\",\n      \"taskReferenceName\": \"jq\",\n      \"inputParameters\": {\n        \"key1\": {\n          \"value1\": [\n            \"a\",\n            \"b\"\n          ]\n        },\n        \"queryExpression\": \"{ key3: (.key1.value1 + .key2.value2) }\",\n        \"value2\": [\n          \"d\",\n          \"e\"\n        ]\n      },\n      \"type\": \"JSON_JQ_TRANSFORM\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"wait\",\n      \"taskReferenceName\": \"wait\",\n      \"inputParameters\": {\n        \"duration\": \"1 s\"\n      },\n      \"type\": \"WAIT\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"set_state\",\n      \"taskReferenceName\": \"set_state\",\n      \"inputParameters\": {\n        \"call_made\": true,\n        \"number\": \"${simple_task_0.output.number}\"\n      },\n      \"type\": \"SET_VARIABLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"sub_flow\",\n      \"taskReferenceName\": \"sub_flow\",\n      \"inputParameters\": {},\n      \"type\": \"SUB_WORKFLOW\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"subWorkflowParam\": {\n        \"name\": \"PopulationMinMax\"\n      },\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"dynamic_fork\",\n      \"taskReferenceName\": \"dynamic_fork\",\n      \"inputParameters\": {\n        \"forkTaskName\": \"x_test_worker_0\",\n        \"forkTaskInputs\": [\n          1,\n          2,\n          3\n        ]\n      },\n      \"type\": \"FORK_JOIN_DYNAMIC\",\n      \"decisionCases\": {},\n      \"dynamicForkTasksParam\": \"forkedTasks\",\n      \"dynamicForkTasksInputParamName\": \"forkedTasksInputs\",\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"dynamic_fork_join\",\n      \"taskReferenceName\": \"dynamic_fork_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"fork\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"loop_until_success\",\n            \"taskReferenceName\": \"loop_until_success\",\n            \"inputParameters\": {\n              \"loop_count\": 2\n            },\n            \"type\": \"DO_WHILE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": true,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopCondition\": \"if ( $.loop_count['iteration'] < $.loop_until_success ) { true; } else { false; }\",\n            \"loopOver\": [\n              {\n                \"name\": \"fact_length\",\n                \"taskReferenceName\": \"fact_length\",\n                \"description\": \"Fail if the fact is too short\",\n                \"inputParameters\": {\n                  \"number\": \"${get_data.output.number}\"\n                },\n                \"type\": \"SWITCH\",\n                \"decisionCases\": {\n                  \"LONG\": [\n                    {\n                      \"name\": \"x_test_worker_1\",\n                      \"taskReferenceName\": \"simple_task_1\",\n                      \"inputParameters\": {},\n                      \"type\": \"SIMPLE\",\n                      \"decisionCases\": {},\n                      \"defaultCase\": [],\n                      \"forkTasks\": [],\n                      \"startDelay\": 0,\n                      \"joinOn\": [],\n                      \"optional\": false,\n                      \"defaultExclusiveJoinTask\": [],\n                      \"asyncComplete\": false,\n                      \"loopOver\": []\n                    }\n                  ],\n                  \"SHORT\": [\n                    {\n                      \"name\": \"too_short\",\n                      \"taskReferenceName\": \"too_short\",\n                      \"inputParameters\": {\n                        \"terminationReason\": \"value too short\",\n                        \"terminationStatus\": \"FAILED\"\n                      },\n                      \"type\": \"TERMINATE\",\n                      \"decisionCases\": {},\n                      \"defaultCase\": [],\n                      \"forkTasks\": [],\n                      \"startDelay\": 0,\n                      \"joinOn\": [],\n                      \"optional\": false,\n                      \"defaultExclusiveJoinTask\": [],\n                      \"asyncComplete\": false,\n                      \"loopOver\": []\n                    }\n                  ]\n                },\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": [],\n                \"evaluatorType\": \"javascript\",\n                \"expression\": \"$.number < 15 ? 'LONG':'LONG'\"\n              }\n            ]\n          },\n          {\n            \"name\": \"sub_flow_inline\",\n            \"taskReferenceName\": \"sub_flow_inline\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"inline_sub\",\n              \"version\": 1,\n              \"workflowDefinition\": {\n                \"name\": \"inline_sub\",\n                \"version\": 1,\n                \"tasks\": [\n                  {\n                    \"name\": \"x_test_worker_2\",\n                    \"taskReferenceName\": \"simple_task_0\",\n                    \"inputParameters\": {},\n                    \"type\": \"SIMPLE\",\n                    \"decisionCases\": {},\n                    \"defaultCase\": [],\n                    \"forkTasks\": [],\n                    \"startDelay\": 0,\n                    \"joinOn\": [],\n                    \"optional\": false,\n                    \"defaultExclusiveJoinTask\": [],\n                    \"asyncComplete\": false,\n                    \"loopOver\": []\n                  },\n                  {\n                    \"name\": \"fact_length2\",\n                    \"taskReferenceName\": \"fact_length2\",\n                    \"description\": \"Fail if the fact is too short\",\n                    \"inputParameters\": {\n                      \"number\": \"${get_data.output.number}\"\n                    },\n                    \"type\": \"SWITCH\",\n                    \"decisionCases\": {\n                      \"LONG\": [\n                        {\n                          \"name\": \"x_test_worker_1\",\n                          \"taskReferenceName\": \"simple_task_1\",\n                          \"inputParameters\": {},\n                          \"type\": \"SIMPLE\",\n                          \"decisionCases\": {},\n                          \"defaultCase\": [],\n                          \"forkTasks\": [],\n                          \"startDelay\": 0,\n                          \"joinOn\": [],\n                          \"optional\": false,\n                          \"defaultExclusiveJoinTask\": [],\n                          \"asyncComplete\": false,\n                          \"loopOver\": []\n                        }\n                      ],\n                      \"SHORT\": [\n                        {\n                          \"name\": \"too_short\",\n                          \"taskReferenceName\": \"too_short\",\n                          \"inputParameters\": {\n                            \"terminationReason\": \"value too short\",\n                            \"terminationStatus\": \"FAILED\"\n                          },\n                          \"type\": \"TERMINATE\",\n                          \"decisionCases\": {},\n                          \"defaultCase\": [],\n                          \"forkTasks\": [],\n                          \"startDelay\": 0,\n                          \"joinOn\": [],\n                          \"optional\": false,\n                          \"defaultExclusiveJoinTask\": [],\n                          \"asyncComplete\": false,\n                          \"loopOver\": []\n                        }\n                      ]\n                    },\n                    \"defaultCase\": [],\n                    \"forkTasks\": [],\n                    \"startDelay\": 0,\n                    \"joinOn\": [],\n                    \"optional\": false,\n                    \"defaultExclusiveJoinTask\": [],\n                    \"asyncComplete\": false,\n                    \"loopOver\": [],\n                    \"evaluatorType\": \"javascript\",\n                    \"expression\": \"$.number < 15 ? 'LONG':'LONG'\"\n                  },\n                  {\n                    \"name\": \"sub_flow_inline_lvl2\",\n                    \"taskReferenceName\": \"sub_flow_inline_lvl2\",\n                    \"inputParameters\": {},\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"decisionCases\": {},\n                    \"defaultCase\": [],\n                    \"forkTasks\": [],\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"inline_sub\",\n                      \"version\": 1,\n                      \"workflowDefinition\": {\n                        \"name\": \"inline_sub\",\n                        \"version\": 1,\n                        \"tasks\": [\n                          {\n                            \"name\": \"x_test_worker_2\",\n                            \"taskReferenceName\": \"simple_task_0\",\n                            \"inputParameters\": {},\n                            \"type\": \"SIMPLE\",\n                            \"decisionCases\": {},\n                            \"defaultCase\": [],\n                            \"forkTasks\": [],\n                            \"startDelay\": 0,\n                            \"joinOn\": [],\n                            \"optional\": false,\n                            \"defaultExclusiveJoinTask\": [],\n                            \"asyncComplete\": false,\n                            \"loopOver\": []\n                          }\n                        ],\n                        \"inputParameters\": [],\n                        \"outputParameters\": {},\n                        \"schemaVersion\": 2,\n                        \"restartable\": true,\n                        \"workflowStatusListenerEnabled\": false,\n                        \"timeoutPolicy\": \"ALERT_ONLY\",\n                        \"timeoutSeconds\": 0,\n                        \"variables\": {},\n                        \"inputTemplate\": {}\n                      }\n                    },\n                    \"joinOn\": [],\n                    \"optional\": false,\n                    \"defaultExclusiveJoinTask\": [],\n                    \"asyncComplete\": false,\n                    \"loopOver\": [],\n                    \"taskDefinition\": {\n                      \"name\": \"sub_flow_inline\",\n                      \"description\": \"sub_flow_inline\",\n                      \"retryCount\": 0,\n                      \"timeoutSeconds\": 3000,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 20,\n                      \"inputTemplate\": {},\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1,\n                      \"pollTimeoutSeconds\": 3600,\n                      \"backoffScaleFactor\": 1\n                    }\n                  }\n                ],\n                \"inputParameters\": [],\n                \"outputParameters\": {},\n                \"schemaVersion\": 2,\n                \"restartable\": true,\n                \"workflowStatusListenerEnabled\": false,\n                \"timeoutPolicy\": \"ALERT_ONLY\",\n                \"timeoutSeconds\": 0,\n                \"variables\": {},\n                \"inputTemplate\": {}\n              }\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": [],\n            \"taskDefinition\": {\n              \"name\": \"sub_flow_inline\",\n              \"description\": \"sub_flow_inline\",\n              \"retryCount\": 0,\n              \"timeoutSeconds\": 3000,\n              \"timeoutPolicy\": \"TIME_OUT_WF\",\n              \"retryLogic\": \"FIXED\",\n              \"retryDelaySeconds\": 60,\n              \"responseTimeoutSeconds\": 20,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"pollTimeoutSeconds\": 3600,\n              \"backoffScaleFactor\": 1\n            }\n          }\n        ],\n        [\n          {\n            \"name\": \"x_test_worker_1\",\n            \"taskReferenceName\": \"simple_task_5\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [\"sub_flow_inline\",\"simple_task_5\"],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"fork_join\",\n      \"taskReferenceName\": \"fork_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\"simple_task_5\",\"sub_flow_inline\"],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"ownerEmail\": \"user@orkes.io\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"variables\": {},\n  \"inputTemplate\": {}\n}"
  },
  {
    "path": "client/src/test/resources/workflows/workflow1.json",
    "content": "{\n  \"createTime\": 1674453020104,\n  \"updateTime\": 1674453020105,\n  \"name\": \"test_http\",\n  \"description\": \"v1\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"get_random_fact\",\n      \"taskReferenceName\": \"get_random_fact\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"https://catfact.ninja/fact\",\n          \"method\": \"GET\",\n          \"connectionTimeOut\": 3000,\n          \"readTimeOut\": 3000\n        }\n      },\n      \"type\": \"HTTP\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"data\": \"${get_random_fact.output.response.body.fact}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"ownerEmail\": \"user@orkes.io\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"variables\": {},\n  \"inputTemplate\": {}\n}"
  },
  {
    "path": "client-spring/build.gradle",
    "content": "\ndependencies {\n\n    implementation project(':conductor-common')\n    api project(':conductor-client')\n    api project(':conductor-java-sdk')\n\n    implementation \"com.netflix.eureka:eureka-client:${revEurekaClient}\"\n    implementation 'org.springframework.boot:spring-boot-starter'\n}\n"
  },
  {
    "path": "client-spring/src/main/java/com/netflix/conductor/client/spring/ClientProperties.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.spring;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@ConfigurationProperties(\"conductor.client\")\npublic class ClientProperties {\n\n    private String rootUri;\n\n    private String workerNamePrefix = \"workflow-worker-%d\";\n\n    private int threadCount = 1;\n\n    private Duration sleepWhenRetryDuration = Duration.ofMillis(500);\n\n    private int updateRetryCount = 3;\n\n    private Map<String, String> taskToDomain = new HashMap<>();\n\n    private Map<String, Integer> taskThreadCount = new HashMap<>();\n\n    private int shutdownGracePeriodSeconds = 10;\n\n    public String getRootUri() {\n        return rootUri;\n    }\n\n    public void setRootUri(String rootUri) {\n        this.rootUri = rootUri;\n    }\n\n    public String getWorkerNamePrefix() {\n        return workerNamePrefix;\n    }\n\n    public void setWorkerNamePrefix(String workerNamePrefix) {\n        this.workerNamePrefix = workerNamePrefix;\n    }\n\n    public int getThreadCount() {\n        return threadCount;\n    }\n\n    public void setThreadCount(int threadCount) {\n        this.threadCount = threadCount;\n    }\n\n    public Duration getSleepWhenRetryDuration() {\n        return sleepWhenRetryDuration;\n    }\n\n    public void setSleepWhenRetryDuration(Duration sleepWhenRetryDuration) {\n        this.sleepWhenRetryDuration = sleepWhenRetryDuration;\n    }\n\n    public int getUpdateRetryCount() {\n        return updateRetryCount;\n    }\n\n    public void setUpdateRetryCount(int updateRetryCount) {\n        this.updateRetryCount = updateRetryCount;\n    }\n\n    public Map<String, String> getTaskToDomain() {\n        return taskToDomain;\n    }\n\n    public void setTaskToDomain(Map<String, String> taskToDomain) {\n        this.taskToDomain = taskToDomain;\n    }\n\n    public int getShutdownGracePeriodSeconds() {\n        return shutdownGracePeriodSeconds;\n    }\n\n    public void setShutdownGracePeriodSeconds(int shutdownGracePeriodSeconds) {\n        this.shutdownGracePeriodSeconds = shutdownGracePeriodSeconds;\n    }\n\n    public Map<String, Integer> getTaskThreadCount() {\n        return taskThreadCount;\n    }\n\n    public void setTaskThreadCount(Map<String, Integer> taskThreadCount) {\n        this.taskThreadCount = taskThreadCount;\n    }\n}\n"
  },
  {
    "path": "client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorClientAutoConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.spring;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.client.automator.TaskRunnerConfigurer;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.sdk.workflow.executor.task.AnnotatedWorkerExecutor;\nimport com.netflix.discovery.EurekaClient;\n\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(ClientProperties.class)\npublic class ConductorClientAutoConfiguration {\n\n    @Autowired(required = false)\n    private EurekaClient eurekaClient;\n\n    @Autowired(required = false)\n    private List<Worker> workers = new ArrayList<>();\n\n    @ConditionalOnMissingBean\n    @Bean\n    public TaskClient taskClient(ClientProperties clientProperties) {\n        TaskClient taskClient = new TaskClient();\n        taskClient.setRootURI(clientProperties.getRootUri());\n        return taskClient;\n    }\n\n    @ConditionalOnMissingBean\n    @Bean\n    public AnnotatedWorkerExecutor annotatedWorkerExecutor(TaskClient taskClient) {\n        return new AnnotatedWorkerExecutor(taskClient);\n    }\n\n    @ConditionalOnMissingBean\n    @Bean(initMethod = \"init\", destroyMethod = \"shutdown\")\n    public TaskRunnerConfigurer taskRunnerConfigurer(\n            TaskClient taskClient, ClientProperties clientProperties) {\n        return new TaskRunnerConfigurer.Builder(taskClient, workers)\n                .withTaskThreadCount(clientProperties.getTaskThreadCount())\n                .withThreadCount(clientProperties.getThreadCount())\n                .withSleepWhenRetry((int) clientProperties.getSleepWhenRetryDuration().toMillis())\n                .withUpdateRetryCount(clientProperties.getUpdateRetryCount())\n                .withTaskToDomain(clientProperties.getTaskToDomain())\n                .withShutdownGracePeriodSeconds(clientProperties.getShutdownGracePeriodSeconds())\n                .withEurekaClient(eurekaClient)\n                .build();\n    }\n}\n"
  },
  {
    "path": "client-spring/src/main/java/com/netflix/conductor/client/spring/ConductorWorkerAutoConfiguration.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.spring;\n\nimport java.util.Map;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationListener;\nimport org.springframework.context.event.ContextRefreshedEvent;\nimport org.springframework.core.env.Environment;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.sdk.workflow.executor.task.AnnotatedWorkerExecutor;\nimport com.netflix.conductor.sdk.workflow.executor.task.WorkerConfiguration;\n\n@Component\npublic class ConductorWorkerAutoConfiguration\n        implements ApplicationListener<ContextRefreshedEvent> {\n\n    @Autowired private TaskClient taskClient;\n\n    @Override\n    public void onApplicationEvent(ContextRefreshedEvent refreshedEvent) {\n        ApplicationContext applicationContext = refreshedEvent.getApplicationContext();\n        Environment environment = applicationContext.getEnvironment();\n        WorkerConfiguration configuration = new SpringWorkerConfiguration(environment);\n        AnnotatedWorkerExecutor annotatedWorkerExecutor =\n                new AnnotatedWorkerExecutor(taskClient, configuration);\n\n        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Component.class);\n        beans.values()\n                .forEach(\n                        bean -> {\n                            annotatedWorkerExecutor.addBean(bean);\n                        });\n        annotatedWorkerExecutor.startPolling();\n    }\n}\n"
  },
  {
    "path": "client-spring/src/main/java/com/netflix/conductor/client/spring/SpringWorkerConfiguration.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.spring;\n\nimport org.springframework.core.env.Environment;\n\nimport com.netflix.conductor.sdk.workflow.executor.task.WorkerConfiguration;\n\npublic class SpringWorkerConfiguration extends WorkerConfiguration {\n\n    private final Environment environment;\n\n    public SpringWorkerConfiguration(Environment environment) {\n        this.environment = environment;\n    }\n\n    @Override\n    public int getPollingInterval(String taskName) {\n        String key = \"conductor.worker.\" + taskName + \".pollingInterval\";\n        return environment.getProperty(key, Integer.class, 0);\n    }\n\n    @Override\n    public int getThreadCount(String taskName) {\n        String key = \"conductor.worker.\" + taskName + \".threadCount\";\n        return environment.getProperty(key, Integer.class, 0);\n    }\n\n    @Override\n    public String getDomain(String taskName) {\n        String key = \"conductor.worker.\" + taskName + \".domain\";\n        return environment.getProperty(key, String.class, null);\n    }\n}\n"
  },
  {
    "path": "client-spring/src/main/resources/META-INF/spring.factories",
    "content": "org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\n  com.netflix.conductor.client.spring.ConductorClientAutoConfiguration\n"
  },
  {
    "path": "client-spring/src/test/java/com/netflix/conductor/client/spring/ExampleClient.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.spring;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\n\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\n\n@SpringBootApplication\npublic class ExampleClient {\n\n    public static void main(String[] args) {\n\n        SpringApplication.run(ExampleClient.class, args);\n    }\n\n    @Bean\n    public Worker worker() {\n        return new Worker() {\n            @Override\n            public String getTaskDefName() {\n                return \"taskDef\";\n            }\n\n            @Override\n            public TaskResult execute(Task task) {\n                return new TaskResult(task);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "client-spring/src/test/java/com/netflix/conductor/client/spring/Workers.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.spring;\n\nimport java.util.Date;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.sdk.workflow.executor.task.TaskContext;\nimport com.netflix.conductor.sdk.workflow.task.InputParam;\nimport com.netflix.conductor.sdk.workflow.task.WorkerTask;\n\n@Component\npublic class Workers {\n\n    @WorkerTask(value = \"hello\", threadCount = 3)\n    public String helloWorld(@InputParam(\"name\") String name) {\n        TaskContext context = TaskContext.get();\n        System.out.println(new Date() + \":: Poll count: \" + context.getPollCount());\n        if (context.getPollCount() < 5) {\n            context.addLog(\"Not ready yet, poll count is only \" + context.getPollCount());\n            context.setCallbackAfter(1);\n        }\n\n        return \"Hello, \" + name;\n    }\n\n    @WorkerTask(value = \"hello_again\", pollingInterval = 333)\n    public String helloAgain(@InputParam(\"name\") String name) {\n        TaskContext context = TaskContext.get();\n        System.out.println(new Date() + \":: Poll count: \" + context.getPollCount());\n        if (context.getPollCount() < 5) {\n            context.addLog(\"Not ready yet, poll count is only \" + context.getPollCount());\n            context.setCallbackAfter(1);\n        }\n\n        return \"Hello (again), \" + name;\n    }\n}\n"
  },
  {
    "path": "client-spring/src/test/resources/application.properties",
    "content": "conductor.client.rootUri=http://localhost:8080/api/\nconductor.worker.hello.threadCount=100\nconductor.worker.hello_again.domain=test"
  },
  {
    "path": "common/build.gradle",
    "content": "configurations {\n    annotationsProcessorCodegen\n}\n\ndependencies {\n    implementation project(':conductor-annotations')\n    annotationsProcessorCodegen project(':conductor-annotations-processor')\n\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n    compileOnly 'org.springframework.boot:spring-boot-starter-validation'\n\n    compileOnly \"org.springdoc:springdoc-openapi-ui:${revOpenapi}\"\n\n    implementation \"org.apache.commons:commons-lang3\"\n\n    implementation \"org.apache.bval:bval-jsr:${revBval}\"\n\n    implementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n\n    implementation \"com.fasterxml.jackson.core:jackson-databind:${revFasterXml}\"\n    implementation \"com.fasterxml.jackson.core:jackson-core:${revFasterXml}\"\n    // https://github.com/FasterXML/jackson-modules-base/tree/master/afterburner\n    implementation \"com.fasterxml.jackson.module:jackson-module-afterburner:${revFasterXml}\"\n\n    testImplementation 'org.springframework.boot:spring-boot-starter-validation'\n}\n\n/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\ntask protogen(dependsOn: jar, type: JavaExec) {\n    classpath configurations.annotationsProcessorCodegen\n    mainClass = \"com.netflix.conductor.annotationsprocessor.protogen.ProtoGenTask\"\n    args(\n            \"conductor.proto\",\n            \"com.netflix.conductor.proto\",\n            \"github.com/netflix/conductor/client/gogrpc/conductor/model\",\n            \"${rootDir}/grpc/src/main/proto\",\n            \"${rootDir}/grpc/src/main/java/com/netflix/conductor/grpc\",\n            \"com.netflix.conductor.grpc\",\n            jar.archivePath,\n            \"com.netflix.conductor.common\",\n    )\n}\n\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/config/ObjectMapperBuilderConfiguration.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.config;\n\nimport org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES;\nimport static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES;\nimport static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;\n\n@Configuration\npublic class ObjectMapperBuilderConfiguration {\n\n    /** Disable features like {@link ObjectMapperProvider#getObjectMapper()}. */\n    @Bean\n    public Jackson2ObjectMapperBuilderCustomizer conductorJackson2ObjectMapperBuilderCustomizer() {\n        return builder ->\n                builder.featuresToDisable(\n                        FAIL_ON_UNKNOWN_PROPERTIES,\n                        FAIL_ON_IGNORED_PROPERTIES,\n                        FAIL_ON_NULL_FOR_PRIMITIVES);\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/config/ObjectMapperConfiguration.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.config;\n\nimport javax.annotation.PostConstruct;\n\nimport org.springframework.context.annotation.Configuration;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.module.afterburner.AfterburnerModule;\n\n@Configuration\npublic class ObjectMapperConfiguration {\n\n    private final ObjectMapper objectMapper;\n\n    public ObjectMapperConfiguration(ObjectMapper objectMapper) {\n        this.objectMapper = objectMapper;\n    }\n\n    /** Set default property inclusion like {@link ObjectMapperProvider#getObjectMapper()}. */\n    @PostConstruct\n    public void customizeDefaultObjectMapper() {\n        objectMapper.setDefaultPropertyInclusion(\n                JsonInclude.Value.construct(\n                        JsonInclude.Include.NON_NULL, JsonInclude.Include.ALWAYS));\n        objectMapper.registerModule(new AfterburnerModule());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/config/ObjectMapperProvider.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.config;\n\nimport com.netflix.conductor.common.jackson.JsonProtoModule;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.module.afterburner.AfterburnerModule;\n\n/**\n * A Factory class for creating a customized {@link ObjectMapper}. This is only used by the\n * conductor-client module and tests that rely on {@link ObjectMapper}. See\n * TestObjectMapperConfiguration.\n */\npublic class ObjectMapperProvider {\n\n    /**\n     * The customizations in this method are configured using {@link\n     * org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration}\n     *\n     * <p>Customizations are spread across, 1. {@link ObjectMapperBuilderConfiguration} 2. {@link\n     * ObjectMapperConfiguration} 3. {@link JsonProtoModule}\n     *\n     * <p>IMPORTANT: Changes in this method need to be also performed in the default {@link\n     * ObjectMapper} that Spring Boot creates.\n     *\n     * @see org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration\n     */\n    public ObjectMapper getObjectMapper() {\n        final ObjectMapper objectMapper = new ObjectMapper();\n        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\n        objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);\n        objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);\n        objectMapper.setDefaultPropertyInclusion(\n                JsonInclude.Value.construct(\n                        JsonInclude.Include.NON_NULL, JsonInclude.Include.ALWAYS));\n        objectMapper.registerModule(new JsonProtoModule());\n        objectMapper.registerModule(new AfterburnerModule());\n        return objectMapper;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/constraints/NoSemiColonConstraint.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.constraints;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport javax.validation.Constraint;\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport javax.validation.Payload;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.ElementType.PARAMETER;\n\n/** This constraint checks semi-colon is not allowed in a given string. */\n@Documented\n@Constraint(validatedBy = NoSemiColonConstraint.NoSemiColonValidator.class)\n@Target({FIELD, PARAMETER})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface NoSemiColonConstraint {\n\n    String message() default \"String: cannot contain the following set of characters: ':'\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n    class NoSemiColonValidator implements ConstraintValidator<NoSemiColonConstraint, String> {\n\n        @Override\n        public void initialize(NoSemiColonConstraint constraintAnnotation) {}\n\n        @Override\n        public boolean isValid(String value, ConstraintValidatorContext context) {\n            boolean valid = true;\n\n            if (!StringUtils.isEmpty(value) && value.contains(\":\")) {\n                valid = false;\n            }\n\n            return valid;\n        }\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/constraints/OwnerEmailMandatoryConstraint.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.constraints;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport javax.validation.Constraint;\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport javax.validation.Payload;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.ElementType.TYPE;\n\n/**\n * This constraint class validates that owner email is non-empty, but only if configuration says\n * owner email is mandatory.\n */\n@Documented\n@Constraint(validatedBy = OwnerEmailMandatoryConstraint.WorkflowTaskValidValidator.class)\n@Target({TYPE, FIELD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface OwnerEmailMandatoryConstraint {\n\n    String message() default \"ownerEmail cannot be empty\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n    class WorkflowTaskValidValidator\n            implements ConstraintValidator<OwnerEmailMandatoryConstraint, String> {\n\n        @Override\n        public void initialize(OwnerEmailMandatoryConstraint constraintAnnotation) {}\n\n        @Override\n        public boolean isValid(String ownerEmail, ConstraintValidatorContext context) {\n            return !ownerEmailMandatory || !StringUtils.isEmpty(ownerEmail);\n        }\n\n        private static boolean ownerEmailMandatory = true;\n\n        public static void setOwnerEmailMandatory(boolean ownerEmailMandatory) {\n            WorkflowTaskValidValidator.ownerEmailMandatory = ownerEmailMandatory;\n        }\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/constraints/TaskReferenceNameUniqueConstraint.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.constraints;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport javax.validation.Constraint;\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport javax.validation.Payload;\n\nimport org.apache.commons.lang3.mutable.MutableBoolean;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.utils.ConstraintParamUtil;\n\nimport static java.lang.annotation.ElementType.TYPE;\n\n/**\n * This constraint class validates following things.\n *\n * <ul>\n *   <li>1. WorkflowDef is valid or not\n *   <li>2. Make sure taskReferenceName used across different tasks are unique\n *   <li>3. Verify inputParameters points to correct tasks or not\n * </ul>\n */\n@Documented\n@Constraint(validatedBy = TaskReferenceNameUniqueConstraint.TaskReferenceNameUniqueValidator.class)\n@Target({TYPE})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface TaskReferenceNameUniqueConstraint {\n\n    String message() default \"\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n    class TaskReferenceNameUniqueValidator\n            implements ConstraintValidator<TaskReferenceNameUniqueConstraint, WorkflowDef> {\n\n        @Override\n        public void initialize(TaskReferenceNameUniqueConstraint constraintAnnotation) {}\n\n        @Override\n        public boolean isValid(WorkflowDef workflowDef, ConstraintValidatorContext context) {\n            context.disableDefaultConstraintViolation();\n\n            boolean valid = true;\n\n            // check if taskReferenceNames are unique across tasks or not\n            HashMap<String, Integer> taskReferenceMap = new HashMap<>();\n            for (WorkflowTask workflowTask : workflowDef.collectTasks()) {\n                if (taskReferenceMap.containsKey(workflowTask.getTaskReferenceName())) {\n                    String message =\n                            String.format(\n                                    \"taskReferenceName: %s should be unique across tasks for a given workflowDefinition: %s\",\n                                    workflowTask.getTaskReferenceName(), workflowDef.getName());\n                    context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                    valid = false;\n                } else {\n                    taskReferenceMap.put(workflowTask.getTaskReferenceName(), 1);\n                }\n            }\n            // check inputParameters points to valid taskDef\n            return valid & verifyTaskInputParameters(context, workflowDef);\n        }\n\n        private boolean verifyTaskInputParameters(\n                ConstraintValidatorContext context, WorkflowDef workflow) {\n            MutableBoolean valid = new MutableBoolean();\n            valid.setValue(true);\n\n            if (workflow.getTasks() == null) {\n                return valid.getValue();\n            }\n\n            workflow.getTasks().stream()\n                    .filter(workflowTask -> workflowTask.getInputParameters() != null)\n                    .forEach(\n                            workflowTask -> {\n                                List<String> errors =\n                                        ConstraintParamUtil.validateInputParam(\n                                                workflowTask.getInputParameters(),\n                                                workflowTask.getName(),\n                                                workflow);\n                                errors.forEach(\n                                        message ->\n                                                context.buildConstraintViolationWithTemplate(\n                                                                message)\n                                                        .addConstraintViolation());\n                                if (errors.size() > 0) {\n                                    valid.setValue(false);\n                                }\n                            });\n\n            return valid.getValue();\n        }\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/constraints/TaskTimeoutConstraint.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.constraints;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport javax.validation.Constraint;\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport javax.validation.Payload;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\n\nimport static java.lang.annotation.ElementType.TYPE;\n\n/**\n * This constraint checks for a given task responseTimeoutSeconds should be less than\n * timeoutSeconds.\n */\n@Documented\n@Constraint(validatedBy = TaskTimeoutConstraint.TaskTimeoutValidator.class)\n@Target({TYPE})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface TaskTimeoutConstraint {\n\n    String message() default \"\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n    class TaskTimeoutValidator implements ConstraintValidator<TaskTimeoutConstraint, TaskDef> {\n\n        @Override\n        public void initialize(TaskTimeoutConstraint constraintAnnotation) {}\n\n        @Override\n        public boolean isValid(TaskDef taskDef, ConstraintValidatorContext context) {\n            context.disableDefaultConstraintViolation();\n\n            boolean valid = true;\n\n            if (taskDef.getTimeoutSeconds() > 0) {\n                if (taskDef.getResponseTimeoutSeconds() > taskDef.getTimeoutSeconds()) {\n                    valid = false;\n                    String message =\n                            String.format(\n                                    \"TaskDef: %s responseTimeoutSeconds: %d must be less than timeoutSeconds: %d\",\n                                    taskDef.getName(),\n                                    taskDef.getResponseTimeoutSeconds(),\n                                    taskDef.getTimeoutSeconds());\n                    context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                }\n            }\n\n            return valid;\n        }\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/jackson/JsonProtoModule.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.jackson;\n\nimport java.io.IOException;\n\nimport org.springframework.stereotype.Component;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.module.SimpleModule;\nimport com.google.protobuf.Any;\nimport com.google.protobuf.ByteString;\nimport com.google.protobuf.Message;\n\n/**\n * JsonProtoModule can be registered into an {@link ObjectMapper} to enable the serialization and\n * deserialization of ProtoBuf objects from/to JSON.\n *\n * <p>Right now this module only provides (de)serialization for the {@link Any} ProtoBuf type, as\n * this is the only ProtoBuf object which we're currently exposing through the REST API.\n *\n * <p>Annotated as {@link Component} so Spring can register it with {@link ObjectMapper}\n *\n * @see AnySerializer\n * @see AnyDeserializer\n * @see org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration\n */\n@Component(JsonProtoModule.NAME)\npublic class JsonProtoModule extends SimpleModule {\n\n    public static final String NAME = \"ConductorJsonProtoModule\";\n\n    private static final String JSON_TYPE = \"@type\";\n    private static final String JSON_VALUE = \"@value\";\n\n    /**\n     * AnySerializer converts a ProtoBuf {@link Any} object into its JSON representation.\n     *\n     * <p>This is <b>not</b> a canonical ProtoBuf JSON representation. Let us explain what we're\n     * trying to accomplish here:\n     *\n     * <p>The {@link Any} ProtoBuf message is a type in the PB standard library that can store any\n     * other arbitrary ProtoBuf message in a type-safe way, even when the server has no knowledge of\n     * the schema of the stored message.\n     *\n     * <p>It accomplishes this by storing a tuple of information: an URL-like type declaration for\n     * the stored message, and the serialized binary encoding of the stored message itself. Language\n     * specific implementations of ProtoBuf provide helper methods to encode and decode arbitrary\n     * messages into an {@link Any} object ({@link Any#pack(Message)} in Java).\n     *\n     * <p>We want to expose these {@link Any} objects in the REST API because they've been\n     * introduced as part of the new GRPC interface to Conductor, but unfortunately we cannot encode\n     * them using their canonical ProtoBuf JSON encoding. According to the docs:\n     *\n     * <p>The JSON representation of an `Any` value uses the regular representation of the\n     * deserialized, embedded message, with an additional field `@type` which contains the type URL.\n     * Example:\n     *\n     * <p>package google.profile; message Person { string first_name = 1; string last_name = 2; } {\n     * \"@type\": \"type.googleapis.com/google.profile.Person\", \"firstName\": <string>, \"lastName\":\n     * <string> }\n     *\n     * <p>In order to accomplish this representation, the PB-JSON encoder needs to have knowledge of\n     * all the ProtoBuf messages that could be serialized inside the {@link Any} message. This is\n     * not possible to accomplish inside the Conductor server, which is simply passing through\n     * arbitrary payloads from/to clients.\n     *\n     * <p>Consequently, to actually expose the Message through the REST API, we must create a custom\n     * encoding that contains the raw data of the serialized message, as we are not able to\n     * deserialize it on the server. We simply return a dictionary with '@type' and '@value' keys,\n     * where '@type' is identical to the canonical representation, but '@value' contains a base64\n     * encoded string with the binary data of the serialized message.\n     *\n     * <p>Since all the provided Conductor clients are required to know this encoding, it's always\n     * possible to re-build the original {@link Any} message regardless of the client's language.\n     *\n     * <p>{@see AnyDeserializer}\n     */\n    @SuppressWarnings(\"InnerClassMayBeStatic\")\n    protected class AnySerializer extends JsonSerializer<Any> {\n\n        @Override\n        public void serialize(Any value, JsonGenerator jgen, SerializerProvider provider)\n                throws IOException {\n            jgen.writeStartObject();\n            jgen.writeStringField(JSON_TYPE, value.getTypeUrl());\n            jgen.writeBinaryField(JSON_VALUE, value.getValue().toByteArray());\n            jgen.writeEndObject();\n        }\n    }\n\n    /**\n     * AnyDeserializer converts the custom JSON representation of an {@link Any} value into its\n     * original form.\n     *\n     * <p>{@see AnySerializer} for details on this representation.\n     */\n    @SuppressWarnings(\"InnerClassMayBeStatic\")\n    protected class AnyDeserializer extends JsonDeserializer<Any> {\n\n        @Override\n        public Any deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {\n            JsonNode root = p.getCodec().readTree(p);\n            JsonNode type = root.get(JSON_TYPE);\n            JsonNode value = root.get(JSON_VALUE);\n\n            if (type == null || !type.isTextual()) {\n                ctxt.reportMappingException(\n                        \"invalid '@type' field when deserializing ProtoBuf Any object\");\n            }\n\n            if (value == null || !value.isTextual()) {\n                ctxt.reportMappingException(\n                        \"invalid '@value' field when deserializing ProtoBuf Any object\");\n            }\n\n            return Any.newBuilder()\n                    .setTypeUrl(type.textValue())\n                    .setValue(ByteString.copyFrom(value.binaryValue()))\n                    .build();\n        }\n    }\n\n    public JsonProtoModule() {\n        super(NAME);\n        addSerializer(Any.class, new AnySerializer());\n        addDeserializer(Any.class, new AnyDeserializer());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/Auditable.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata;\n\npublic abstract class Auditable {\n\n    private String ownerApp;\n\n    private Long createTime;\n\n    private Long updateTime;\n\n    private String createdBy;\n\n    private String updatedBy;\n\n    /**\n     * @return the ownerApp\n     */\n    public String getOwnerApp() {\n        return ownerApp;\n    }\n\n    /**\n     * @param ownerApp the ownerApp to set\n     */\n    public void setOwnerApp(String ownerApp) {\n        this.ownerApp = ownerApp;\n    }\n\n    /**\n     * @return the createTime\n     */\n    public Long getCreateTime() {\n        return createTime;\n    }\n\n    /**\n     * @param createTime the createTime to set\n     */\n    public void setCreateTime(Long createTime) {\n        this.createTime = createTime;\n    }\n\n    /**\n     * @return the updateTime\n     */\n    public Long getUpdateTime() {\n        return updateTime;\n    }\n\n    /**\n     * @param updateTime the updateTime to set\n     */\n    public void setUpdateTime(Long updateTime) {\n        this.updateTime = updateTime;\n    }\n\n    /**\n     * @return the createdBy\n     */\n    public String getCreatedBy() {\n        return createdBy;\n    }\n\n    /**\n     * @param createdBy the createdBy to set\n     */\n    public void setCreatedBy(String createdBy) {\n        this.createdBy = createdBy;\n    }\n\n    /**\n     * @return the updatedBy\n     */\n    public String getUpdatedBy() {\n        return updatedBy;\n    }\n\n    /**\n     * @param updatedBy the updatedBy to set\n     */\n    public void setUpdatedBy(String updatedBy) {\n        this.updatedBy = updatedBy;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/BaseDef.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata;\n\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.Map;\n\nimport com.netflix.conductor.common.metadata.acl.Permission;\n\n/**\n * A base class for {@link com.netflix.conductor.common.metadata.workflow.WorkflowDef} and {@link\n * com.netflix.conductor.common.metadata.tasks.TaskDef}.\n */\npublic abstract class BaseDef extends Auditable {\n\n    private final Map<Permission, String> accessPolicy = new EnumMap<>(Permission.class);\n\n    public void addPermission(Permission permission, String allowedAuthority) {\n        this.accessPolicy.put(permission, allowedAuthority);\n    }\n\n    public void addPermissionIfAbsent(Permission permission, String allowedAuthority) {\n        this.accessPolicy.putIfAbsent(permission, allowedAuthority);\n    }\n\n    public void removePermission(Permission permission) {\n        this.accessPolicy.remove(permission);\n    }\n\n    public String getAllowedAuthority(Permission permission) {\n        return this.accessPolicy.get(permission);\n    }\n\n    public void clearAccessPolicy() {\n        this.accessPolicy.clear();\n    }\n\n    public Map<Permission, String> getAccessPolicy() {\n        return Collections.unmodifiableMap(this.accessPolicy);\n    }\n\n    public void setAccessPolicy(Map<Permission, String> accessPolicy) {\n        this.accessPolicy.putAll(accessPolicy);\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/acl/Permission.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.acl;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\n\n@ProtoEnum\npublic enum Permission {\n    OWNER,\n    OPERATOR\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/events/EventExecution.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.events;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action;\n\n@ProtoMessage\npublic class EventExecution {\n\n    @ProtoEnum\n    public enum Status {\n        IN_PROGRESS,\n        COMPLETED,\n        FAILED,\n        SKIPPED\n    }\n\n    @ProtoField(id = 1)\n    private String id;\n\n    @ProtoField(id = 2)\n    private String messageId;\n\n    @ProtoField(id = 3)\n    private String name;\n\n    @ProtoField(id = 4)\n    private String event;\n\n    @ProtoField(id = 5)\n    private long created;\n\n    @ProtoField(id = 6)\n    private Status status;\n\n    @ProtoField(id = 7)\n    private Action.Type action;\n\n    @ProtoField(id = 8)\n    private Map<String, Object> output = new HashMap<>();\n\n    public EventExecution() {}\n\n    public EventExecution(String id, String messageId) {\n        this.id = id;\n        this.messageId = messageId;\n    }\n\n    /**\n     * @return the id\n     */\n    public String getId() {\n        return id;\n    }\n\n    /**\n     * @param id the id to set\n     */\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    /**\n     * @return the messageId\n     */\n    public String getMessageId() {\n        return messageId;\n    }\n\n    /**\n     * @param messageId the messageId to set\n     */\n    public void setMessageId(String messageId) {\n        this.messageId = messageId;\n    }\n\n    /**\n     * @return the name\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @param name the name to set\n     */\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * @return the event\n     */\n    public String getEvent() {\n        return event;\n    }\n\n    /**\n     * @param event the event to set\n     */\n    public void setEvent(String event) {\n        this.event = event;\n    }\n\n    /**\n     * @return the created\n     */\n    public long getCreated() {\n        return created;\n    }\n\n    /**\n     * @param created the created to set\n     */\n    public void setCreated(long created) {\n        this.created = created;\n    }\n\n    /**\n     * @return the status\n     */\n    public Status getStatus() {\n        return status;\n    }\n\n    /**\n     * @param status the status to set\n     */\n    public void setStatus(Status status) {\n        this.status = status;\n    }\n\n    /**\n     * @return the action\n     */\n    public Action.Type getAction() {\n        return action;\n    }\n\n    /**\n     * @param action the action to set\n     */\n    public void setAction(Action.Type action) {\n        this.action = action;\n    }\n\n    /**\n     * @return the output\n     */\n    public Map<String, Object> getOutput() {\n        return output;\n    }\n\n    /**\n     * @param output the output to set\n     */\n    public void setOutput(Map<String, Object> output) {\n        this.output = output;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        EventExecution execution = (EventExecution) o;\n        return created == execution.created\n                && Objects.equals(id, execution.id)\n                && Objects.equals(messageId, execution.messageId)\n                && Objects.equals(name, execution.name)\n                && Objects.equals(event, execution.event)\n                && status == execution.status\n                && action == execution.action\n                && Objects.equals(output, execution.output);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, messageId, name, event, created, status, action, output);\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/events/EventHandler.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.events;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\nimport com.google.protobuf.Any;\nimport io.swagger.v3.oas.annotations.Hidden;\n\n/** Defines an event handler */\n@ProtoMessage\npublic class EventHandler {\n\n    @ProtoField(id = 1)\n    @NotEmpty(message = \"Missing event handler name\")\n    private String name;\n\n    @ProtoField(id = 2)\n    @NotEmpty(message = \"Missing event location\")\n    private String event;\n\n    @ProtoField(id = 3)\n    private String condition;\n\n    @ProtoField(id = 4)\n    @NotNull\n    @NotEmpty(message = \"No actions specified. Please specify at-least one action\")\n    private List<@Valid Action> actions = new LinkedList<>();\n\n    @ProtoField(id = 5)\n    private boolean active;\n\n    @ProtoField(id = 6)\n    private String evaluatorType;\n\n    public EventHandler() {}\n\n    /**\n     * @return the name MUST be unique within a conductor instance\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @param name the name to set\n     */\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * @return the event\n     */\n    public String getEvent() {\n        return event;\n    }\n\n    /**\n     * @param event the event to set\n     */\n    public void setEvent(String event) {\n        this.event = event;\n    }\n\n    /**\n     * @return the condition\n     */\n    public String getCondition() {\n        return condition;\n    }\n\n    /**\n     * @param condition the condition to set\n     */\n    public void setCondition(String condition) {\n        this.condition = condition;\n    }\n\n    /**\n     * @return the actions\n     */\n    public List<Action> getActions() {\n        return actions;\n    }\n\n    /**\n     * @param actions the actions to set\n     */\n    public void setActions(List<Action> actions) {\n        this.actions = actions;\n    }\n\n    /**\n     * @return the active\n     */\n    public boolean isActive() {\n        return active;\n    }\n\n    /**\n     * @param active if set to false, the event handler is deactivated\n     */\n    public void setActive(boolean active) {\n        this.active = active;\n    }\n\n    /**\n     * @return the evaluator type\n     */\n    public String getEvaluatorType() {\n        return evaluatorType;\n    }\n\n    /**\n     * @param evaluatorType the evaluatorType to set\n     */\n    public void setEvaluatorType(String evaluatorType) {\n        this.evaluatorType = evaluatorType;\n    }\n\n    @ProtoMessage\n    public static class Action {\n\n        @ProtoEnum\n        public enum Type {\n            start_workflow,\n            complete_task,\n            fail_task\n        }\n\n        @ProtoField(id = 1)\n        private Type action;\n\n        @ProtoField(id = 2)\n        private StartWorkflow start_workflow;\n\n        @ProtoField(id = 3)\n        private TaskDetails complete_task;\n\n        @ProtoField(id = 4)\n        private TaskDetails fail_task;\n\n        @ProtoField(id = 5)\n        private boolean expandInlineJSON;\n\n        /**\n         * @return the action\n         */\n        public Type getAction() {\n            return action;\n        }\n\n        /**\n         * @param action the action to set\n         */\n        public void setAction(Type action) {\n            this.action = action;\n        }\n\n        /**\n         * @return the start_workflow\n         */\n        public StartWorkflow getStart_workflow() {\n            return start_workflow;\n        }\n\n        /**\n         * @param start_workflow the start_workflow to set\n         */\n        public void setStart_workflow(StartWorkflow start_workflow) {\n            this.start_workflow = start_workflow;\n        }\n\n        /**\n         * @return the complete_task\n         */\n        public TaskDetails getComplete_task() {\n            return complete_task;\n        }\n\n        /**\n         * @param complete_task the complete_task to set\n         */\n        public void setComplete_task(TaskDetails complete_task) {\n            this.complete_task = complete_task;\n        }\n\n        /**\n         * @return the fail_task\n         */\n        public TaskDetails getFail_task() {\n            return fail_task;\n        }\n\n        /**\n         * @param fail_task the fail_task to set\n         */\n        public void setFail_task(TaskDetails fail_task) {\n            this.fail_task = fail_task;\n        }\n\n        /**\n         * @param expandInlineJSON when set to true, the in-lined JSON strings are expanded to a\n         *     full json document\n         */\n        public void setExpandInlineJSON(boolean expandInlineJSON) {\n            this.expandInlineJSON = expandInlineJSON;\n        }\n\n        /**\n         * @return true if the json strings within the payload should be expanded.\n         */\n        public boolean isExpandInlineJSON() {\n            return expandInlineJSON;\n        }\n    }\n\n    @ProtoMessage\n    public static class TaskDetails {\n\n        @ProtoField(id = 1)\n        private String workflowId;\n\n        @ProtoField(id = 2)\n        private String taskRefName;\n\n        @ProtoField(id = 3)\n        private Map<String, Object> output = new HashMap<>();\n\n        @ProtoField(id = 4)\n        @Hidden\n        private Any outputMessage;\n\n        @ProtoField(id = 5)\n        private String taskId;\n\n        /**\n         * @return the workflowId\n         */\n        public String getWorkflowId() {\n            return workflowId;\n        }\n\n        /**\n         * @param workflowId the workflowId to set\n         */\n        public void setWorkflowId(String workflowId) {\n            this.workflowId = workflowId;\n        }\n\n        /**\n         * @return the taskRefName\n         */\n        public String getTaskRefName() {\n            return taskRefName;\n        }\n\n        /**\n         * @param taskRefName the taskRefName to set\n         */\n        public void setTaskRefName(String taskRefName) {\n            this.taskRefName = taskRefName;\n        }\n\n        /**\n         * @return the output\n         */\n        public Map<String, Object> getOutput() {\n            return output;\n        }\n\n        /**\n         * @param output the output to set\n         */\n        public void setOutput(Map<String, Object> output) {\n            this.output = output;\n        }\n\n        public Any getOutputMessage() {\n            return outputMessage;\n        }\n\n        public void setOutputMessage(Any outputMessage) {\n            this.outputMessage = outputMessage;\n        }\n\n        /**\n         * @return the taskId\n         */\n        public String getTaskId() {\n            return taskId;\n        }\n\n        /**\n         * @param taskId the taskId to set\n         */\n        public void setTaskId(String taskId) {\n            this.taskId = taskId;\n        }\n    }\n\n    @ProtoMessage\n    public static class StartWorkflow {\n\n        @ProtoField(id = 1)\n        private String name;\n\n        @ProtoField(id = 2)\n        private Integer version;\n\n        @ProtoField(id = 3)\n        private String correlationId;\n\n        @ProtoField(id = 4)\n        private Map<String, Object> input = new HashMap<>();\n\n        @ProtoField(id = 5)\n        @Hidden\n        private Any inputMessage;\n\n        @ProtoField(id = 6)\n        private Map<String, String> taskToDomain;\n\n        /**\n         * @return the name\n         */\n        public String getName() {\n            return name;\n        }\n\n        /**\n         * @param name the name to set\n         */\n        public void setName(String name) {\n            this.name = name;\n        }\n\n        /**\n         * @return the version\n         */\n        public Integer getVersion() {\n            return version;\n        }\n\n        /**\n         * @param version the version to set\n         */\n        public void setVersion(Integer version) {\n            this.version = version;\n        }\n\n        /**\n         * @return the correlationId\n         */\n        public String getCorrelationId() {\n            return correlationId;\n        }\n\n        /**\n         * @param correlationId the correlationId to set\n         */\n        public void setCorrelationId(String correlationId) {\n            this.correlationId = correlationId;\n        }\n\n        /**\n         * @return the input\n         */\n        public Map<String, Object> getInput() {\n            return input;\n        }\n\n        /**\n         * @param input the input to set\n         */\n        public void setInput(Map<String, Object> input) {\n            this.input = input;\n        }\n\n        public Any getInputMessage() {\n            return inputMessage;\n        }\n\n        public void setInputMessage(Any inputMessage) {\n            this.inputMessage = inputMessage;\n        }\n\n        public Map<String, String> getTaskToDomain() {\n            return taskToDomain;\n        }\n\n        public void setTaskToDomain(Map<String, String> taskToDomain) {\n            this.taskToDomain = taskToDomain;\n        }\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/tasks/PollData.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.tasks;\n\nimport java.util.Objects;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\n@ProtoMessage\npublic class PollData {\n\n    @ProtoField(id = 1)\n    private String queueName;\n\n    @ProtoField(id = 2)\n    private String domain;\n\n    @ProtoField(id = 3)\n    private String workerId;\n\n    @ProtoField(id = 4)\n    private long lastPollTime;\n\n    public PollData() {\n        super();\n    }\n\n    public PollData(String queueName, String domain, String workerId, long lastPollTime) {\n        super();\n        this.queueName = queueName;\n        this.domain = domain;\n        this.workerId = workerId;\n        this.lastPollTime = lastPollTime;\n    }\n\n    public String getQueueName() {\n        return queueName;\n    }\n\n    public void setQueueName(String queueName) {\n        this.queueName = queueName;\n    }\n\n    public String getDomain() {\n        return domain;\n    }\n\n    public void setDomain(String domain) {\n        this.domain = domain;\n    }\n\n    public String getWorkerId() {\n        return workerId;\n    }\n\n    public void setWorkerId(String workerId) {\n        this.workerId = workerId;\n    }\n\n    public long getLastPollTime() {\n        return lastPollTime;\n    }\n\n    public void setLastPollTime(long lastPollTime) {\n        this.lastPollTime = lastPollTime;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        PollData pollData = (PollData) o;\n        return getLastPollTime() == pollData.getLastPollTime()\n                && Objects.equals(getQueueName(), pollData.getQueueName())\n                && Objects.equals(getDomain(), pollData.getDomain())\n                && Objects.equals(getWorkerId(), pollData.getWorkerId());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(getQueueName(), getDomain(), getWorkerId(), getLastPollTime());\n    }\n\n    @Override\n    public String toString() {\n        return \"PollData{\"\n                + \"queueName='\"\n                + queueName\n                + '\\''\n                + \", domain='\"\n                + domain\n                + '\\''\n                + \", workerId='\"\n                + workerId\n                + '\\''\n                + \", lastPollTime=\"\n                + lastPollTime\n                + '}';\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/tasks/Task.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.google.protobuf.Any;\nimport io.swagger.v3.oas.annotations.Hidden;\n\n@ProtoMessage\npublic class Task {\n\n    @ProtoEnum\n    public enum Status {\n        IN_PROGRESS(false, true, true),\n        CANCELED(true, false, false),\n        FAILED(true, false, true),\n        FAILED_WITH_TERMINAL_ERROR(\n                true, false,\n                false), // No retries even if retries are configured, the task and the related\n        // workflow should be terminated\n        COMPLETED(true, true, true),\n        COMPLETED_WITH_ERRORS(true, true, true),\n        SCHEDULED(false, true, true),\n        TIMED_OUT(true, false, true),\n        SKIPPED(true, true, false);\n\n        private final boolean terminal;\n\n        private final boolean successful;\n\n        private final boolean retriable;\n\n        Status(boolean terminal, boolean successful, boolean retriable) {\n            this.terminal = terminal;\n            this.successful = successful;\n            this.retriable = retriable;\n        }\n\n        public boolean isTerminal() {\n            return terminal;\n        }\n\n        public boolean isSuccessful() {\n            return successful;\n        }\n\n        public boolean isRetriable() {\n            return retriable;\n        }\n    }\n\n    @ProtoField(id = 1)\n    private String taskType;\n\n    @ProtoField(id = 2)\n    private Status status;\n\n    @ProtoField(id = 3)\n    private Map<String, Object> inputData = new HashMap<>();\n\n    @ProtoField(id = 4)\n    private String referenceTaskName;\n\n    @ProtoField(id = 5)\n    private int retryCount;\n\n    @ProtoField(id = 6)\n    private int seq;\n\n    @ProtoField(id = 7)\n    private String correlationId;\n\n    @ProtoField(id = 8)\n    private int pollCount;\n\n    @ProtoField(id = 9)\n    private String taskDefName;\n\n    /** Time when the task was scheduled */\n    @ProtoField(id = 10)\n    private long scheduledTime;\n\n    /** Time when the task was first polled */\n    @ProtoField(id = 11)\n    private long startTime;\n\n    /** Time when the task completed executing */\n    @ProtoField(id = 12)\n    private long endTime;\n\n    /** Time when the task was last updated */\n    @ProtoField(id = 13)\n    private long updateTime;\n\n    @ProtoField(id = 14)\n    private int startDelayInSeconds;\n\n    @ProtoField(id = 15)\n    private String retriedTaskId;\n\n    @ProtoField(id = 16)\n    private boolean retried;\n\n    @ProtoField(id = 17)\n    private boolean executed;\n\n    @ProtoField(id = 18)\n    private boolean callbackFromWorker = true;\n\n    @ProtoField(id = 19)\n    private long responseTimeoutSeconds;\n\n    @ProtoField(id = 20)\n    private String workflowInstanceId;\n\n    @ProtoField(id = 21)\n    private String workflowType;\n\n    @ProtoField(id = 22)\n    private String taskId;\n\n    @ProtoField(id = 23)\n    private String reasonForIncompletion;\n\n    @ProtoField(id = 24)\n    private long callbackAfterSeconds;\n\n    @ProtoField(id = 25)\n    private String workerId;\n\n    @ProtoField(id = 26)\n    private Map<String, Object> outputData = new HashMap<>();\n\n    @ProtoField(id = 27)\n    private WorkflowTask workflowTask;\n\n    @ProtoField(id = 28)\n    private String domain;\n\n    @ProtoField(id = 29)\n    @Hidden\n    private Any inputMessage;\n\n    @ProtoField(id = 30)\n    @Hidden\n    private Any outputMessage;\n\n    // id 31 is reserved\n\n    @ProtoField(id = 32)\n    private int rateLimitPerFrequency;\n\n    @ProtoField(id = 33)\n    private int rateLimitFrequencyInSeconds;\n\n    @ProtoField(id = 34)\n    private String externalInputPayloadStoragePath;\n\n    @ProtoField(id = 35)\n    private String externalOutputPayloadStoragePath;\n\n    @ProtoField(id = 36)\n    private int workflowPriority;\n\n    @ProtoField(id = 37)\n    private String executionNameSpace;\n\n    @ProtoField(id = 38)\n    private String isolationGroupId;\n\n    @ProtoField(id = 40)\n    private int iteration;\n\n    @ProtoField(id = 41)\n    private String subWorkflowId;\n\n    /**\n     * Use to note that a sub workflow associated with SUB_WORKFLOW task has an action performed on\n     * it directly.\n     */\n    @ProtoField(id = 42)\n    private boolean subworkflowChanged;\n\n    public Task() {}\n\n    /**\n     * @return Type of the task\n     * @see TaskType\n     */\n    public String getTaskType() {\n        return taskType;\n    }\n\n    public void setTaskType(String taskType) {\n        this.taskType = taskType;\n    }\n\n    /**\n     * @return Status of the task\n     */\n    public Status getStatus() {\n        return status;\n    }\n\n    /**\n     * @param status Status of the task\n     */\n    public void setStatus(Status status) {\n        this.status = status;\n    }\n\n    public Map<String, Object> getInputData() {\n        return inputData;\n    }\n\n    public void setInputData(Map<String, Object> inputData) {\n        if (inputData == null) {\n            inputData = new HashMap<>();\n        }\n        this.inputData = inputData;\n    }\n\n    /**\n     * @return the referenceTaskName\n     */\n    public String getReferenceTaskName() {\n        return referenceTaskName;\n    }\n\n    /**\n     * @param referenceTaskName the referenceTaskName to set\n     */\n    public void setReferenceTaskName(String referenceTaskName) {\n        this.referenceTaskName = referenceTaskName;\n    }\n\n    /**\n     * @return the correlationId\n     */\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    /**\n     * @param correlationId the correlationId to set\n     */\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    /**\n     * @return the retryCount\n     */\n    public int getRetryCount() {\n        return retryCount;\n    }\n\n    /**\n     * @param retryCount the retryCount to set\n     */\n    public void setRetryCount(int retryCount) {\n        this.retryCount = retryCount;\n    }\n\n    /**\n     * @return the scheduledTime\n     */\n    public long getScheduledTime() {\n        return scheduledTime;\n    }\n\n    /**\n     * @param scheduledTime the scheduledTime to set\n     */\n    public void setScheduledTime(long scheduledTime) {\n        this.scheduledTime = scheduledTime;\n    }\n\n    /**\n     * @return the startTime\n     */\n    public long getStartTime() {\n        return startTime;\n    }\n\n    /**\n     * @param startTime the startTime to set\n     */\n    public void setStartTime(long startTime) {\n        this.startTime = startTime;\n    }\n\n    /**\n     * @return the endTime\n     */\n    public long getEndTime() {\n        return endTime;\n    }\n\n    /**\n     * @param endTime the endTime to set\n     */\n    public void setEndTime(long endTime) {\n        this.endTime = endTime;\n    }\n\n    /**\n     * @return the startDelayInSeconds\n     */\n    public int getStartDelayInSeconds() {\n        return startDelayInSeconds;\n    }\n\n    /**\n     * @param startDelayInSeconds the startDelayInSeconds to set\n     */\n    public void setStartDelayInSeconds(int startDelayInSeconds) {\n        this.startDelayInSeconds = startDelayInSeconds;\n    }\n\n    /**\n     * @return the retriedTaskId\n     */\n    public String getRetriedTaskId() {\n        return retriedTaskId;\n    }\n\n    /**\n     * @param retriedTaskId the retriedTaskId to set\n     */\n    public void setRetriedTaskId(String retriedTaskId) {\n        this.retriedTaskId = retriedTaskId;\n    }\n\n    /**\n     * @return the seq\n     */\n    public int getSeq() {\n        return seq;\n    }\n\n    /**\n     * @param seq the seq to set\n     */\n    public void setSeq(int seq) {\n        this.seq = seq;\n    }\n\n    /**\n     * @return the updateTime\n     */\n    public long getUpdateTime() {\n        return updateTime;\n    }\n\n    /**\n     * @param updateTime the updateTime to set\n     */\n    public void setUpdateTime(long updateTime) {\n        this.updateTime = updateTime;\n    }\n\n    /**\n     * @return the queueWaitTime\n     */\n    public long getQueueWaitTime() {\n        if (this.startTime > 0 && this.scheduledTime > 0) {\n            if (this.updateTime > 0 && getCallbackAfterSeconds() > 0) {\n                long waitTime =\n                        System.currentTimeMillis()\n                                - (this.updateTime + (getCallbackAfterSeconds() * 1000));\n                return waitTime > 0 ? waitTime : 0;\n            } else {\n                return this.startTime - this.scheduledTime;\n            }\n        }\n        return 0L;\n    }\n\n    /**\n     * @return True if the task has been retried after failure\n     */\n    public boolean isRetried() {\n        return retried;\n    }\n\n    /**\n     * @param retried the retried to set\n     */\n    public void setRetried(boolean retried) {\n        this.retried = retried;\n    }\n\n    /**\n     * @return True if the task has completed its lifecycle within conductor (from start to\n     *     completion to being updated in the datastore)\n     */\n    public boolean isExecuted() {\n        return executed;\n    }\n\n    /**\n     * @param executed the executed value to set\n     */\n    public void setExecuted(boolean executed) {\n        this.executed = executed;\n    }\n\n    /**\n     * @return No. of times task has been polled\n     */\n    public int getPollCount() {\n        return pollCount;\n    }\n\n    public void setPollCount(int pollCount) {\n        this.pollCount = pollCount;\n    }\n\n    public void incrementPollCount() {\n        ++this.pollCount;\n    }\n\n    public boolean isCallbackFromWorker() {\n        return callbackFromWorker;\n    }\n\n    public void setCallbackFromWorker(boolean callbackFromWorker) {\n        this.callbackFromWorker = callbackFromWorker;\n    }\n\n    /**\n     * @return Name of the task definition\n     */\n    public String getTaskDefName() {\n        if (taskDefName == null || \"\".equals(taskDefName)) {\n            taskDefName = taskType;\n        }\n        return taskDefName;\n    }\n\n    /**\n     * @param taskDefName Name of the task definition\n     */\n    public void setTaskDefName(String taskDefName) {\n        this.taskDefName = taskDefName;\n    }\n\n    /**\n     * @return the timeout for task to send response. After this timeout, the task will be re-queued\n     */\n    public long getResponseTimeoutSeconds() {\n        return responseTimeoutSeconds;\n    }\n\n    /**\n     * @param responseTimeoutSeconds - timeout for task to send response. After this timeout, the\n     *     task will be re-queued\n     */\n    public void setResponseTimeoutSeconds(long responseTimeoutSeconds) {\n        this.responseTimeoutSeconds = responseTimeoutSeconds;\n    }\n\n    /**\n     * @return the workflowInstanceId\n     */\n    public String getWorkflowInstanceId() {\n        return workflowInstanceId;\n    }\n\n    /**\n     * @param workflowInstanceId the workflowInstanceId to set\n     */\n    public void setWorkflowInstanceId(String workflowInstanceId) {\n        this.workflowInstanceId = workflowInstanceId;\n    }\n\n    public String getWorkflowType() {\n        return workflowType;\n    }\n\n    /**\n     * @param workflowType the name of the workflow\n     * @return the task object with the workflow type set\n     */\n    public com.netflix.conductor.common.metadata.tasks.Task setWorkflowType(String workflowType) {\n        this.workflowType = workflowType;\n        return this;\n    }\n\n    /**\n     * @return the taskId\n     */\n    public String getTaskId() {\n        return taskId;\n    }\n\n    /**\n     * @param taskId the taskId to set\n     */\n    public void setTaskId(String taskId) {\n        this.taskId = taskId;\n    }\n\n    /**\n     * @return the reasonForIncompletion\n     */\n    public String getReasonForIncompletion() {\n        return reasonForIncompletion;\n    }\n\n    /**\n     * @param reasonForIncompletion the reasonForIncompletion to set\n     */\n    public void setReasonForIncompletion(String reasonForIncompletion) {\n        this.reasonForIncompletion = StringUtils.substring(reasonForIncompletion, 0, 500);\n    }\n\n    /**\n     * @return the callbackAfterSeconds\n     */\n    public long getCallbackAfterSeconds() {\n        return callbackAfterSeconds;\n    }\n\n    /**\n     * @param callbackAfterSeconds the callbackAfterSeconds to set\n     */\n    public void setCallbackAfterSeconds(long callbackAfterSeconds) {\n        this.callbackAfterSeconds = callbackAfterSeconds;\n    }\n\n    /**\n     * @return the workerId\n     */\n    public String getWorkerId() {\n        return workerId;\n    }\n\n    /**\n     * @param workerId the workerId to set\n     */\n    public void setWorkerId(String workerId) {\n        this.workerId = workerId;\n    }\n\n    /**\n     * @return the outputData\n     */\n    public Map<String, Object> getOutputData() {\n        return outputData;\n    }\n\n    /**\n     * @param outputData the outputData to set\n     */\n    public void setOutputData(Map<String, Object> outputData) {\n        if (outputData == null) {\n            outputData = new HashMap<>();\n        }\n        this.outputData = outputData;\n    }\n\n    /**\n     * @return Workflow Task definition\n     */\n    public WorkflowTask getWorkflowTask() {\n        return workflowTask;\n    }\n\n    /**\n     * @param workflowTask Task definition\n     */\n    public void setWorkflowTask(WorkflowTask workflowTask) {\n        this.workflowTask = workflowTask;\n    }\n\n    /**\n     * @return the domain\n     */\n    public String getDomain() {\n        return domain;\n    }\n\n    /**\n     * @param domain the Domain\n     */\n    public void setDomain(String domain) {\n        this.domain = domain;\n    }\n\n    public Any getInputMessage() {\n        return inputMessage;\n    }\n\n    public void setInputMessage(Any inputMessage) {\n        this.inputMessage = inputMessage;\n    }\n\n    public Any getOutputMessage() {\n        return outputMessage;\n    }\n\n    public void setOutputMessage(Any outputMessage) {\n        this.outputMessage = outputMessage;\n    }\n\n    /**\n     * @return {@link Optional} containing the task definition if available\n     */\n    public Optional<TaskDef> getTaskDefinition() {\n        return Optional.ofNullable(this.getWorkflowTask()).map(WorkflowTask::getTaskDefinition);\n    }\n\n    public int getRateLimitPerFrequency() {\n        return rateLimitPerFrequency;\n    }\n\n    public void setRateLimitPerFrequency(int rateLimitPerFrequency) {\n        this.rateLimitPerFrequency = rateLimitPerFrequency;\n    }\n\n    public int getRateLimitFrequencyInSeconds() {\n        return rateLimitFrequencyInSeconds;\n    }\n\n    public void setRateLimitFrequencyInSeconds(int rateLimitFrequencyInSeconds) {\n        this.rateLimitFrequencyInSeconds = rateLimitFrequencyInSeconds;\n    }\n\n    /**\n     * @return the external storage path for the task input payload\n     */\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalInputPayloadStoragePath the external storage path where the task input payload\n     *     is stored\n     */\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @return the external storage path for the task output payload\n     */\n    public String getExternalOutputPayloadStoragePath() {\n        return externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalOutputPayloadStoragePath the external storage path where the task output\n     *     payload is stored\n     */\n    public void setExternalOutputPayloadStoragePath(String externalOutputPayloadStoragePath) {\n        this.externalOutputPayloadStoragePath = externalOutputPayloadStoragePath;\n    }\n\n    public void setIsolationGroupId(String isolationGroupId) {\n        this.isolationGroupId = isolationGroupId;\n    }\n\n    public String getIsolationGroupId() {\n        return isolationGroupId;\n    }\n\n    public String getExecutionNameSpace() {\n        return executionNameSpace;\n    }\n\n    public void setExecutionNameSpace(String executionNameSpace) {\n        this.executionNameSpace = executionNameSpace;\n    }\n\n    /**\n     * @return the iteration\n     */\n    public int getIteration() {\n        return iteration;\n    }\n\n    /**\n     * @param iteration iteration\n     */\n    public void setIteration(int iteration) {\n        this.iteration = iteration;\n    }\n\n    public boolean isLoopOverTask() {\n        return iteration > 0;\n    }\n\n    /** * @return the priority defined on workflow */\n    public int getWorkflowPriority() {\n        return workflowPriority;\n    }\n\n    /**\n     * @param workflowPriority Priority defined for workflow\n     */\n    public void setWorkflowPriority(int workflowPriority) {\n        this.workflowPriority = workflowPriority;\n    }\n\n    public boolean isSubworkflowChanged() {\n        return subworkflowChanged;\n    }\n\n    public void setSubworkflowChanged(boolean subworkflowChanged) {\n        this.subworkflowChanged = subworkflowChanged;\n    }\n\n    public String getSubWorkflowId() {\n        // For backwards compatibility\n        if (StringUtils.isNotBlank(subWorkflowId)) {\n            return subWorkflowId;\n        } else {\n            return this.getOutputData() != null && this.getOutputData().get(\"subWorkflowId\") != null\n                    ? (String) this.getOutputData().get(\"subWorkflowId\")\n                    : this.getInputData() != null\n                            ? (String) this.getInputData().get(\"subWorkflowId\")\n                            : null;\n        }\n    }\n\n    public void setSubWorkflowId(String subWorkflowId) {\n        this.subWorkflowId = subWorkflowId;\n        // For backwards compatibility\n        if (this.getOutputData() != null && this.getOutputData().containsKey(\"subWorkflowId\")) {\n            this.getOutputData().put(\"subWorkflowId\", subWorkflowId);\n        }\n    }\n\n    public Task copy() {\n        Task copy = new Task();\n        copy.setCallbackAfterSeconds(callbackAfterSeconds);\n        copy.setCallbackFromWorker(callbackFromWorker);\n        copy.setCorrelationId(correlationId);\n        copy.setInputData(inputData);\n        copy.setOutputData(outputData);\n        copy.setReferenceTaskName(referenceTaskName);\n        copy.setStartDelayInSeconds(startDelayInSeconds);\n        copy.setTaskDefName(taskDefName);\n        copy.setTaskType(taskType);\n        copy.setWorkflowInstanceId(workflowInstanceId);\n        copy.setWorkflowType(workflowType);\n        copy.setResponseTimeoutSeconds(responseTimeoutSeconds);\n        copy.setStatus(status);\n        copy.setRetryCount(retryCount);\n        copy.setPollCount(pollCount);\n        copy.setTaskId(taskId);\n        copy.setWorkflowTask(workflowTask);\n        copy.setDomain(domain);\n        copy.setInputMessage(inputMessage);\n        copy.setOutputMessage(outputMessage);\n        copy.setRateLimitPerFrequency(rateLimitPerFrequency);\n        copy.setRateLimitFrequencyInSeconds(rateLimitFrequencyInSeconds);\n        copy.setExternalInputPayloadStoragePath(externalInputPayloadStoragePath);\n        copy.setExternalOutputPayloadStoragePath(externalOutputPayloadStoragePath);\n        copy.setWorkflowPriority(workflowPriority);\n        copy.setIteration(iteration);\n        copy.setExecutionNameSpace(executionNameSpace);\n        copy.setIsolationGroupId(isolationGroupId);\n        copy.setSubWorkflowId(getSubWorkflowId());\n        copy.setSubworkflowChanged(subworkflowChanged);\n\n        return copy;\n    }\n\n    /**\n     * @return a deep copy of the task instance To be used inside copy Workflow method to provide a\n     *     valid deep copied object. Note: This does not copy the following fields:\n     *     <ul>\n     *       <li>retried\n     *       <li>updateTime\n     *       <li>retriedTaskId\n     *     </ul>\n     */\n    public Task deepCopy() {\n        Task deepCopy = copy();\n        deepCopy.setStartTime(startTime);\n        deepCopy.setScheduledTime(scheduledTime);\n        deepCopy.setEndTime(endTime);\n        deepCopy.setWorkerId(workerId);\n        deepCopy.setReasonForIncompletion(reasonForIncompletion);\n        deepCopy.setSeq(seq);\n\n        return deepCopy;\n    }\n\n    @Override\n    public String toString() {\n        return \"Task{\"\n                + \"taskType='\"\n                + taskType\n                + '\\''\n                + \", status=\"\n                + status\n                + \", inputData=\"\n                + inputData\n                + \", referenceTaskName='\"\n                + referenceTaskName\n                + '\\''\n                + \", retryCount=\"\n                + retryCount\n                + \", seq=\"\n                + seq\n                + \", correlationId='\"\n                + correlationId\n                + '\\''\n                + \", pollCount=\"\n                + pollCount\n                + \", taskDefName='\"\n                + taskDefName\n                + '\\''\n                + \", scheduledTime=\"\n                + scheduledTime\n                + \", startTime=\"\n                + startTime\n                + \", endTime=\"\n                + endTime\n                + \", updateTime=\"\n                + updateTime\n                + \", startDelayInSeconds=\"\n                + startDelayInSeconds\n                + \", retriedTaskId='\"\n                + retriedTaskId\n                + '\\''\n                + \", retried=\"\n                + retried\n                + \", executed=\"\n                + executed\n                + \", callbackFromWorker=\"\n                + callbackFromWorker\n                + \", responseTimeoutSeconds=\"\n                + responseTimeoutSeconds\n                + \", workflowInstanceId='\"\n                + workflowInstanceId\n                + '\\''\n                + \", workflowType='\"\n                + workflowType\n                + '\\''\n                + \", taskId='\"\n                + taskId\n                + '\\''\n                + \", reasonForIncompletion='\"\n                + reasonForIncompletion\n                + '\\''\n                + \", callbackAfterSeconds=\"\n                + callbackAfterSeconds\n                + \", workerId='\"\n                + workerId\n                + '\\''\n                + \", outputData=\"\n                + outputData\n                + \", workflowTask=\"\n                + workflowTask\n                + \", domain='\"\n                + domain\n                + '\\''\n                + \", inputMessage='\"\n                + inputMessage\n                + '\\''\n                + \", outputMessage='\"\n                + outputMessage\n                + '\\''\n                + \", rateLimitPerFrequency=\"\n                + rateLimitPerFrequency\n                + \", rateLimitFrequencyInSeconds=\"\n                + rateLimitFrequencyInSeconds\n                + \", workflowPriority=\"\n                + workflowPriority\n                + \", externalInputPayloadStoragePath='\"\n                + externalInputPayloadStoragePath\n                + '\\''\n                + \", externalOutputPayloadStoragePath='\"\n                + externalOutputPayloadStoragePath\n                + '\\''\n                + \", isolationGroupId='\"\n                + isolationGroupId\n                + '\\''\n                + \", executionNameSpace='\"\n                + executionNameSpace\n                + '\\''\n                + \", subworkflowChanged='\"\n                + subworkflowChanged\n                + '\\''\n                + '}';\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Task task = (Task) o;\n        return getRetryCount() == task.getRetryCount()\n                && getSeq() == task.getSeq()\n                && getPollCount() == task.getPollCount()\n                && getScheduledTime() == task.getScheduledTime()\n                && getStartTime() == task.getStartTime()\n                && getEndTime() == task.getEndTime()\n                && getUpdateTime() == task.getUpdateTime()\n                && getStartDelayInSeconds() == task.getStartDelayInSeconds()\n                && isRetried() == task.isRetried()\n                && isExecuted() == task.isExecuted()\n                && isCallbackFromWorker() == task.isCallbackFromWorker()\n                && getResponseTimeoutSeconds() == task.getResponseTimeoutSeconds()\n                && getCallbackAfterSeconds() == task.getCallbackAfterSeconds()\n                && getRateLimitPerFrequency() == task.getRateLimitPerFrequency()\n                && getRateLimitFrequencyInSeconds() == task.getRateLimitFrequencyInSeconds()\n                && Objects.equals(getTaskType(), task.getTaskType())\n                && getStatus() == task.getStatus()\n                && getIteration() == task.getIteration()\n                && getWorkflowPriority() == task.getWorkflowPriority()\n                && Objects.equals(getInputData(), task.getInputData())\n                && Objects.equals(getReferenceTaskName(), task.getReferenceTaskName())\n                && Objects.equals(getCorrelationId(), task.getCorrelationId())\n                && Objects.equals(getTaskDefName(), task.getTaskDefName())\n                && Objects.equals(getRetriedTaskId(), task.getRetriedTaskId())\n                && Objects.equals(getWorkflowInstanceId(), task.getWorkflowInstanceId())\n                && Objects.equals(getWorkflowType(), task.getWorkflowType())\n                && Objects.equals(getTaskId(), task.getTaskId())\n                && Objects.equals(getReasonForIncompletion(), task.getReasonForIncompletion())\n                && Objects.equals(getWorkerId(), task.getWorkerId())\n                && Objects.equals(getOutputData(), task.getOutputData())\n                && Objects.equals(getWorkflowTask(), task.getWorkflowTask())\n                && Objects.equals(getDomain(), task.getDomain())\n                && Objects.equals(getInputMessage(), task.getInputMessage())\n                && Objects.equals(getOutputMessage(), task.getOutputMessage())\n                && Objects.equals(\n                        getExternalInputPayloadStoragePath(),\n                        task.getExternalInputPayloadStoragePath())\n                && Objects.equals(\n                        getExternalOutputPayloadStoragePath(),\n                        task.getExternalOutputPayloadStoragePath())\n                && Objects.equals(getIsolationGroupId(), task.getIsolationGroupId())\n                && Objects.equals(getExecutionNameSpace(), task.getExecutionNameSpace());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                getTaskType(),\n                getStatus(),\n                getInputData(),\n                getReferenceTaskName(),\n                getWorkflowPriority(),\n                getRetryCount(),\n                getSeq(),\n                getCorrelationId(),\n                getPollCount(),\n                getTaskDefName(),\n                getScheduledTime(),\n                getStartTime(),\n                getEndTime(),\n                getUpdateTime(),\n                getStartDelayInSeconds(),\n                getRetriedTaskId(),\n                isRetried(),\n                isExecuted(),\n                isCallbackFromWorker(),\n                getResponseTimeoutSeconds(),\n                getWorkflowInstanceId(),\n                getWorkflowType(),\n                getTaskId(),\n                getReasonForIncompletion(),\n                getCallbackAfterSeconds(),\n                getWorkerId(),\n                getOutputData(),\n                getWorkflowTask(),\n                getDomain(),\n                getInputMessage(),\n                getOutputMessage(),\n                getRateLimitPerFrequency(),\n                getRateLimitFrequencyInSeconds(),\n                getExternalInputPayloadStoragePath(),\n                getExternalOutputPayloadStoragePath(),\n                getIsolationGroupId(),\n                getExecutionNameSpace());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/tasks/TaskDef.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.tasks;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.Email;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.constraints.OwnerEmailMandatoryConstraint;\nimport com.netflix.conductor.common.constraints.TaskTimeoutConstraint;\nimport com.netflix.conductor.common.metadata.BaseDef;\n\n@ProtoMessage\n@TaskTimeoutConstraint\n@Valid\npublic class TaskDef extends BaseDef {\n\n    @ProtoEnum\n    public enum TimeoutPolicy {\n        RETRY,\n        TIME_OUT_WF,\n        ALERT_ONLY\n    }\n\n    @ProtoEnum\n    public enum RetryLogic {\n        FIXED,\n        EXPONENTIAL_BACKOFF,\n        LINEAR_BACKOFF\n    }\n\n    public static final int ONE_HOUR = 60 * 60;\n\n    /** Unique name identifying the task. The name is unique across */\n    @NotEmpty(message = \"TaskDef name cannot be null or empty\")\n    @ProtoField(id = 1)\n    private String name;\n\n    @ProtoField(id = 2)\n    private String description;\n\n    @ProtoField(id = 3)\n    @Min(value = 0, message = \"TaskDef retryCount: {value} must be >= 0\")\n    private int retryCount = 3; // Default\n\n    @ProtoField(id = 4)\n    @NotNull\n    private long timeoutSeconds;\n\n    @ProtoField(id = 5)\n    private List<String> inputKeys = new ArrayList<>();\n\n    @ProtoField(id = 6)\n    private List<String> outputKeys = new ArrayList<>();\n\n    @ProtoField(id = 7)\n    private TimeoutPolicy timeoutPolicy = TimeoutPolicy.TIME_OUT_WF;\n\n    @ProtoField(id = 8)\n    private RetryLogic retryLogic = RetryLogic.FIXED;\n\n    @ProtoField(id = 9)\n    private int retryDelaySeconds = 60;\n\n    @ProtoField(id = 10)\n    @Min(\n            value = 1,\n            message =\n                    \"TaskDef responseTimeoutSeconds: ${validatedValue} should be minimum {value} second\")\n    private long responseTimeoutSeconds = ONE_HOUR;\n\n    @ProtoField(id = 11)\n    private Integer concurrentExecLimit;\n\n    @ProtoField(id = 12)\n    private Map<String, Object> inputTemplate = new HashMap<>();\n\n    // This field is deprecated, do not use id 13.\n    //\t@ProtoField(id = 13)\n    //\tprivate Integer rateLimitPerSecond;\n\n    @ProtoField(id = 14)\n    private Integer rateLimitPerFrequency;\n\n    @ProtoField(id = 15)\n    private Integer rateLimitFrequencyInSeconds;\n\n    @ProtoField(id = 16)\n    private String isolationGroupId;\n\n    @ProtoField(id = 17)\n    private String executionNameSpace;\n\n    @ProtoField(id = 18)\n    @OwnerEmailMandatoryConstraint\n    @Email(message = \"ownerEmail should be valid email address\")\n    private String ownerEmail;\n\n    @ProtoField(id = 19)\n    @Min(value = 0, message = \"TaskDef pollTimeoutSeconds: {value} must be >= 0\")\n    private Integer pollTimeoutSeconds;\n\n    @ProtoField(id = 20)\n    @Min(value = 1, message = \"Backoff scale factor. Applicable for LINEAR_BACKOFF\")\n    private Integer backoffScaleFactor = 1;\n\n    public TaskDef() {}\n\n    public TaskDef(String name) {\n        this.name = name;\n    }\n\n    public TaskDef(String name, String description) {\n        this.name = name;\n        this.description = description;\n    }\n\n    public TaskDef(String name, String description, int retryCount, long timeoutSeconds) {\n        this.name = name;\n        this.description = description;\n        this.retryCount = retryCount;\n        this.timeoutSeconds = timeoutSeconds;\n    }\n\n    public TaskDef(\n            String name,\n            String description,\n            String ownerEmail,\n            int retryCount,\n            long timeoutSeconds,\n            long responseTimeoutSeconds) {\n        this.name = name;\n        this.description = description;\n        this.ownerEmail = ownerEmail;\n        this.retryCount = retryCount;\n        this.timeoutSeconds = timeoutSeconds;\n        this.responseTimeoutSeconds = responseTimeoutSeconds;\n    }\n\n    /**\n     * @return the name\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @param name the name to set\n     */\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * @return the description\n     */\n    public String getDescription() {\n        return description;\n    }\n\n    /**\n     * @param description the description to set\n     */\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    /**\n     * @return the retryCount\n     */\n    public int getRetryCount() {\n        return retryCount;\n    }\n\n    /**\n     * @param retryCount the retryCount to set\n     */\n    public void setRetryCount(int retryCount) {\n        this.retryCount = retryCount;\n    }\n\n    /**\n     * @return the timeoutSeconds\n     */\n    public long getTimeoutSeconds() {\n        return timeoutSeconds;\n    }\n\n    /**\n     * @param timeoutSeconds the timeoutSeconds to set\n     */\n    public void setTimeoutSeconds(long timeoutSeconds) {\n        this.timeoutSeconds = timeoutSeconds;\n    }\n\n    /**\n     * @return Returns the input keys\n     */\n    public List<String> getInputKeys() {\n        return inputKeys;\n    }\n\n    /**\n     * @param inputKeys Set of keys that the task accepts in the input map\n     */\n    public void setInputKeys(List<String> inputKeys) {\n        this.inputKeys = inputKeys;\n    }\n\n    /**\n     * @return Returns the output keys for the task when executed\n     */\n    public List<String> getOutputKeys() {\n        return outputKeys;\n    }\n\n    /**\n     * @param outputKeys Sets the output keys\n     */\n    public void setOutputKeys(List<String> outputKeys) {\n        this.outputKeys = outputKeys;\n    }\n\n    /**\n     * @return the timeoutPolicy\n     */\n    public TimeoutPolicy getTimeoutPolicy() {\n        return timeoutPolicy;\n    }\n\n    /**\n     * @param timeoutPolicy the timeoutPolicy to set\n     */\n    public void setTimeoutPolicy(TimeoutPolicy timeoutPolicy) {\n        this.timeoutPolicy = timeoutPolicy;\n    }\n\n    /**\n     * @return the retryLogic\n     */\n    public RetryLogic getRetryLogic() {\n        return retryLogic;\n    }\n\n    /**\n     * @param retryLogic the retryLogic to set\n     */\n    public void setRetryLogic(RetryLogic retryLogic) {\n        this.retryLogic = retryLogic;\n    }\n\n    /**\n     * @return the retryDelaySeconds\n     */\n    public int getRetryDelaySeconds() {\n        return retryDelaySeconds;\n    }\n\n    /**\n     * @return the timeout for task to send response. After this timeout, the task will be re-queued\n     */\n    public long getResponseTimeoutSeconds() {\n        return responseTimeoutSeconds;\n    }\n\n    /**\n     * @param responseTimeoutSeconds - timeout for task to send response. After this timeout, the\n     *     task will be re-queued\n     */\n    public void setResponseTimeoutSeconds(long responseTimeoutSeconds) {\n        this.responseTimeoutSeconds = responseTimeoutSeconds;\n    }\n\n    /**\n     * @param retryDelaySeconds the retryDelaySeconds to set\n     */\n    public void setRetryDelaySeconds(int retryDelaySeconds) {\n        this.retryDelaySeconds = retryDelaySeconds;\n    }\n\n    /**\n     * @return the inputTemplate\n     */\n    public Map<String, Object> getInputTemplate() {\n        return inputTemplate;\n    }\n\n    /**\n     * @return rateLimitPerFrequency The max number of tasks that will be allowed to be executed per\n     *     rateLimitFrequencyInSeconds.\n     */\n    public Integer getRateLimitPerFrequency() {\n        return rateLimitPerFrequency == null ? 0 : rateLimitPerFrequency;\n    }\n\n    /**\n     * @param rateLimitPerFrequency The max number of tasks that will be allowed to be executed per\n     *     rateLimitFrequencyInSeconds. Setting the value to 0 removes the rate limit\n     */\n    public void setRateLimitPerFrequency(Integer rateLimitPerFrequency) {\n        this.rateLimitPerFrequency = rateLimitPerFrequency;\n    }\n\n    /**\n     * @return rateLimitFrequencyInSeconds: The time bucket that is used to rate limit tasks based\n     *     on {@link #getRateLimitPerFrequency()} If null or not set, then defaults to 1 second\n     */\n    public Integer getRateLimitFrequencyInSeconds() {\n        return rateLimitFrequencyInSeconds == null ? 1 : rateLimitFrequencyInSeconds;\n    }\n\n    /**\n     * @param rateLimitFrequencyInSeconds: The time window/bucket for which the rate limit needs to\n     *     be applied. This will only have affect if {@link #getRateLimitPerFrequency()} is greater\n     *     than zero\n     */\n    public void setRateLimitFrequencyInSeconds(Integer rateLimitFrequencyInSeconds) {\n        this.rateLimitFrequencyInSeconds = rateLimitFrequencyInSeconds;\n    }\n\n    /**\n     * @param concurrentExecLimit Limit of number of concurrent task that can be IN_PROGRESS at a\n     *     given time. Seting the value to 0 removes the limit.\n     */\n    public void setConcurrentExecLimit(Integer concurrentExecLimit) {\n        this.concurrentExecLimit = concurrentExecLimit;\n    }\n\n    /**\n     * @return Limit of number of concurrent task that can be IN_PROGRESS at a given time\n     */\n    public Integer getConcurrentExecLimit() {\n        return concurrentExecLimit;\n    }\n\n    /**\n     * @return concurrency limit\n     */\n    public int concurrencyLimit() {\n        return concurrentExecLimit == null ? 0 : concurrentExecLimit;\n    }\n\n    /**\n     * @param inputTemplate the inputTemplate to set\n     */\n    public void setInputTemplate(Map<String, Object> inputTemplate) {\n        this.inputTemplate = inputTemplate;\n    }\n\n    public String getIsolationGroupId() {\n        return isolationGroupId;\n    }\n\n    public void setIsolationGroupId(String isolationGroupId) {\n        this.isolationGroupId = isolationGroupId;\n    }\n\n    public String getExecutionNameSpace() {\n        return executionNameSpace;\n    }\n\n    public void setExecutionNameSpace(String executionNameSpace) {\n        this.executionNameSpace = executionNameSpace;\n    }\n\n    /**\n     * @return the email of the owner of this task definition\n     */\n    public String getOwnerEmail() {\n        return ownerEmail;\n    }\n\n    /**\n     * @param ownerEmail the owner email to set\n     */\n    public void setOwnerEmail(String ownerEmail) {\n        this.ownerEmail = ownerEmail;\n    }\n\n    /**\n     * @param pollTimeoutSeconds the poll timeout to set\n     */\n    public void setPollTimeoutSeconds(Integer pollTimeoutSeconds) {\n        this.pollTimeoutSeconds = pollTimeoutSeconds;\n    }\n\n    /**\n     * @return the poll timeout of this task definition\n     */\n    public Integer getPollTimeoutSeconds() {\n        return pollTimeoutSeconds;\n    }\n\n    /**\n     * @param backoffScaleFactor the backoff rate to set\n     */\n    public void setBackoffScaleFactor(Integer backoffScaleFactor) {\n        this.backoffScaleFactor = backoffScaleFactor;\n    }\n\n    /**\n     * @return the backoff rate of this task definition\n     */\n    public Integer getBackoffScaleFactor() {\n        return backoffScaleFactor;\n    }\n\n    @Override\n    public String toString() {\n        return name;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        TaskDef taskDef = (TaskDef) o;\n        return getRetryCount() == taskDef.getRetryCount()\n                && getTimeoutSeconds() == taskDef.getTimeoutSeconds()\n                && getRetryDelaySeconds() == taskDef.getRetryDelaySeconds()\n                && getBackoffScaleFactor() == taskDef.getBackoffScaleFactor()\n                && getResponseTimeoutSeconds() == taskDef.getResponseTimeoutSeconds()\n                && Objects.equals(getName(), taskDef.getName())\n                && Objects.equals(getDescription(), taskDef.getDescription())\n                && Objects.equals(getInputKeys(), taskDef.getInputKeys())\n                && Objects.equals(getOutputKeys(), taskDef.getOutputKeys())\n                && getTimeoutPolicy() == taskDef.getTimeoutPolicy()\n                && getRetryLogic() == taskDef.getRetryLogic()\n                && Objects.equals(getConcurrentExecLimit(), taskDef.getConcurrentExecLimit())\n                && Objects.equals(getRateLimitPerFrequency(), taskDef.getRateLimitPerFrequency())\n                && Objects.equals(getInputTemplate(), taskDef.getInputTemplate())\n                && Objects.equals(getIsolationGroupId(), taskDef.getIsolationGroupId())\n                && Objects.equals(getExecutionNameSpace(), taskDef.getExecutionNameSpace())\n                && Objects.equals(getOwnerEmail(), taskDef.getOwnerEmail());\n    }\n\n    @Override\n    public int hashCode() {\n\n        return Objects.hash(\n                getName(),\n                getDescription(),\n                getRetryCount(),\n                getTimeoutSeconds(),\n                getInputKeys(),\n                getOutputKeys(),\n                getTimeoutPolicy(),\n                getRetryLogic(),\n                getRetryDelaySeconds(),\n                getBackoffScaleFactor(),\n                getResponseTimeoutSeconds(),\n                getConcurrentExecLimit(),\n                getRateLimitPerFrequency(),\n                getInputTemplate(),\n                getIsolationGroupId(),\n                getExecutionNameSpace(),\n                getOwnerEmail());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/tasks/TaskExecLog.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.tasks;\n\nimport java.util.Objects;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\n/** Model that represents the task's execution log. */\n@ProtoMessage\npublic class TaskExecLog {\n\n    @ProtoField(id = 1)\n    private String log;\n\n    @ProtoField(id = 2)\n    private String taskId;\n\n    @ProtoField(id = 3)\n    private long createdTime;\n\n    public TaskExecLog() {}\n\n    public TaskExecLog(String log) {\n        this.log = log;\n        this.createdTime = System.currentTimeMillis();\n    }\n\n    /**\n     * @return Task Exec Log\n     */\n    public String getLog() {\n        return log;\n    }\n\n    /**\n     * @param log The Log\n     */\n    public void setLog(String log) {\n        this.log = log;\n    }\n\n    /**\n     * @return the taskId\n     */\n    public String getTaskId() {\n        return taskId;\n    }\n\n    /**\n     * @param taskId the taskId to set\n     */\n    public void setTaskId(String taskId) {\n        this.taskId = taskId;\n    }\n\n    /**\n     * @return the createdTime\n     */\n    public long getCreatedTime() {\n        return createdTime;\n    }\n\n    /**\n     * @param createdTime the createdTime to set\n     */\n    public void setCreatedTime(long createdTime) {\n        this.createdTime = createdTime;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        TaskExecLog that = (TaskExecLog) o;\n        return createdTime == that.createdTime\n                && Objects.equals(log, that.log)\n                && Objects.equals(taskId, that.taskId);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(log, taskId, createdTime);\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/tasks/TaskResult.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.tasks;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport javax.validation.constraints.NotEmpty;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\nimport com.google.protobuf.Any;\nimport io.swagger.v3.oas.annotations.Hidden;\n\n/** Result of the task execution. */\n@ProtoMessage\npublic class TaskResult {\n\n    @ProtoEnum\n    public enum Status {\n        IN_PROGRESS,\n        FAILED,\n        FAILED_WITH_TERMINAL_ERROR,\n        COMPLETED\n    }\n\n    @NotEmpty(message = \"Workflow Id cannot be null or empty\")\n    @ProtoField(id = 1)\n    private String workflowInstanceId;\n\n    @NotEmpty(message = \"Task ID cannot be null or empty\")\n    @ProtoField(id = 2)\n    private String taskId;\n\n    @ProtoField(id = 3)\n    private String reasonForIncompletion;\n\n    @ProtoField(id = 4)\n    private long callbackAfterSeconds;\n\n    @ProtoField(id = 5)\n    private String workerId;\n\n    @ProtoField(id = 6)\n    private Status status;\n\n    @ProtoField(id = 7)\n    private Map<String, Object> outputData = new HashMap<>();\n\n    @ProtoField(id = 8)\n    @Hidden\n    private Any outputMessage;\n\n    private List<TaskExecLog> logs = new CopyOnWriteArrayList<>();\n\n    private String externalOutputPayloadStoragePath;\n\n    private String subWorkflowId;\n\n    private boolean extendLease;\n\n    public TaskResult(Task task) {\n        this.workflowInstanceId = task.getWorkflowInstanceId();\n        this.taskId = task.getTaskId();\n        this.reasonForIncompletion = task.getReasonForIncompletion();\n        this.callbackAfterSeconds = task.getCallbackAfterSeconds();\n        this.workerId = task.getWorkerId();\n        this.outputData = task.getOutputData();\n        this.externalOutputPayloadStoragePath = task.getExternalOutputPayloadStoragePath();\n        this.subWorkflowId = task.getSubWorkflowId();\n        switch (task.getStatus()) {\n            case CANCELED:\n            case COMPLETED_WITH_ERRORS:\n            case TIMED_OUT:\n            case SKIPPED:\n                this.status = Status.FAILED;\n                break;\n            case SCHEDULED:\n                this.status = Status.IN_PROGRESS;\n                break;\n            default:\n                this.status = Status.valueOf(task.getStatus().name());\n                break;\n        }\n    }\n\n    public TaskResult() {}\n\n    /**\n     * @return Workflow instance id for which the task result is produced\n     */\n    public String getWorkflowInstanceId() {\n        return workflowInstanceId;\n    }\n\n    public void setWorkflowInstanceId(String workflowInstanceId) {\n        this.workflowInstanceId = workflowInstanceId;\n    }\n\n    public String getTaskId() {\n        return taskId;\n    }\n\n    public void setTaskId(String taskId) {\n        this.taskId = taskId;\n    }\n\n    public String getReasonForIncompletion() {\n        return reasonForIncompletion;\n    }\n\n    public void setReasonForIncompletion(String reasonForIncompletion) {\n        this.reasonForIncompletion = StringUtils.substring(reasonForIncompletion, 0, 500);\n    }\n\n    public long getCallbackAfterSeconds() {\n        return callbackAfterSeconds;\n    }\n\n    /**\n     * When set to non-zero values, the task remains in the queue for the specified seconds before\n     * sent back to the worker when polled. Useful for the long running task, where the task is\n     * updated as IN_PROGRESS and should not be polled out of the queue for a specified amount of\n     * time. (delayed queue implementation)\n     *\n     * @param callbackAfterSeconds Amount of time in seconds the task should be held in the queue\n     *     before giving it to a polling worker.\n     */\n    public void setCallbackAfterSeconds(long callbackAfterSeconds) {\n        this.callbackAfterSeconds = callbackAfterSeconds;\n    }\n\n    public String getWorkerId() {\n        return workerId;\n    }\n\n    /**\n     * @param workerId a free form string identifying the worker host. Could be hostname, IP Address\n     *     or any other meaningful identifier that can help identify the host/process which executed\n     *     the task, in case of troubleshooting.\n     */\n    public void setWorkerId(String workerId) {\n        this.workerId = workerId;\n    }\n\n    /**\n     * @return the status\n     */\n    public Status getStatus() {\n        return status;\n    }\n\n    /**\n     * @param status Status of the task\n     *     <p><b>IN_PROGRESS</b>: Use this for long running tasks, indicating the task is still in\n     *     progress and should be checked again at a later time. e.g. the worker checks the status\n     *     of the job in the DB, while the job is being executed by another process.\n     *     <p><b>FAILED, FAILED_WITH_TERMINAL_ERROR, COMPLETED</b>: Terminal statuses for the task.\n     *     Use FAILED_WITH_TERMINAL_ERROR when you do not want the task to be retried.\n     * @see #setCallbackAfterSeconds(long)\n     */\n    public void setStatus(Status status) {\n        this.status = status;\n    }\n\n    public Map<String, Object> getOutputData() {\n        return outputData;\n    }\n\n    /**\n     * @param outputData output data to be set for the task execution result\n     */\n    public void setOutputData(Map<String, Object> outputData) {\n        this.outputData = outputData;\n    }\n\n    /**\n     * Adds output\n     *\n     * @param key output field\n     * @param value value\n     * @return current instance\n     */\n    public TaskResult addOutputData(String key, Object value) {\n        this.outputData.put(key, value);\n        return this;\n    }\n\n    public Any getOutputMessage() {\n        return outputMessage;\n    }\n\n    public void setOutputMessage(Any outputMessage) {\n        this.outputMessage = outputMessage;\n    }\n\n    /**\n     * @return Task execution logs\n     */\n    public List<TaskExecLog> getLogs() {\n        return logs;\n    }\n\n    /**\n     * @param logs Task execution logs\n     */\n    public void setLogs(List<TaskExecLog> logs) {\n        this.logs = logs;\n    }\n\n    /**\n     * @param log Log line to be added\n     * @return Instance of TaskResult\n     */\n    public TaskResult log(String log) {\n        this.logs.add(new TaskExecLog(log));\n        return this;\n    }\n\n    /**\n     * @return the path where the task output is stored in external storage\n     */\n    public String getExternalOutputPayloadStoragePath() {\n        return externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalOutputPayloadStoragePath path in the external storage where the task output is\n     *     stored\n     */\n    public void setExternalOutputPayloadStoragePath(String externalOutputPayloadStoragePath) {\n        this.externalOutputPayloadStoragePath = externalOutputPayloadStoragePath;\n    }\n\n    public String getSubWorkflowId() {\n        return subWorkflowId;\n    }\n\n    public void setSubWorkflowId(String subWorkflowId) {\n        this.subWorkflowId = subWorkflowId;\n    }\n\n    public boolean isExtendLease() {\n        return extendLease;\n    }\n\n    public void setExtendLease(boolean extendLease) {\n        this.extendLease = extendLease;\n    }\n\n    @Override\n    public String toString() {\n        return \"TaskResult{\"\n                + \"workflowInstanceId='\"\n                + workflowInstanceId\n                + '\\''\n                + \", taskId='\"\n                + taskId\n                + '\\''\n                + \", reasonForIncompletion='\"\n                + reasonForIncompletion\n                + '\\''\n                + \", callbackAfterSeconds=\"\n                + callbackAfterSeconds\n                + \", workerId='\"\n                + workerId\n                + '\\''\n                + \", status=\"\n                + status\n                + \", outputData=\"\n                + outputData\n                + \", outputMessage=\"\n                + outputMessage\n                + \", logs=\"\n                + logs\n                + \", externalOutputPayloadStoragePath='\"\n                + externalOutputPayloadStoragePath\n                + '\\''\n                + \", subWorkflowId='\"\n                + subWorkflowId\n                + '\\''\n                + \", extendLease='\"\n                + extendLease\n                + '\\''\n                + '}';\n    }\n\n    public static TaskResult complete() {\n        return newTaskResult(Status.COMPLETED);\n    }\n\n    public static TaskResult failed() {\n        return newTaskResult(Status.FAILED);\n    }\n\n    public static TaskResult failed(String failureReason) {\n        TaskResult result = newTaskResult(Status.FAILED);\n        result.setReasonForIncompletion(failureReason);\n        return result;\n    }\n\n    public static TaskResult inProgress() {\n        return newTaskResult(Status.IN_PROGRESS);\n    }\n\n    public static TaskResult newTaskResult(Status status) {\n        TaskResult result = new TaskResult();\n        result.setStatus(status);\n        return result;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/tasks/TaskType.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.tasks;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\n\n@ProtoEnum\npublic enum TaskType {\n    SIMPLE,\n    DYNAMIC,\n    FORK_JOIN,\n    FORK_JOIN_DYNAMIC,\n    DECISION,\n    SWITCH,\n    JOIN,\n    DO_WHILE,\n    SUB_WORKFLOW,\n    START_WORKFLOW,\n    EVENT,\n    WAIT,\n    HUMAN,\n    USER_DEFINED,\n    HTTP,\n    LAMBDA,\n    INLINE,\n    EXCLUSIVE_JOIN,\n    TERMINATE,\n    KAFKA_PUBLISH,\n    JSON_JQ_TRANSFORM,\n    SET_VARIABLE,\n    NOOP;\n\n    /**\n     * TaskType constants representing each of the possible enumeration values. Motivation: to not\n     * have any hardcoded/inline strings used in the code.\n     */\n    public static final String TASK_TYPE_DECISION = \"DECISION\";\n\n    public static final String TASK_TYPE_SWITCH = \"SWITCH\";\n    public static final String TASK_TYPE_DYNAMIC = \"DYNAMIC\";\n    public static final String TASK_TYPE_JOIN = \"JOIN\";\n    public static final String TASK_TYPE_DO_WHILE = \"DO_WHILE\";\n    public static final String TASK_TYPE_FORK_JOIN_DYNAMIC = \"FORK_JOIN_DYNAMIC\";\n    public static final String TASK_TYPE_EVENT = \"EVENT\";\n    public static final String TASK_TYPE_WAIT = \"WAIT\";\n    public static final String TASK_TYPE_HUMAN = \"HUMAN\";\n    public static final String TASK_TYPE_SUB_WORKFLOW = \"SUB_WORKFLOW\";\n    public static final String TASK_TYPE_START_WORKFLOW = \"START_WORKFLOW\";\n    public static final String TASK_TYPE_FORK_JOIN = \"FORK_JOIN\";\n    public static final String TASK_TYPE_SIMPLE = \"SIMPLE\";\n    public static final String TASK_TYPE_HTTP = \"HTTP\";\n    public static final String TASK_TYPE_LAMBDA = \"LAMBDA\";\n    public static final String TASK_TYPE_INLINE = \"INLINE\";\n    public static final String TASK_TYPE_EXCLUSIVE_JOIN = \"EXCLUSIVE_JOIN\";\n    public static final String TASK_TYPE_TERMINATE = \"TERMINATE\";\n    public static final String TASK_TYPE_KAFKA_PUBLISH = \"KAFKA_PUBLISH\";\n    public static final String TASK_TYPE_JSON_JQ_TRANSFORM = \"JSON_JQ_TRANSFORM\";\n    public static final String TASK_TYPE_SET_VARIABLE = \"SET_VARIABLE\";\n    public static final String TASK_TYPE_FORK = \"FORK\";\n    public static final String TASK_TYPE_NOOP = \"NOOP\";\n\n    private static final Set<String> BUILT_IN_TASKS = new HashSet<>();\n\n    static {\n        BUILT_IN_TASKS.add(TASK_TYPE_DECISION);\n        BUILT_IN_TASKS.add(TASK_TYPE_SWITCH);\n        BUILT_IN_TASKS.add(TASK_TYPE_FORK);\n        BUILT_IN_TASKS.add(TASK_TYPE_JOIN);\n        BUILT_IN_TASKS.add(TASK_TYPE_EXCLUSIVE_JOIN);\n        BUILT_IN_TASKS.add(TASK_TYPE_DO_WHILE);\n    }\n\n    /**\n     * Converts a task type string to {@link TaskType}. For an unknown string, the value is\n     * defaulted to {@link TaskType#USER_DEFINED}.\n     *\n     * <p>NOTE: Use {@link Enum#valueOf(Class, String)} if the default of USER_DEFINED is not\n     * necessary.\n     *\n     * @param taskType The task type string.\n     * @return The {@link TaskType} enum.\n     */\n    public static TaskType of(String taskType) {\n        try {\n            return TaskType.valueOf(taskType);\n        } catch (IllegalArgumentException iae) {\n            return TaskType.USER_DEFINED;\n        }\n    }\n\n    public static boolean isBuiltIn(String taskType) {\n        return BUILT_IN_TASKS.contains(taskType);\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/DynamicForkJoinTask.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\n\n@ProtoMessage\npublic class DynamicForkJoinTask {\n\n    @ProtoField(id = 1)\n    private String taskName;\n\n    @ProtoField(id = 2)\n    private String workflowName;\n\n    @ProtoField(id = 3)\n    private String referenceName;\n\n    @ProtoField(id = 4)\n    private Map<String, Object> input = new HashMap<>();\n\n    @ProtoField(id = 5)\n    private String type = TaskType.SIMPLE.name();\n\n    public DynamicForkJoinTask() {}\n\n    public DynamicForkJoinTask(\n            String taskName, String workflowName, String referenceName, Map<String, Object> input) {\n        super();\n        this.taskName = taskName;\n        this.workflowName = workflowName;\n        this.referenceName = referenceName;\n        this.input = input;\n    }\n\n    public DynamicForkJoinTask(\n            String taskName,\n            String workflowName,\n            String referenceName,\n            String type,\n            Map<String, Object> input) {\n        super();\n        this.taskName = taskName;\n        this.workflowName = workflowName;\n        this.referenceName = referenceName;\n        this.input = input;\n        this.type = type;\n    }\n\n    public String getTaskName() {\n        return taskName;\n    }\n\n    public void setTaskName(String taskName) {\n        this.taskName = taskName;\n    }\n\n    public String getWorkflowName() {\n        return workflowName;\n    }\n\n    public void setWorkflowName(String workflowName) {\n        this.workflowName = workflowName;\n    }\n\n    public String getReferenceName() {\n        return referenceName;\n    }\n\n    public void setReferenceName(String referenceName) {\n        this.referenceName = referenceName;\n    }\n\n    public Map<String, Object> getInput() {\n        return input;\n    }\n\n    public void setInput(Map<String, Object> input) {\n        this.input = input;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/DynamicForkJoinTaskList.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\n@ProtoMessage\npublic class DynamicForkJoinTaskList {\n\n    @ProtoField(id = 1)\n    private List<DynamicForkJoinTask> dynamicTasks = new ArrayList<>();\n\n    public void add(\n            String taskName, String workflowName, String referenceName, Map<String, Object> input) {\n        dynamicTasks.add(new DynamicForkJoinTask(taskName, workflowName, referenceName, input));\n    }\n\n    public void add(DynamicForkJoinTask dtask) {\n        dynamicTasks.add(dtask);\n    }\n\n    public List<DynamicForkJoinTask> getDynamicTasks() {\n        return dynamicTasks;\n    }\n\n    public void setDynamicTasks(List<DynamicForkJoinTask> dynamicTasks) {\n        this.dynamicTasks = dynamicTasks;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/RerunWorkflowRequest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.Map;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\n@ProtoMessage\npublic class RerunWorkflowRequest {\n\n    @ProtoField(id = 1)\n    private String reRunFromWorkflowId;\n\n    @ProtoField(id = 2)\n    private Map<String, Object> workflowInput;\n\n    @ProtoField(id = 3)\n    private String reRunFromTaskId;\n\n    @ProtoField(id = 4)\n    private Map<String, Object> taskInput;\n\n    @ProtoField(id = 5)\n    private String correlationId;\n\n    public String getReRunFromWorkflowId() {\n        return reRunFromWorkflowId;\n    }\n\n    public void setReRunFromWorkflowId(String reRunFromWorkflowId) {\n        this.reRunFromWorkflowId = reRunFromWorkflowId;\n    }\n\n    public Map<String, Object> getWorkflowInput() {\n        return workflowInput;\n    }\n\n    public void setWorkflowInput(Map<String, Object> workflowInput) {\n        this.workflowInput = workflowInput;\n    }\n\n    public String getReRunFromTaskId() {\n        return reRunFromTaskId;\n    }\n\n    public void setReRunFromTaskId(String reRunFromTaskId) {\n        this.reRunFromTaskId = reRunFromTaskId;\n    }\n\n    public Map<String, Object> getTaskInput() {\n        return taskInput;\n    }\n\n    public void setTaskInput(Map<String, Object> taskInput) {\n        this.taskInput = taskInput;\n    }\n\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/SkipTaskRequest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.Map;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\nimport com.google.protobuf.Any;\nimport io.swagger.v3.oas.annotations.Hidden;\n\n@ProtoMessage(toProto = false)\npublic class SkipTaskRequest {\n\n    @ProtoField(id = 1)\n    private Map<String, Object> taskInput;\n\n    @ProtoField(id = 2)\n    private Map<String, Object> taskOutput;\n\n    @ProtoField(id = 3)\n    @Hidden\n    private Any taskInputMessage;\n\n    @ProtoField(id = 4)\n    @Hidden\n    private Any taskOutputMessage;\n\n    public Map<String, Object> getTaskInput() {\n        return taskInput;\n    }\n\n    public void setTaskInput(Map<String, Object> taskInput) {\n        this.taskInput = taskInput;\n    }\n\n    public Map<String, Object> getTaskOutput() {\n        return taskOutput;\n    }\n\n    public void setTaskOutput(Map<String, Object> taskOutput) {\n        this.taskOutput = taskOutput;\n    }\n\n    public Any getTaskInputMessage() {\n        return taskInputMessage;\n    }\n\n    public void setTaskInputMessage(Any taskInputMessage) {\n        this.taskInputMessage = taskInputMessage;\n    }\n\n    public Any getTaskOutputMessage() {\n        return taskOutputMessage;\n    }\n\n    public void setTaskOutputMessage(Any taskOutputMessage) {\n        this.taskOutputMessage = taskOutputMessage;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/StartWorkflowRequest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.NotNull;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\n@ProtoMessage\npublic class StartWorkflowRequest {\n\n    @ProtoField(id = 1)\n    @NotNull(message = \"Workflow name cannot be null or empty\")\n    private String name;\n\n    @ProtoField(id = 2)\n    private Integer version;\n\n    @ProtoField(id = 3)\n    private String correlationId;\n\n    @ProtoField(id = 4)\n    private Map<String, Object> input = new HashMap<>();\n\n    @ProtoField(id = 5)\n    private Map<String, String> taskToDomain = new HashMap<>();\n\n    @ProtoField(id = 6)\n    @Valid\n    private WorkflowDef workflowDef;\n\n    @ProtoField(id = 7)\n    private String externalInputPayloadStoragePath;\n\n    @ProtoField(id = 8)\n    @Min(value = 0, message = \"priority: ${validatedValue} should be minimum {value}\")\n    @Max(value = 99, message = \"priority: ${validatedValue} should be maximum {value}\")\n    private Integer priority = 0;\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public StartWorkflowRequest withName(String name) {\n        this.name = name;\n        return this;\n    }\n\n    public Integer getVersion() {\n        return version;\n    }\n\n    public void setVersion(Integer version) {\n        this.version = version;\n    }\n\n    public StartWorkflowRequest withVersion(Integer version) {\n        this.version = version;\n        return this;\n    }\n\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    public StartWorkflowRequest withCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n        return this;\n    }\n\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    public StartWorkflowRequest withExternalInputPayloadStoragePath(\n            String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n        return this;\n    }\n\n    public Integer getPriority() {\n        return priority;\n    }\n\n    public void setPriority(Integer priority) {\n        this.priority = priority;\n    }\n\n    public StartWorkflowRequest withPriority(Integer priority) {\n        this.priority = priority;\n        return this;\n    }\n\n    public Map<String, Object> getInput() {\n        return input;\n    }\n\n    public void setInput(Map<String, Object> input) {\n        this.input = input;\n    }\n\n    public StartWorkflowRequest withInput(Map<String, Object> input) {\n        this.input = input;\n        return this;\n    }\n\n    public Map<String, String> getTaskToDomain() {\n        return taskToDomain;\n    }\n\n    public void setTaskToDomain(Map<String, String> taskToDomain) {\n        this.taskToDomain = taskToDomain;\n    }\n\n    public StartWorkflowRequest withTaskToDomain(Map<String, String> taskToDomain) {\n        this.taskToDomain = taskToDomain;\n        return this;\n    }\n\n    public WorkflowDef getWorkflowDef() {\n        return workflowDef;\n    }\n\n    public void setWorkflowDef(WorkflowDef workflowDef) {\n        this.workflowDef = workflowDef;\n    }\n\n    public StartWorkflowRequest withWorkflowDef(WorkflowDef workflowDef) {\n        this.workflowDef = workflowDef;\n        return this;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/SubWorkflowParams.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.Map;\nimport java.util.Objects;\n\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\n\nimport com.fasterxml.jackson.annotation.JsonGetter;\nimport com.fasterxml.jackson.annotation.JsonSetter;\n\n@ProtoMessage\npublic class SubWorkflowParams {\n\n    @ProtoField(id = 1)\n    @NotNull(message = \"SubWorkflowParams name cannot be null\")\n    @NotEmpty(message = \"SubWorkflowParams name cannot be empty\")\n    private String name;\n\n    @ProtoField(id = 2)\n    private Integer version;\n\n    @ProtoField(id = 3)\n    private Map<String, String> taskToDomain;\n\n    // workaround as WorkflowDef cannot directly be used due to cyclic dependency issue in protobuf\n    // imports\n    @ProtoField(id = 4)\n    private Object workflowDefinition;\n\n    /**\n     * @return the name\n     */\n    public String getName() {\n        if (workflowDefinition != null) {\n            return getWorkflowDef().getName();\n        } else {\n            return name;\n        }\n    }\n\n    /**\n     * @param name the name to set\n     */\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * @return the version\n     */\n    public Integer getVersion() {\n        if (workflowDefinition != null) {\n            return getWorkflowDef().getVersion();\n        } else {\n            return version;\n        }\n    }\n\n    /**\n     * @param version the version to set\n     */\n    public void setVersion(Integer version) {\n        this.version = version;\n    }\n\n    /**\n     * @return the taskToDomain\n     */\n    public Map<String, String> getTaskToDomain() {\n        return taskToDomain;\n    }\n\n    /**\n     * @param taskToDomain the taskToDomain to set\n     */\n    public void setTaskToDomain(Map<String, String> taskToDomain) {\n        this.taskToDomain = taskToDomain;\n    }\n\n    /**\n     * @return the workflowDefinition as an Object\n     */\n    public Object getWorkflowDefinition() {\n        return workflowDefinition;\n    }\n\n    /**\n     * @return the workflowDefinition as a WorkflowDef\n     */\n    @JsonGetter(\"workflowDefinition\")\n    public WorkflowDef getWorkflowDef() {\n        return (WorkflowDef) workflowDefinition;\n    }\n\n    /**\n     * @param workflowDef the workflowDefinition to set\n     */\n    public void setWorkflowDefinition(Object workflowDef) {\n        if (!(workflowDef == null || workflowDef instanceof WorkflowDef)) {\n            throw new IllegalArgumentException(\n                    \"workflowDefinition must be either null or WorkflowDef\");\n        }\n        this.workflowDefinition = workflowDef;\n    }\n\n    /**\n     * @param workflowDef the workflowDefinition to set\n     */\n    @JsonSetter(\"workflowDefinition\")\n    public void setWorkflowDef(WorkflowDef workflowDef) {\n        this.workflowDefinition = workflowDef;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        SubWorkflowParams that = (SubWorkflowParams) o;\n        return Objects.equals(getName(), that.getName())\n                && Objects.equals(getVersion(), that.getVersion())\n                && Objects.equals(getTaskToDomain(), that.getTaskToDomain())\n                && Objects.equals(getWorkflowDefinition(), that.getWorkflowDefinition());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/WorkflowDef.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.Email;\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.constraints.NoSemiColonConstraint;\nimport com.netflix.conductor.common.constraints.OwnerEmailMandatoryConstraint;\nimport com.netflix.conductor.common.constraints.TaskReferenceNameUniqueConstraint;\nimport com.netflix.conductor.common.metadata.BaseDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\n\n@ProtoMessage\n@TaskReferenceNameUniqueConstraint\npublic class WorkflowDef extends BaseDef {\n\n    @ProtoEnum\n    public enum TimeoutPolicy {\n        TIME_OUT_WF,\n        ALERT_ONLY\n    }\n\n    @NotEmpty(message = \"WorkflowDef name cannot be null or empty\")\n    @ProtoField(id = 1)\n    @NoSemiColonConstraint(\n            message = \"Workflow name cannot contain the following set of characters: ':'\")\n    private String name;\n\n    @ProtoField(id = 2)\n    private String description;\n\n    @ProtoField(id = 3)\n    private int version = 1;\n\n    @ProtoField(id = 4)\n    @NotNull\n    @NotEmpty(message = \"WorkflowTask list cannot be empty\")\n    private List<@Valid WorkflowTask> tasks = new LinkedList<>();\n\n    @ProtoField(id = 5)\n    private List<String> inputParameters = new LinkedList<>();\n\n    @ProtoField(id = 6)\n    private Map<String, Object> outputParameters = new HashMap<>();\n\n    @ProtoField(id = 7)\n    private String failureWorkflow;\n\n    @ProtoField(id = 8)\n    @Min(value = 2, message = \"workflowDef schemaVersion: {value} is only supported\")\n    @Max(value = 2, message = \"workflowDef schemaVersion: {value} is only supported\")\n    private int schemaVersion = 2;\n\n    // By default, a workflow is restartable\n    @ProtoField(id = 9)\n    private boolean restartable = true;\n\n    @ProtoField(id = 10)\n    private boolean workflowStatusListenerEnabled = false;\n\n    @ProtoField(id = 11)\n    @OwnerEmailMandatoryConstraint\n    @Email(message = \"ownerEmail should be valid email address\")\n    private String ownerEmail;\n\n    @ProtoField(id = 12)\n    private TimeoutPolicy timeoutPolicy = TimeoutPolicy.ALERT_ONLY;\n\n    @ProtoField(id = 13)\n    @NotNull\n    private long timeoutSeconds;\n\n    @ProtoField(id = 14)\n    private Map<String, Object> variables = new HashMap<>();\n\n    @ProtoField(id = 15)\n    private Map<String, Object> inputTemplate = new HashMap<>();\n\n    /**\n     * @return the name\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @param name the name to set\n     */\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * @return the description\n     */\n    public String getDescription() {\n        return description;\n    }\n\n    /**\n     * @param description the description to set\n     */\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    /**\n     * @return the tasks\n     */\n    public List<WorkflowTask> getTasks() {\n        return tasks;\n    }\n\n    /**\n     * @param tasks the tasks to set\n     */\n    public void setTasks(List<@Valid WorkflowTask> tasks) {\n        this.tasks = tasks;\n    }\n\n    /**\n     * @return the inputParameters\n     */\n    public List<String> getInputParameters() {\n        return inputParameters;\n    }\n\n    /**\n     * @param inputParameters the inputParameters to set\n     */\n    public void setInputParameters(List<String> inputParameters) {\n        this.inputParameters = inputParameters;\n    }\n\n    /**\n     * @return the outputParameters\n     */\n    public Map<String, Object> getOutputParameters() {\n        return outputParameters;\n    }\n\n    /**\n     * @param outputParameters the outputParameters to set\n     */\n    public void setOutputParameters(Map<String, Object> outputParameters) {\n        this.outputParameters = outputParameters;\n    }\n\n    /**\n     * @return the version\n     */\n    public int getVersion() {\n        return version;\n    }\n\n    /**\n     * @return the failureWorkflow\n     */\n    public String getFailureWorkflow() {\n        return failureWorkflow;\n    }\n\n    /**\n     * @param failureWorkflow the failureWorkflow to set\n     */\n    public void setFailureWorkflow(String failureWorkflow) {\n        this.failureWorkflow = failureWorkflow;\n    }\n\n    /**\n     * @param version the version to set\n     */\n    public void setVersion(int version) {\n        this.version = version;\n    }\n\n    /**\n     * This method determines if the workflow is restartable or not\n     *\n     * @return true: if the workflow is restartable false: if the workflow is non restartable\n     */\n    public boolean isRestartable() {\n        return restartable;\n    }\n\n    /**\n     * This method is called only when the workflow definition is created\n     *\n     * @param restartable true: if the workflow is restartable false: if the workflow is non\n     *     restartable\n     */\n    public void setRestartable(boolean restartable) {\n        this.restartable = restartable;\n    }\n\n    /**\n     * @return the schemaVersion\n     */\n    public int getSchemaVersion() {\n        return schemaVersion;\n    }\n\n    /**\n     * @param schemaVersion the schemaVersion to set\n     */\n    public void setSchemaVersion(int schemaVersion) {\n        this.schemaVersion = schemaVersion;\n    }\n\n    /**\n     * @return true is workflow listener will be invoked when workflow gets into a terminal state\n     */\n    public boolean isWorkflowStatusListenerEnabled() {\n        return workflowStatusListenerEnabled;\n    }\n\n    /**\n     * Specify if workflow listener is enabled to invoke a callback for completed or terminated\n     * workflows\n     *\n     * @param workflowStatusListenerEnabled\n     */\n    public void setWorkflowStatusListenerEnabled(boolean workflowStatusListenerEnabled) {\n        this.workflowStatusListenerEnabled = workflowStatusListenerEnabled;\n    }\n\n    /**\n     * @return the email of the owner of this workflow definition\n     */\n    public String getOwnerEmail() {\n        return ownerEmail;\n    }\n\n    /**\n     * @param ownerEmail the owner email to set\n     */\n    public void setOwnerEmail(String ownerEmail) {\n        this.ownerEmail = ownerEmail;\n    }\n\n    /**\n     * @return the timeoutPolicy\n     */\n    public TimeoutPolicy getTimeoutPolicy() {\n        return timeoutPolicy;\n    }\n\n    /**\n     * @param timeoutPolicy the timeoutPolicy to set\n     */\n    public void setTimeoutPolicy(TimeoutPolicy timeoutPolicy) {\n        this.timeoutPolicy = timeoutPolicy;\n    }\n\n    /**\n     * @return the time after which a workflow is deemed to have timed out\n     */\n    public long getTimeoutSeconds() {\n        return timeoutSeconds;\n    }\n\n    /**\n     * @param timeoutSeconds the timeout in seconds to set\n     */\n    public void setTimeoutSeconds(long timeoutSeconds) {\n        this.timeoutSeconds = timeoutSeconds;\n    }\n\n    /**\n     * @return the global workflow variables\n     */\n    public Map<String, Object> getVariables() {\n        return variables;\n    }\n\n    /**\n     * @param variables the set of global workflow variables to set\n     */\n    public void setVariables(Map<String, Object> variables) {\n        this.variables = variables;\n    }\n\n    public Map<String, Object> getInputTemplate() {\n        return inputTemplate;\n    }\n\n    public void setInputTemplate(Map<String, Object> inputTemplate) {\n        this.inputTemplate = inputTemplate;\n    }\n\n    public String key() {\n        return getKey(name, version);\n    }\n\n    public static String getKey(String name, int version) {\n        return name + \".\" + version;\n    }\n\n    public boolean containsType(String taskType) {\n        return collectTasks().stream().anyMatch(t -> t.getType().equals(taskType));\n    }\n\n    public WorkflowTask getNextTask(String taskReferenceName) {\n        WorkflowTask workflowTask = getTaskByRefName(taskReferenceName);\n        if (workflowTask != null && TaskType.TERMINATE.name().equals(workflowTask.getType())) {\n            return null;\n        }\n\n        Iterator<WorkflowTask> iterator = tasks.iterator();\n        while (iterator.hasNext()) {\n            WorkflowTask task = iterator.next();\n            if (task.getTaskReferenceName().equals(taskReferenceName)) {\n                // If taskReferenceName matches, break out\n                break;\n            }\n            WorkflowTask nextTask = task.next(taskReferenceName, null);\n            if (nextTask != null) {\n                return nextTask;\n            } else if (TaskType.DO_WHILE.name().equals(task.getType())\n                    && !task.getTaskReferenceName().equals(taskReferenceName)\n                    && task.has(taskReferenceName)) {\n                // If the task is child of Loop Task and at last position, return null.\n                return null;\n            }\n\n            if (task.has(taskReferenceName)) {\n                break;\n            }\n        }\n        if (iterator.hasNext()) {\n            return iterator.next();\n        }\n        return null;\n    }\n\n    public WorkflowTask getTaskByRefName(String taskReferenceName) {\n        return collectTasks().stream()\n                .filter(\n                        workflowTask ->\n                                workflowTask.getTaskReferenceName().equals(taskReferenceName))\n                .findFirst()\n                .orElse(null);\n    }\n\n    public List<WorkflowTask> collectTasks() {\n        List<WorkflowTask> tasks = new LinkedList<>();\n        for (WorkflowTask workflowTask : this.tasks) {\n            tasks.addAll(workflowTask.collectTasks());\n        }\n        return tasks;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        WorkflowDef that = (WorkflowDef) o;\n        return getVersion() == that.getVersion()\n                && getSchemaVersion() == that.getSchemaVersion()\n                && Objects.equals(getName(), that.getName())\n                && Objects.equals(getDescription(), that.getDescription())\n                && Objects.equals(getTasks(), that.getTasks())\n                && Objects.equals(getInputParameters(), that.getInputParameters())\n                && Objects.equals(getOutputParameters(), that.getOutputParameters())\n                && Objects.equals(getFailureWorkflow(), that.getFailureWorkflow())\n                && Objects.equals(getOwnerEmail(), that.getOwnerEmail())\n                && Objects.equals(getTimeoutSeconds(), that.getTimeoutSeconds());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                getName(),\n                getDescription(),\n                getVersion(),\n                getTasks(),\n                getInputParameters(),\n                getOutputParameters(),\n                getFailureWorkflow(),\n                getSchemaVersion(),\n                getOwnerEmail(),\n                getTimeoutSeconds());\n    }\n\n    @Override\n    public String toString() {\n        return \"WorkflowDef{\"\n                + \"name='\"\n                + name\n                + '\\''\n                + \", description='\"\n                + description\n                + '\\''\n                + \", version=\"\n                + version\n                + \", tasks=\"\n                + tasks\n                + \", inputParameters=\"\n                + inputParameters\n                + \", outputParameters=\"\n                + outputParameters\n                + \", failureWorkflow='\"\n                + failureWorkflow\n                + '\\''\n                + \", schemaVersion=\"\n                + schemaVersion\n                + \", restartable=\"\n                + restartable\n                + \", workflowStatusListenerEnabled=\"\n                + workflowStatusListenerEnabled\n                + \", timeoutSeconds=\"\n                + timeoutSeconds\n                + '}';\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/WorkflowDefSummary.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.Objects;\n\nimport javax.validation.constraints.NotEmpty;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.constraints.NoSemiColonConstraint;\n\n@ProtoMessage\npublic class WorkflowDefSummary implements Comparable<WorkflowDefSummary> {\n\n    @NotEmpty(message = \"WorkflowDef name cannot be null or empty\")\n    @ProtoField(id = 1)\n    @NoSemiColonConstraint(\n            message = \"Workflow name cannot contain the following set of characters: ':'\")\n    private String name;\n\n    @ProtoField(id = 2)\n    private int version = 1;\n\n    @ProtoField(id = 3)\n    private Long createTime;\n\n    /**\n     * @return the version\n     */\n    public int getVersion() {\n        return version;\n    }\n\n    /**\n     * @return the workflow name\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @return the createTime\n     */\n    public Long getCreateTime() {\n        return createTime;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        WorkflowDefSummary that = (WorkflowDefSummary) o;\n        return getVersion() == that.getVersion() && Objects.equals(getName(), that.getName());\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public void setVersion(int version) {\n        this.version = version;\n    }\n\n    public void setCreateTime(Long createTime) {\n        this.createTime = createTime;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(getName(), getVersion());\n    }\n\n    @Override\n    public String toString() {\n        return \"WorkflowDef{name='\" + name + \", version=\" + version + \"}\";\n    }\n\n    @Override\n    public int compareTo(WorkflowDefSummary o) {\n        int res = this.name.compareTo(o.name);\n        if (res != 0) {\n            return res;\n        }\n        res = Integer.compare(this.version, o.version);\n        return res;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/metadata/workflow/WorkflowTask.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.metadata.workflow;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.PositiveOrZero;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\n\n/**\n * This is the task definition definied as part of the {@link WorkflowDef}. The tasks definied in\n * the Workflow definition are saved as part of {@link WorkflowDef#getTasks}\n */\n@ProtoMessage\npublic class WorkflowTask {\n\n    @ProtoField(id = 1)\n    @NotEmpty(message = \"WorkflowTask name cannot be empty or null\")\n    private String name;\n\n    @ProtoField(id = 2)\n    @NotEmpty(message = \"WorkflowTask taskReferenceName name cannot be empty or null\")\n    private String taskReferenceName;\n\n    @ProtoField(id = 3)\n    private String description;\n\n    @ProtoField(id = 4)\n    private Map<String, Object> inputParameters = new HashMap<>();\n\n    @ProtoField(id = 5)\n    private String type = TaskType.SIMPLE.name();\n\n    @ProtoField(id = 6)\n    private String dynamicTaskNameParam;\n\n    @Deprecated\n    @ProtoField(id = 7)\n    private String caseValueParam;\n\n    @Deprecated\n    @ProtoField(id = 8)\n    private String caseExpression;\n\n    @ProtoField(id = 22)\n    private String scriptExpression;\n\n    @ProtoMessage(wrapper = true)\n    public static class WorkflowTaskList {\n\n        public List<WorkflowTask> getTasks() {\n            return tasks;\n        }\n\n        public void setTasks(List<WorkflowTask> tasks) {\n            this.tasks = tasks;\n        }\n\n        @ProtoField(id = 1)\n        private List<WorkflowTask> tasks;\n    }\n\n    // Populates for the tasks of the decision type\n    @ProtoField(id = 9)\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private Map<String, @Valid List<@Valid WorkflowTask>> decisionCases = new LinkedHashMap<>();\n\n    @Deprecated private String dynamicForkJoinTasksParam;\n\n    @ProtoField(id = 10)\n    private String dynamicForkTasksParam;\n\n    @ProtoField(id = 11)\n    private String dynamicForkTasksInputParamName;\n\n    @ProtoField(id = 12)\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private List<@Valid WorkflowTask> defaultCase = new LinkedList<>();\n\n    @ProtoField(id = 13)\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private List<@Valid List<@Valid WorkflowTask>> forkTasks = new LinkedList<>();\n\n    @ProtoField(id = 14)\n    @PositiveOrZero\n    private int startDelay; // No. of seconds (at-least) to wait before starting a task.\n\n    @ProtoField(id = 15)\n    @Valid\n    private SubWorkflowParams subWorkflowParam;\n\n    @ProtoField(id = 16)\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private List<String> joinOn = new LinkedList<>();\n\n    @ProtoField(id = 17)\n    private String sink;\n\n    @ProtoField(id = 18)\n    private boolean optional = false;\n\n    @ProtoField(id = 19)\n    private TaskDef taskDefinition;\n\n    @ProtoField(id = 20)\n    private Boolean rateLimited;\n\n    @ProtoField(id = 21)\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private List<String> defaultExclusiveJoinTask = new LinkedList<>();\n\n    @ProtoField(id = 23)\n    private Boolean asyncComplete = false;\n\n    @ProtoField(id = 24)\n    private String loopCondition;\n\n    @ProtoField(id = 25)\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private List<WorkflowTask> loopOver = new LinkedList<>();\n\n    @ProtoField(id = 26)\n    private Integer retryCount;\n\n    @ProtoField(id = 27)\n    private String evaluatorType;\n\n    @ProtoField(id = 28)\n    private String expression;\n\n    /**\n     * @return the name\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @param name the name to set\n     */\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * @return the taskReferenceName\n     */\n    public String getTaskReferenceName() {\n        return taskReferenceName;\n    }\n\n    /**\n     * @param taskReferenceName the taskReferenceName to set\n     */\n    public void setTaskReferenceName(String taskReferenceName) {\n        this.taskReferenceName = taskReferenceName;\n    }\n\n    /**\n     * @return the description\n     */\n    public String getDescription() {\n        return description;\n    }\n\n    /**\n     * @param description the description to set\n     */\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    /**\n     * @return the inputParameters\n     */\n    public Map<String, Object> getInputParameters() {\n        return inputParameters;\n    }\n\n    /**\n     * @param inputParameters the inputParameters to set\n     */\n    public void setInputParameters(Map<String, Object> inputParameters) {\n        this.inputParameters = inputParameters;\n    }\n\n    /**\n     * @return the type\n     */\n    public String getType() {\n        return type;\n    }\n\n    public void setWorkflowTaskType(TaskType type) {\n        this.type = type.name();\n    }\n\n    /**\n     * @param type the type to set\n     */\n    public void setType(@NotEmpty(message = \"WorkTask type cannot be null or empty\") String type) {\n        this.type = type;\n    }\n\n    /**\n     * @return the decisionCases\n     */\n    public Map<String, List<WorkflowTask>> getDecisionCases() {\n        return decisionCases;\n    }\n\n    /**\n     * @param decisionCases the decisionCases to set\n     */\n    public void setDecisionCases(Map<String, List<WorkflowTask>> decisionCases) {\n        this.decisionCases = decisionCases;\n    }\n\n    /**\n     * @return the defaultCase\n     */\n    public List<WorkflowTask> getDefaultCase() {\n        return defaultCase;\n    }\n\n    /**\n     * @param defaultCase the defaultCase to set\n     */\n    public void setDefaultCase(List<WorkflowTask> defaultCase) {\n        this.defaultCase = defaultCase;\n    }\n\n    /**\n     * @return the forkTasks\n     */\n    public List<List<WorkflowTask>> getForkTasks() {\n        return forkTasks;\n    }\n\n    /**\n     * @param forkTasks the forkTasks to set\n     */\n    public void setForkTasks(List<List<WorkflowTask>> forkTasks) {\n        this.forkTasks = forkTasks;\n    }\n\n    /**\n     * @return the startDelay in seconds\n     */\n    public int getStartDelay() {\n        return startDelay;\n    }\n\n    /**\n     * @param startDelay the startDelay to set\n     */\n    public void setStartDelay(int startDelay) {\n        this.startDelay = startDelay;\n    }\n\n    /**\n     * @return the retryCount\n     */\n    public Integer getRetryCount() {\n        return retryCount;\n    }\n\n    /**\n     * @param retryCount the retryCount to set\n     */\n    public void setRetryCount(final Integer retryCount) {\n        this.retryCount = retryCount;\n    }\n\n    /**\n     * @return the dynamicTaskNameParam\n     */\n    public String getDynamicTaskNameParam() {\n        return dynamicTaskNameParam;\n    }\n\n    /**\n     * @param dynamicTaskNameParam the dynamicTaskNameParam to set to be used by DYNAMIC tasks\n     */\n    public void setDynamicTaskNameParam(String dynamicTaskNameParam) {\n        this.dynamicTaskNameParam = dynamicTaskNameParam;\n    }\n\n    /**\n     * @deprecated Use {@link WorkflowTask#getEvaluatorType()} and {@link\n     *     WorkflowTask#getExpression()} combination.\n     * @return the caseValueParam\n     */\n    @Deprecated\n    public String getCaseValueParam() {\n        return caseValueParam;\n    }\n\n    @Deprecated\n    public String getDynamicForkJoinTasksParam() {\n        return dynamicForkJoinTasksParam;\n    }\n\n    @Deprecated\n    public void setDynamicForkJoinTasksParam(String dynamicForkJoinTasksParam) {\n        this.dynamicForkJoinTasksParam = dynamicForkJoinTasksParam;\n    }\n\n    public String getDynamicForkTasksParam() {\n        return dynamicForkTasksParam;\n    }\n\n    public void setDynamicForkTasksParam(String dynamicForkTasksParam) {\n        this.dynamicForkTasksParam = dynamicForkTasksParam;\n    }\n\n    public String getDynamicForkTasksInputParamName() {\n        return dynamicForkTasksInputParamName;\n    }\n\n    public void setDynamicForkTasksInputParamName(String dynamicForkTasksInputParamName) {\n        this.dynamicForkTasksInputParamName = dynamicForkTasksInputParamName;\n    }\n\n    /**\n     * @param caseValueParam the caseValueParam to set\n     * @deprecated Use {@link WorkflowTask#getEvaluatorType()} and {@link\n     *     WorkflowTask#getExpression()} combination.\n     */\n    @Deprecated\n    public void setCaseValueParam(String caseValueParam) {\n        this.caseValueParam = caseValueParam;\n    }\n\n    /**\n     * @return A javascript expression for decision cases. The result should be a scalar value that\n     *     is used to decide the case branches.\n     * @see #getDecisionCases()\n     * @deprecated Use {@link WorkflowTask#getEvaluatorType()} and {@link\n     *     WorkflowTask#getExpression()} combination.\n     */\n    @Deprecated\n    public String getCaseExpression() {\n        return caseExpression;\n    }\n\n    /**\n     * @param caseExpression A javascript expression for decision cases. The result should be a\n     *     scalar value that is used to decide the case branches.\n     * @deprecated Use {@link WorkflowTask#getEvaluatorType()} and {@link\n     *     WorkflowTask#getExpression()} combination.\n     */\n    @Deprecated\n    public void setCaseExpression(String caseExpression) {\n        this.caseExpression = caseExpression;\n    }\n\n    public String getScriptExpression() {\n        return scriptExpression;\n    }\n\n    public void setScriptExpression(String expression) {\n        this.scriptExpression = expression;\n    }\n\n    /**\n     * @return the subWorkflow\n     */\n    public SubWorkflowParams getSubWorkflowParam() {\n        return subWorkflowParam;\n    }\n\n    /**\n     * @param subWorkflow the subWorkflowParam to set\n     */\n    public void setSubWorkflowParam(SubWorkflowParams subWorkflow) {\n        this.subWorkflowParam = subWorkflow;\n    }\n\n    /**\n     * @return the joinOn\n     */\n    public List<String> getJoinOn() {\n        return joinOn;\n    }\n\n    /**\n     * @param joinOn the joinOn to set\n     */\n    public void setJoinOn(List<String> joinOn) {\n        this.joinOn = joinOn;\n    }\n\n    /**\n     * @return the loopCondition\n     */\n    public String getLoopCondition() {\n        return loopCondition;\n    }\n\n    /**\n     * @param loopCondition the expression to set\n     */\n    public void setLoopCondition(String loopCondition) {\n        this.loopCondition = loopCondition;\n    }\n\n    /**\n     * @return the loopOver\n     */\n    public List<WorkflowTask> getLoopOver() {\n        return loopOver;\n    }\n\n    /**\n     * @param loopOver the loopOver to set\n     */\n    public void setLoopOver(List<WorkflowTask> loopOver) {\n        this.loopOver = loopOver;\n    }\n\n    /**\n     * @return Sink value for the EVENT type of task\n     */\n    public String getSink() {\n        return sink;\n    }\n\n    /**\n     * @param sink Name of the sink\n     */\n    public void setSink(String sink) {\n        this.sink = sink;\n    }\n\n    /**\n     * @return whether wait for an external event to complete the task, for EVENT and HTTP tasks\n     */\n    public Boolean isAsyncComplete() {\n        return asyncComplete;\n    }\n\n    public void setAsyncComplete(Boolean asyncComplete) {\n        this.asyncComplete = asyncComplete;\n    }\n\n    /**\n     * @return If the task is optional. When set to true, the workflow execution continues even when\n     *     the task is in failed status.\n     */\n    public boolean isOptional() {\n        return optional;\n    }\n\n    /**\n     * @return Task definition associated to the Workflow Task\n     */\n    public TaskDef getTaskDefinition() {\n        return taskDefinition;\n    }\n\n    /**\n     * @param taskDefinition Task definition\n     */\n    public void setTaskDefinition(TaskDef taskDefinition) {\n        this.taskDefinition = taskDefinition;\n    }\n\n    /**\n     * @param optional when set to true, the task is marked as optional\n     */\n    public void setOptional(boolean optional) {\n        this.optional = optional;\n    }\n\n    public Boolean getRateLimited() {\n        return rateLimited;\n    }\n\n    public void setRateLimited(Boolean rateLimited) {\n        this.rateLimited = rateLimited;\n    }\n\n    public Boolean isRateLimited() {\n        return rateLimited != null && rateLimited;\n    }\n\n    public List<String> getDefaultExclusiveJoinTask() {\n        return defaultExclusiveJoinTask;\n    }\n\n    public void setDefaultExclusiveJoinTask(List<String> defaultExclusiveJoinTask) {\n        this.defaultExclusiveJoinTask = defaultExclusiveJoinTask;\n    }\n\n    /**\n     * @return the evaluatorType\n     */\n    public String getEvaluatorType() {\n        return evaluatorType;\n    }\n\n    /**\n     * @param evaluatorType the evaluatorType to set\n     */\n    public void setEvaluatorType(String evaluatorType) {\n        this.evaluatorType = evaluatorType;\n    }\n\n    /**\n     * @return An evaluation expression for switch cases evaluated by corresponding evaluator. The\n     *     result should be a scalar value that is used to decide the case branches.\n     * @see #getDecisionCases()\n     */\n    public String getExpression() {\n        return expression;\n    }\n\n    /**\n     * @param expression the expression to set\n     */\n    public void setExpression(String expression) {\n        this.expression = expression;\n    }\n\n    private Collection<List<WorkflowTask>> children() {\n        Collection<List<WorkflowTask>> workflowTaskLists = new LinkedList<>();\n\n        switch (TaskType.of(type)) {\n            case DECISION:\n            case SWITCH:\n                workflowTaskLists.addAll(decisionCases.values());\n                workflowTaskLists.add(defaultCase);\n                break;\n            case FORK_JOIN:\n                workflowTaskLists.addAll(forkTasks);\n                break;\n            case DO_WHILE:\n                workflowTaskLists.add(loopOver);\n                break;\n            default:\n                break;\n        }\n        return workflowTaskLists;\n    }\n\n    public List<WorkflowTask> collectTasks() {\n        List<WorkflowTask> tasks = new LinkedList<>();\n        tasks.add(this);\n        for (List<WorkflowTask> workflowTaskList : children()) {\n            for (WorkflowTask workflowTask : workflowTaskList) {\n                tasks.addAll(workflowTask.collectTasks());\n            }\n        }\n        return tasks;\n    }\n\n    public WorkflowTask next(String taskReferenceName, WorkflowTask parent) {\n        TaskType taskType = TaskType.of(type);\n\n        switch (taskType) {\n            case DO_WHILE:\n            case DECISION:\n            case SWITCH:\n                for (List<WorkflowTask> workflowTasks : children()) {\n                    Iterator<WorkflowTask> iterator = workflowTasks.iterator();\n                    while (iterator.hasNext()) {\n                        WorkflowTask task = iterator.next();\n                        if (task.getTaskReferenceName().equals(taskReferenceName)) {\n                            break;\n                        }\n                        WorkflowTask nextTask = task.next(taskReferenceName, this);\n                        if (nextTask != null) {\n                            return nextTask;\n                        }\n                        if (task.has(taskReferenceName)) {\n                            break;\n                        }\n                    }\n                    if (iterator.hasNext()) {\n                        return iterator.next();\n                    }\n                }\n                if (taskType == TaskType.DO_WHILE && this.has(taskReferenceName)) {\n                    // come here means this is DO_WHILE task and `taskReferenceName` is the last\n                    // task in\n                    // this DO_WHILE task, because DO_WHILE task need to be executed to decide\n                    // whether to\n                    // schedule next iteration, so we just return the DO_WHILE task, and then ignore\n                    // generating this task again in deciderService.getNextTask()\n                    return this;\n                }\n                break;\n            case FORK_JOIN:\n                boolean found = false;\n                for (List<WorkflowTask> workflowTasks : children()) {\n                    Iterator<WorkflowTask> iterator = workflowTasks.iterator();\n                    while (iterator.hasNext()) {\n                        WorkflowTask task = iterator.next();\n                        if (task.getTaskReferenceName().equals(taskReferenceName)) {\n                            found = true;\n                            break;\n                        }\n                        WorkflowTask nextTask = task.next(taskReferenceName, this);\n                        if (nextTask != null) {\n                            return nextTask;\n                        }\n                        if (task.has(taskReferenceName)) {\n                            break;\n                        }\n                    }\n                    if (iterator.hasNext()) {\n                        return iterator.next();\n                    }\n                    if (found && parent != null) {\n                        return parent.next(\n                                this.taskReferenceName,\n                                parent); // we need to return join task... -- get my sibling from my\n                        // parent..\n                    }\n                }\n                break;\n            case DYNAMIC:\n            case TERMINATE:\n            case SIMPLE:\n                return null;\n            default:\n                break;\n        }\n        return null;\n    }\n\n    public boolean has(String taskReferenceName) {\n        if (this.getTaskReferenceName().equals(taskReferenceName)) {\n            return true;\n        }\n\n        switch (TaskType.of(type)) {\n            case DECISION:\n            case SWITCH:\n            case DO_WHILE:\n            case FORK_JOIN:\n                for (List<WorkflowTask> childx : children()) {\n                    for (WorkflowTask child : childx) {\n                        if (child.has(taskReferenceName)) {\n                            return true;\n                        }\n                    }\n                }\n                break;\n            default:\n                break;\n        }\n        return false;\n    }\n\n    public WorkflowTask get(String taskReferenceName) {\n\n        if (this.getTaskReferenceName().equals(taskReferenceName)) {\n            return this;\n        }\n        for (List<WorkflowTask> childx : children()) {\n            for (WorkflowTask child : childx) {\n                WorkflowTask found = child.get(taskReferenceName);\n                if (found != null) {\n                    return found;\n                }\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public String toString() {\n        return name + \"/\" + taskReferenceName;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        WorkflowTask that = (WorkflowTask) o;\n        return getStartDelay() == that.getStartDelay()\n                && isOptional() == that.isOptional()\n                && Objects.equals(getName(), that.getName())\n                && Objects.equals(getTaskReferenceName(), that.getTaskReferenceName())\n                && Objects.equals(getDescription(), that.getDescription())\n                && Objects.equals(getInputParameters(), that.getInputParameters())\n                && Objects.equals(getType(), that.getType())\n                && Objects.equals(getDynamicTaskNameParam(), that.getDynamicTaskNameParam())\n                && Objects.equals(getCaseValueParam(), that.getCaseValueParam())\n                && Objects.equals(getEvaluatorType(), that.getEvaluatorType())\n                && Objects.equals(getExpression(), that.getExpression())\n                && Objects.equals(getCaseExpression(), that.getCaseExpression())\n                && Objects.equals(getDecisionCases(), that.getDecisionCases())\n                && Objects.equals(\n                        getDynamicForkJoinTasksParam(), that.getDynamicForkJoinTasksParam())\n                && Objects.equals(getDynamicForkTasksParam(), that.getDynamicForkTasksParam())\n                && Objects.equals(\n                        getDynamicForkTasksInputParamName(),\n                        that.getDynamicForkTasksInputParamName())\n                && Objects.equals(getDefaultCase(), that.getDefaultCase())\n                && Objects.equals(getForkTasks(), that.getForkTasks())\n                && Objects.equals(getSubWorkflowParam(), that.getSubWorkflowParam())\n                && Objects.equals(getJoinOn(), that.getJoinOn())\n                && Objects.equals(getSink(), that.getSink())\n                && Objects.equals(isAsyncComplete(), that.isAsyncComplete())\n                && Objects.equals(getDefaultExclusiveJoinTask(), that.getDefaultExclusiveJoinTask())\n                && Objects.equals(getRetryCount(), that.getRetryCount());\n    }\n\n    @Override\n    public int hashCode() {\n\n        return Objects.hash(\n                getName(),\n                getTaskReferenceName(),\n                getDescription(),\n                getInputParameters(),\n                getType(),\n                getDynamicTaskNameParam(),\n                getCaseValueParam(),\n                getCaseExpression(),\n                getEvaluatorType(),\n                getExpression(),\n                getDecisionCases(),\n                getDynamicForkJoinTasksParam(),\n                getDynamicForkTasksParam(),\n                getDynamicForkTasksInputParamName(),\n                getDefaultCase(),\n                getForkTasks(),\n                getStartDelay(),\n                getSubWorkflowParam(),\n                getJoinOn(),\n                getSink(),\n                isAsyncComplete(),\n                isOptional(),\n                getDefaultExclusiveJoinTask(),\n                getRetryCount());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/model/BulkResponse.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.model;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Response object to return a list of succeeded entities and a map of failed ones, including error\n * message, for the bulk request.\n */\npublic class BulkResponse {\n\n    /** Key - entityId Value - error message processing this entity */\n    private final Map<String, String> bulkErrorResults;\n\n    private final List<String> bulkSuccessfulResults;\n    private final String message = \"Bulk Request has been processed.\";\n\n    public BulkResponse() {\n        this.bulkSuccessfulResults = new ArrayList<>();\n        this.bulkErrorResults = new HashMap<>();\n    }\n\n    public List<String> getBulkSuccessfulResults() {\n        return bulkSuccessfulResults;\n    }\n\n    public Map<String, String> getBulkErrorResults() {\n        return bulkErrorResults;\n    }\n\n    public void appendSuccessResponse(String id) {\n        bulkSuccessfulResults.add(id);\n    }\n\n    public void appendFailedResponse(String id, String errorMessage) {\n        bulkErrorResults.put(id, errorMessage);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof BulkResponse)) {\n            return false;\n        }\n        BulkResponse that = (BulkResponse) o;\n        return Objects.equals(bulkSuccessfulResults, that.bulkSuccessfulResults)\n                && Objects.equals(bulkErrorResults, that.bulkErrorResults);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(bulkSuccessfulResults, bulkErrorResults, message);\n    }\n\n    @Override\n    public String toString() {\n        return \"BulkResponse{\"\n                + \"bulkSuccessfulResults=\"\n                + bulkSuccessfulResults\n                + \", bulkErrorResults=\"\n                + bulkErrorResults\n                + \", message='\"\n                + message\n                + '\\''\n                + '}';\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/run/ExternalStorageLocation.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.run;\n\n/**\n * Describes the location where the JSON payload is stored in external storage.\n *\n * <p>The location is described using the following fields:\n *\n * <ul>\n *   <li>uri: The uri of the json file in external storage.\n *   <li>path: The relative path of the file in external storage.\n * </ul>\n */\npublic class ExternalStorageLocation {\n\n    private String uri;\n    private String path;\n\n    public String getUri() {\n        return uri;\n    }\n\n    public void setUri(String uri) {\n        this.uri = uri;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    @Override\n    public String toString() {\n        return \"ExternalStorageLocation{\" + \"uri='\" + uri + '\\'' + \", path='\" + path + '\\'' + '}';\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/run/SearchResult.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.run;\n\nimport java.util.List;\n\npublic class SearchResult<T> {\n\n    private long totalHits;\n\n    private List<T> results;\n\n    public SearchResult() {}\n\n    public SearchResult(long totalHits, List<T> results) {\n        super();\n        this.totalHits = totalHits;\n        this.results = results;\n    }\n\n    /**\n     * @return the totalHits\n     */\n    public long getTotalHits() {\n        return totalHits;\n    }\n\n    /**\n     * @return the results\n     */\n    public List<T> getResults() {\n        return results;\n    }\n\n    /**\n     * @param totalHits the totalHits to set\n     */\n    public void setTotalHits(long totalHits) {\n        this.totalHits = totalHits;\n    }\n\n    /**\n     * @param results the results to set\n     */\n    public void setResults(List<T> results) {\n        this.results = results;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/run/TaskSummary.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.run;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Objects;\nimport java.util.TimeZone;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.Task.Status;\nimport com.netflix.conductor.common.utils.SummaryUtil;\n\n@ProtoMessage\npublic class TaskSummary {\n\n    /** The time should be stored as GMT */\n    private static final TimeZone GMT = TimeZone.getTimeZone(\"GMT\");\n\n    @ProtoField(id = 1)\n    private String workflowId;\n\n    @ProtoField(id = 2)\n    private String workflowType;\n\n    @ProtoField(id = 3)\n    private String correlationId;\n\n    @ProtoField(id = 4)\n    private String scheduledTime;\n\n    @ProtoField(id = 5)\n    private String startTime;\n\n    @ProtoField(id = 6)\n    private String updateTime;\n\n    @ProtoField(id = 7)\n    private String endTime;\n\n    @ProtoField(id = 8)\n    private Task.Status status;\n\n    @ProtoField(id = 9)\n    private String reasonForIncompletion;\n\n    @ProtoField(id = 10)\n    private long executionTime;\n\n    @ProtoField(id = 11)\n    private long queueWaitTime;\n\n    @ProtoField(id = 12)\n    private String taskDefName;\n\n    @ProtoField(id = 13)\n    private String taskType;\n\n    @ProtoField(id = 14)\n    private String input;\n\n    @ProtoField(id = 15)\n    private String output;\n\n    @ProtoField(id = 16)\n    private String taskId;\n\n    @ProtoField(id = 17)\n    private String externalInputPayloadStoragePath;\n\n    @ProtoField(id = 18)\n    private String externalOutputPayloadStoragePath;\n\n    @ProtoField(id = 19)\n    private int workflowPriority;\n\n    @ProtoField(id = 20)\n    private String domain;\n\n    public TaskSummary() {}\n\n    public TaskSummary(Task task) {\n\n        SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\");\n        sdf.setTimeZone(GMT);\n\n        this.taskId = task.getTaskId();\n        this.taskDefName = task.getTaskDefName();\n        this.taskType = task.getTaskType();\n        this.workflowId = task.getWorkflowInstanceId();\n        this.workflowType = task.getWorkflowType();\n        this.workflowPriority = task.getWorkflowPriority();\n        this.correlationId = task.getCorrelationId();\n        this.scheduledTime = sdf.format(new Date(task.getScheduledTime()));\n        this.startTime = sdf.format(new Date(task.getStartTime()));\n        this.updateTime = sdf.format(new Date(task.getUpdateTime()));\n        this.endTime = sdf.format(new Date(task.getEndTime()));\n        this.status = task.getStatus();\n        this.reasonForIncompletion = task.getReasonForIncompletion();\n        this.queueWaitTime = task.getQueueWaitTime();\n        this.domain = task.getDomain();\n        if (task.getInputData() != null) {\n            this.input = SummaryUtil.serializeInputOutput(task.getInputData());\n        }\n\n        if (task.getOutputData() != null) {\n            this.output = SummaryUtil.serializeInputOutput(task.getOutputData());\n        }\n\n        if (task.getEndTime() > 0) {\n            this.executionTime = task.getEndTime() - task.getStartTime();\n        }\n\n        if (StringUtils.isNotBlank(task.getExternalInputPayloadStoragePath())) {\n            this.externalInputPayloadStoragePath = task.getExternalInputPayloadStoragePath();\n        }\n        if (StringUtils.isNotBlank(task.getExternalOutputPayloadStoragePath())) {\n            this.externalOutputPayloadStoragePath = task.getExternalOutputPayloadStoragePath();\n        }\n    }\n\n    /**\n     * @return the workflowId\n     */\n    public String getWorkflowId() {\n        return workflowId;\n    }\n\n    /**\n     * @param workflowId the workflowId to set\n     */\n    public void setWorkflowId(String workflowId) {\n        this.workflowId = workflowId;\n    }\n\n    /**\n     * @return the workflowType\n     */\n    public String getWorkflowType() {\n        return workflowType;\n    }\n\n    /**\n     * @param workflowType the workflowType to set\n     */\n    public void setWorkflowType(String workflowType) {\n        this.workflowType = workflowType;\n    }\n\n    /**\n     * @return the correlationId\n     */\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    /**\n     * @param correlationId the correlationId to set\n     */\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    /**\n     * @return the scheduledTime\n     */\n    public String getScheduledTime() {\n        return scheduledTime;\n    }\n\n    /**\n     * @param scheduledTime the scheduledTime to set\n     */\n    public void setScheduledTime(String scheduledTime) {\n        this.scheduledTime = scheduledTime;\n    }\n\n    /**\n     * @return the startTime\n     */\n    public String getStartTime() {\n        return startTime;\n    }\n\n    /**\n     * @param startTime the startTime to set\n     */\n    public void setStartTime(String startTime) {\n        this.startTime = startTime;\n    }\n\n    /**\n     * @return the updateTime\n     */\n    public String getUpdateTime() {\n        return updateTime;\n    }\n\n    /**\n     * @param updateTime the updateTime to set\n     */\n    public void setUpdateTime(String updateTime) {\n        this.updateTime = updateTime;\n    }\n\n    /**\n     * @return the endTime\n     */\n    public String getEndTime() {\n        return endTime;\n    }\n\n    /**\n     * @param endTime the endTime to set\n     */\n    public void setEndTime(String endTime) {\n        this.endTime = endTime;\n    }\n\n    /**\n     * @return the status\n     */\n    public Status getStatus() {\n        return status;\n    }\n\n    /**\n     * @param status the status to set\n     */\n    public void setStatus(Status status) {\n        this.status = status;\n    }\n\n    /**\n     * @return the reasonForIncompletion\n     */\n    public String getReasonForIncompletion() {\n        return reasonForIncompletion;\n    }\n\n    /**\n     * @param reasonForIncompletion the reasonForIncompletion to set\n     */\n    public void setReasonForIncompletion(String reasonForIncompletion) {\n        this.reasonForIncompletion = reasonForIncompletion;\n    }\n\n    /**\n     * @return the executionTime\n     */\n    public long getExecutionTime() {\n        return executionTime;\n    }\n\n    /**\n     * @param executionTime the executionTime to set\n     */\n    public void setExecutionTime(long executionTime) {\n        this.executionTime = executionTime;\n    }\n\n    /**\n     * @return the queueWaitTime\n     */\n    public long getQueueWaitTime() {\n        return queueWaitTime;\n    }\n\n    /**\n     * @param queueWaitTime the queueWaitTime to set\n     */\n    public void setQueueWaitTime(long queueWaitTime) {\n        this.queueWaitTime = queueWaitTime;\n    }\n\n    /**\n     * @return the taskDefName\n     */\n    public String getTaskDefName() {\n        return taskDefName;\n    }\n\n    /**\n     * @param taskDefName the taskDefName to set\n     */\n    public void setTaskDefName(String taskDefName) {\n        this.taskDefName = taskDefName;\n    }\n\n    /**\n     * @return the taskType\n     */\n    public String getTaskType() {\n        return taskType;\n    }\n\n    /**\n     * @param taskType the taskType to set\n     */\n    public void setTaskType(String taskType) {\n        this.taskType = taskType;\n    }\n\n    /**\n     * @return input to the task\n     */\n    public String getInput() {\n        return input;\n    }\n\n    /**\n     * @param input input to the task\n     */\n    public void setInput(String input) {\n        this.input = input;\n    }\n\n    /**\n     * @return output of the task\n     */\n    public String getOutput() {\n        return output;\n    }\n\n    /**\n     * @param output Task output\n     */\n    public void setOutput(String output) {\n        this.output = output;\n    }\n\n    /**\n     * @return the taskId\n     */\n    public String getTaskId() {\n        return taskId;\n    }\n\n    /**\n     * @param taskId the taskId to set\n     */\n    public void setTaskId(String taskId) {\n        this.taskId = taskId;\n    }\n\n    /**\n     * @return the external storage path for the task input payload\n     */\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalInputPayloadStoragePath the external storage path where the task input payload\n     *     is stored\n     */\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @return the external storage path for the task output payload\n     */\n    public String getExternalOutputPayloadStoragePath() {\n        return externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalOutputPayloadStoragePath the external storage path where the task output\n     *     payload is stored\n     */\n    public void setExternalOutputPayloadStoragePath(String externalOutputPayloadStoragePath) {\n        this.externalOutputPayloadStoragePath = externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @return the priority defined on workflow\n     */\n    public int getWorkflowPriority() {\n        return workflowPriority;\n    }\n\n    /**\n     * @param workflowPriority Priority defined for workflow\n     */\n    public void setWorkflowPriority(int workflowPriority) {\n        this.workflowPriority = workflowPriority;\n    }\n\n    /**\n     * @return the domain that the task was scheduled in\n     */\n    public String getDomain() {\n        return domain;\n    }\n\n    /**\n     * @param domain The domain that the task was scheduled in\n     */\n    public void setDomain(String domain) {\n        this.domain = domain;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        TaskSummary that = (TaskSummary) o;\n        return getExecutionTime() == that.getExecutionTime()\n                && getQueueWaitTime() == that.getQueueWaitTime()\n                && getWorkflowPriority() == that.getWorkflowPriority()\n                && getWorkflowId().equals(that.getWorkflowId())\n                && getWorkflowType().equals(that.getWorkflowType())\n                && Objects.equals(getCorrelationId(), that.getCorrelationId())\n                && getScheduledTime().equals(that.getScheduledTime())\n                && Objects.equals(getStartTime(), that.getStartTime())\n                && Objects.equals(getUpdateTime(), that.getUpdateTime())\n                && Objects.equals(getEndTime(), that.getEndTime())\n                && getStatus() == that.getStatus()\n                && Objects.equals(getReasonForIncompletion(), that.getReasonForIncompletion())\n                && Objects.equals(getTaskDefName(), that.getTaskDefName())\n                && getTaskType().equals(that.getTaskType())\n                && getTaskId().equals(that.getTaskId())\n                && Objects.equals(getDomain(), that.getDomain());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                getWorkflowId(),\n                getWorkflowType(),\n                getCorrelationId(),\n                getScheduledTime(),\n                getStartTime(),\n                getUpdateTime(),\n                getEndTime(),\n                getStatus(),\n                getReasonForIncompletion(),\n                getExecutionTime(),\n                getQueueWaitTime(),\n                getTaskDefName(),\n                getTaskType(),\n                getTaskId(),\n                getWorkflowPriority(),\n                getDomain());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/run/Workflow.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.run;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.annotations.protogen.ProtoEnum;\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.metadata.Auditable;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\n\n@ProtoMessage\npublic class Workflow extends Auditable {\n\n    @ProtoEnum\n    public enum WorkflowStatus {\n        RUNNING(false, false),\n        COMPLETED(true, true),\n        FAILED(true, false),\n        TIMED_OUT(true, false),\n        TERMINATED(true, false),\n        PAUSED(false, true);\n\n        private final boolean terminal;\n\n        private final boolean successful;\n\n        WorkflowStatus(boolean terminal, boolean successful) {\n            this.terminal = terminal;\n            this.successful = successful;\n        }\n\n        public boolean isTerminal() {\n            return terminal;\n        }\n\n        public boolean isSuccessful() {\n            return successful;\n        }\n    }\n\n    @ProtoField(id = 1)\n    private WorkflowStatus status = WorkflowStatus.RUNNING;\n\n    @ProtoField(id = 2)\n    private long endTime;\n\n    @ProtoField(id = 3)\n    private String workflowId;\n\n    @ProtoField(id = 4)\n    private String parentWorkflowId;\n\n    @ProtoField(id = 5)\n    private String parentWorkflowTaskId;\n\n    @ProtoField(id = 6)\n    private List<Task> tasks = new LinkedList<>();\n\n    @ProtoField(id = 8)\n    private Map<String, Object> input = new HashMap<>();\n\n    @ProtoField(id = 9)\n    private Map<String, Object> output = new HashMap<>();\n\n    // ids 10,11 are reserved\n\n    @ProtoField(id = 12)\n    private String correlationId;\n\n    @ProtoField(id = 13)\n    private String reRunFromWorkflowId;\n\n    @ProtoField(id = 14)\n    private String reasonForIncompletion;\n\n    // id 15 is reserved\n\n    @ProtoField(id = 16)\n    private String event;\n\n    @ProtoField(id = 17)\n    private Map<String, String> taskToDomain = new HashMap<>();\n\n    @ProtoField(id = 18)\n    private Set<String> failedReferenceTaskNames = new HashSet<>();\n\n    @ProtoField(id = 19)\n    private WorkflowDef workflowDefinition;\n\n    @ProtoField(id = 20)\n    private String externalInputPayloadStoragePath;\n\n    @ProtoField(id = 21)\n    private String externalOutputPayloadStoragePath;\n\n    @ProtoField(id = 22)\n    @Min(value = 0, message = \"workflow priority: ${validatedValue} should be minimum {value}\")\n    @Max(value = 99, message = \"workflow priority: ${validatedValue} should be maximum {value}\")\n    private int priority;\n\n    @ProtoField(id = 23)\n    private Map<String, Object> variables = new HashMap<>();\n\n    @ProtoField(id = 24)\n    private long lastRetriedTime;\n\n    @ProtoField(id = 25)\n    private Set<String> failedTaskNames = new HashSet<>();\n\n    public Workflow() {}\n\n    /**\n     * @return the status\n     */\n    public WorkflowStatus getStatus() {\n        return status;\n    }\n\n    /**\n     * @param status the status to set\n     */\n    public void setStatus(WorkflowStatus status) {\n        this.status = status;\n    }\n\n    /**\n     * @return the startTime\n     */\n    public long getStartTime() {\n        return getCreateTime();\n    }\n\n    /**\n     * @param startTime the startTime to set\n     */\n    public void setStartTime(long startTime) {\n        this.setCreateTime(startTime);\n    }\n\n    /**\n     * @return the endTime\n     */\n    public long getEndTime() {\n        return endTime;\n    }\n\n    /**\n     * @param endTime the endTime to set\n     */\n    public void setEndTime(long endTime) {\n        this.endTime = endTime;\n    }\n\n    /**\n     * @return the workflowId\n     */\n    public String getWorkflowId() {\n        return workflowId;\n    }\n\n    /**\n     * @param workflowId the workflowId to set\n     */\n    public void setWorkflowId(String workflowId) {\n        this.workflowId = workflowId;\n    }\n\n    /**\n     * @return the tasks which are scheduled, in progress or completed.\n     */\n    public List<Task> getTasks() {\n        return tasks;\n    }\n\n    /**\n     * @param tasks the tasks to set\n     */\n    public void setTasks(List<Task> tasks) {\n        this.tasks = tasks;\n    }\n\n    /**\n     * @return the input\n     */\n    public Map<String, Object> getInput() {\n        return input;\n    }\n\n    /**\n     * @param input the input to set\n     */\n    public void setInput(Map<String, Object> input) {\n        if (input == null) {\n            input = new HashMap<>();\n        }\n        this.input = input;\n    }\n\n    /**\n     * @return the task to domain map\n     */\n    public Map<String, String> getTaskToDomain() {\n        return taskToDomain;\n    }\n\n    /**\n     * @param taskToDomain the task to domain map\n     */\n    public void setTaskToDomain(Map<String, String> taskToDomain) {\n        this.taskToDomain = taskToDomain;\n    }\n\n    /**\n     * @return the output\n     */\n    public Map<String, Object> getOutput() {\n        return output;\n    }\n\n    /**\n     * @param output the output to set\n     */\n    public void setOutput(Map<String, Object> output) {\n        if (output == null) {\n            output = new HashMap<>();\n        }\n        this.output = output;\n    }\n\n    /**\n     * @return The correlation id used when starting the workflow\n     */\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    /**\n     * @param correlationId the correlation id\n     */\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    public String getReRunFromWorkflowId() {\n        return reRunFromWorkflowId;\n    }\n\n    public void setReRunFromWorkflowId(String reRunFromWorkflowId) {\n        this.reRunFromWorkflowId = reRunFromWorkflowId;\n    }\n\n    public String getReasonForIncompletion() {\n        return reasonForIncompletion;\n    }\n\n    public void setReasonForIncompletion(String reasonForIncompletion) {\n        this.reasonForIncompletion = reasonForIncompletion;\n    }\n\n    /**\n     * @return the parentWorkflowId\n     */\n    public String getParentWorkflowId() {\n        return parentWorkflowId;\n    }\n\n    /**\n     * @param parentWorkflowId the parentWorkflowId to set\n     */\n    public void setParentWorkflowId(String parentWorkflowId) {\n        this.parentWorkflowId = parentWorkflowId;\n    }\n\n    /**\n     * @return the parentWorkflowTaskId\n     */\n    public String getParentWorkflowTaskId() {\n        return parentWorkflowTaskId;\n    }\n\n    /**\n     * @param parentWorkflowTaskId the parentWorkflowTaskId to set\n     */\n    public void setParentWorkflowTaskId(String parentWorkflowTaskId) {\n        this.parentWorkflowTaskId = parentWorkflowTaskId;\n    }\n\n    /**\n     * @return Name of the event that started the workflow\n     */\n    public String getEvent() {\n        return event;\n    }\n\n    /**\n     * @param event Name of the event that started the workflow\n     */\n    public void setEvent(String event) {\n        this.event = event;\n    }\n\n    public Set<String> getFailedReferenceTaskNames() {\n        return failedReferenceTaskNames;\n    }\n\n    public void setFailedReferenceTaskNames(Set<String> failedReferenceTaskNames) {\n        this.failedReferenceTaskNames = failedReferenceTaskNames;\n    }\n\n    public Set<String> getFailedTaskNames() {\n        return failedTaskNames;\n    }\n\n    public void setFailedTaskNames(Set<String> failedTaskNames) {\n        this.failedTaskNames = failedTaskNames;\n    }\n\n    public WorkflowDef getWorkflowDefinition() {\n        return workflowDefinition;\n    }\n\n    public void setWorkflowDefinition(WorkflowDef workflowDefinition) {\n        this.workflowDefinition = workflowDefinition;\n    }\n\n    /**\n     * @return the external storage path of the workflow input payload\n     */\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalInputPayloadStoragePath the external storage path where the workflow input\n     *     payload is stored\n     */\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @return the external storage path of the workflow output payload\n     */\n    public String getExternalOutputPayloadStoragePath() {\n        return externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @return the priority to define on tasks\n     */\n    public int getPriority() {\n        return priority;\n    }\n\n    /**\n     * @param priority priority of tasks (between 0 and 99)\n     */\n    public void setPriority(int priority) {\n        if (priority < 0 || priority > 99) {\n            throw new IllegalArgumentException(\"priority MUST be between 0 and 99 (inclusive)\");\n        }\n        this.priority = priority;\n    }\n\n    /**\n     * Convenience method for accessing the workflow definition name.\n     *\n     * @return the workflow definition name.\n     */\n    public String getWorkflowName() {\n        if (workflowDefinition == null) {\n            throw new NullPointerException(\"Workflow definition is null\");\n        }\n        return workflowDefinition.getName();\n    }\n\n    /**\n     * Convenience method for accessing the workflow definition version.\n     *\n     * @return the workflow definition version.\n     */\n    public int getWorkflowVersion() {\n        if (workflowDefinition == null) {\n            throw new NullPointerException(\"Workflow definition is null\");\n        }\n        return workflowDefinition.getVersion();\n    }\n\n    /**\n     * @param externalOutputPayloadStoragePath the external storage path where the workflow output\n     *     payload is stored\n     */\n    public void setExternalOutputPayloadStoragePath(String externalOutputPayloadStoragePath) {\n        this.externalOutputPayloadStoragePath = externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @return the global workflow variables\n     */\n    public Map<String, Object> getVariables() {\n        return variables;\n    }\n\n    /**\n     * @param variables the set of global workflow variables to set\n     */\n    public void setVariables(Map<String, Object> variables) {\n        this.variables = variables;\n    }\n\n    /**\n     * Captures the last time the workflow was retried\n     *\n     * @return the last retried time of the workflow\n     */\n    public long getLastRetriedTime() {\n        return lastRetriedTime;\n    }\n\n    /**\n     * @param lastRetriedTime time in milliseconds when the workflow is retried\n     */\n    public void setLastRetriedTime(long lastRetriedTime) {\n        this.lastRetriedTime = lastRetriedTime;\n    }\n\n    public boolean hasParent() {\n        return StringUtils.isNotEmpty(parentWorkflowId);\n    }\n\n    public Task getTaskByRefName(String refName) {\n        if (refName == null) {\n            throw new RuntimeException(\n                    \"refName passed is null.  Check the workflow execution.  For dynamic tasks, make sure referenceTaskName is set to a not null value\");\n        }\n        LinkedList<Task> found = new LinkedList<>();\n        for (Task t : tasks) {\n            if (t.getReferenceTaskName() == null) {\n                throw new RuntimeException(\n                        \"Task \"\n                                + t.getTaskDefName()\n                                + \", seq=\"\n                                + t.getSeq()\n                                + \" does not have reference name specified.\");\n            }\n            if (t.getReferenceTaskName().equals(refName)) {\n                found.add(t);\n            }\n        }\n        if (found.isEmpty()) {\n            return null;\n        }\n        return found.getLast();\n    }\n\n    /**\n     * @return a deep copy of the workflow instance\n     */\n    public Workflow copy() {\n        Workflow copy = new Workflow();\n        copy.setInput(input);\n        copy.setOutput(output);\n        copy.setStatus(status);\n        copy.setWorkflowId(workflowId);\n        copy.setParentWorkflowId(parentWorkflowId);\n        copy.setParentWorkflowTaskId(parentWorkflowTaskId);\n        copy.setReRunFromWorkflowId(reRunFromWorkflowId);\n        copy.setCorrelationId(correlationId);\n        copy.setEvent(event);\n        copy.setReasonForIncompletion(reasonForIncompletion);\n        copy.setWorkflowDefinition(workflowDefinition);\n        copy.setPriority(priority);\n        copy.setTasks(tasks.stream().map(Task::deepCopy).collect(Collectors.toList()));\n        copy.setVariables(variables);\n        copy.setEndTime(endTime);\n        copy.setLastRetriedTime(lastRetriedTime);\n        copy.setTaskToDomain(taskToDomain);\n        copy.setFailedReferenceTaskNames(failedReferenceTaskNames);\n        copy.setFailedTaskNames(failedTaskNames);\n        copy.setExternalInputPayloadStoragePath(externalInputPayloadStoragePath);\n        copy.setExternalOutputPayloadStoragePath(externalOutputPayloadStoragePath);\n        return copy;\n    }\n\n    @Override\n    public String toString() {\n        String name = workflowDefinition != null ? workflowDefinition.getName() : null;\n        Integer version = workflowDefinition != null ? workflowDefinition.getVersion() : null;\n        return String.format(\"%s.%s/%s.%s\", name, version, workflowId, status);\n    }\n\n    /**\n     * A string representation of all relevant fields that identify this workflow. Intended for use\n     * in log and other system generated messages.\n     */\n    public String toShortString() {\n        String name = workflowDefinition != null ? workflowDefinition.getName() : null;\n        Integer version = workflowDefinition != null ? workflowDefinition.getVersion() : null;\n        return String.format(\"%s.%s/%s\", name, version, workflowId);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Workflow workflow = (Workflow) o;\n        return getEndTime() == workflow.getEndTime()\n                && getWorkflowVersion() == workflow.getWorkflowVersion()\n                && getStatus() == workflow.getStatus()\n                && Objects.equals(getWorkflowId(), workflow.getWorkflowId())\n                && Objects.equals(getParentWorkflowId(), workflow.getParentWorkflowId())\n                && Objects.equals(getParentWorkflowTaskId(), workflow.getParentWorkflowTaskId())\n                && Objects.equals(getTasks(), workflow.getTasks())\n                && Objects.equals(getInput(), workflow.getInput())\n                && Objects.equals(getOutput(), workflow.getOutput())\n                && Objects.equals(getWorkflowName(), workflow.getWorkflowName())\n                && Objects.equals(getCorrelationId(), workflow.getCorrelationId())\n                && Objects.equals(getReRunFromWorkflowId(), workflow.getReRunFromWorkflowId())\n                && Objects.equals(getReasonForIncompletion(), workflow.getReasonForIncompletion())\n                && Objects.equals(getEvent(), workflow.getEvent())\n                && Objects.equals(getTaskToDomain(), workflow.getTaskToDomain())\n                && Objects.equals(\n                        getFailedReferenceTaskNames(), workflow.getFailedReferenceTaskNames())\n                && Objects.equals(getFailedTaskNames(), workflow.getFailedTaskNames())\n                && Objects.equals(\n                        getExternalInputPayloadStoragePath(),\n                        workflow.getExternalInputPayloadStoragePath())\n                && Objects.equals(\n                        getExternalOutputPayloadStoragePath(),\n                        workflow.getExternalOutputPayloadStoragePath())\n                && Objects.equals(getPriority(), workflow.getPriority())\n                && Objects.equals(getWorkflowDefinition(), workflow.getWorkflowDefinition())\n                && Objects.equals(getVariables(), workflow.getVariables())\n                && Objects.equals(getLastRetriedTime(), workflow.getLastRetriedTime());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                getStatus(),\n                getEndTime(),\n                getWorkflowId(),\n                getParentWorkflowId(),\n                getParentWorkflowTaskId(),\n                getTasks(),\n                getInput(),\n                getOutput(),\n                getWorkflowName(),\n                getWorkflowVersion(),\n                getCorrelationId(),\n                getReRunFromWorkflowId(),\n                getReasonForIncompletion(),\n                getEvent(),\n                getTaskToDomain(),\n                getFailedReferenceTaskNames(),\n                getFailedTaskNames(),\n                getWorkflowDefinition(),\n                getExternalInputPayloadStoragePath(),\n                getExternalOutputPayloadStoragePath(),\n                getPriority(),\n                getVariables(),\n                getLastRetriedTime());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/run/WorkflowSummary.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.run;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.HashSet;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.annotations.protogen.ProtoField;\nimport com.netflix.conductor.annotations.protogen.ProtoMessage;\nimport com.netflix.conductor.common.run.Workflow.WorkflowStatus;\nimport com.netflix.conductor.common.utils.SummaryUtil;\n\n/** Captures workflow summary info to be indexed in Elastic Search. */\n@ProtoMessage\npublic class WorkflowSummary {\n\n    /** The time should be stored as GMT */\n    private static final TimeZone GMT = TimeZone.getTimeZone(\"GMT\");\n\n    @ProtoField(id = 1)\n    private String workflowType;\n\n    @ProtoField(id = 2)\n    private int version;\n\n    @ProtoField(id = 3)\n    private String workflowId;\n\n    @ProtoField(id = 4)\n    private String correlationId;\n\n    @ProtoField(id = 5)\n    private String startTime;\n\n    @ProtoField(id = 6)\n    private String updateTime;\n\n    @ProtoField(id = 7)\n    private String endTime;\n\n    @ProtoField(id = 8)\n    private Workflow.WorkflowStatus status;\n\n    @ProtoField(id = 9)\n    private String input;\n\n    @ProtoField(id = 10)\n    private String output;\n\n    @ProtoField(id = 11)\n    private String reasonForIncompletion;\n\n    @ProtoField(id = 12)\n    private long executionTime;\n\n    @ProtoField(id = 13)\n    private String event;\n\n    @ProtoField(id = 14)\n    private String failedReferenceTaskNames = \"\";\n\n    @ProtoField(id = 15)\n    private String externalInputPayloadStoragePath;\n\n    @ProtoField(id = 16)\n    private String externalOutputPayloadStoragePath;\n\n    @ProtoField(id = 17)\n    private int priority;\n\n    @ProtoField(id = 18)\n    private Set<String> failedTaskNames = new HashSet<>();\n\n    public WorkflowSummary() {}\n\n    public WorkflowSummary(Workflow workflow) {\n\n        SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\");\n        sdf.setTimeZone(GMT);\n\n        this.workflowType = workflow.getWorkflowName();\n        this.version = workflow.getWorkflowVersion();\n        this.workflowId = workflow.getWorkflowId();\n        this.priority = workflow.getPriority();\n        this.correlationId = workflow.getCorrelationId();\n        if (workflow.getCreateTime() != null) {\n            this.startTime = sdf.format(new Date(workflow.getCreateTime()));\n        }\n        if (workflow.getEndTime() > 0) {\n            this.endTime = sdf.format(new Date(workflow.getEndTime()));\n        }\n        if (workflow.getUpdateTime() != null) {\n            this.updateTime = sdf.format(new Date(workflow.getUpdateTime()));\n        }\n        this.status = workflow.getStatus();\n        if (workflow.getInput() != null) {\n            this.input = SummaryUtil.serializeInputOutput(workflow.getInput());\n        }\n        if (workflow.getOutput() != null) {\n            this.output = SummaryUtil.serializeInputOutput(workflow.getOutput());\n        }\n        this.reasonForIncompletion = workflow.getReasonForIncompletion();\n        if (workflow.getEndTime() > 0) {\n            this.executionTime = workflow.getEndTime() - workflow.getStartTime();\n        }\n        this.event = workflow.getEvent();\n        this.failedReferenceTaskNames =\n                workflow.getFailedReferenceTaskNames().stream().collect(Collectors.joining(\",\"));\n        this.failedTaskNames = workflow.getFailedTaskNames();\n        if (StringUtils.isNotBlank(workflow.getExternalInputPayloadStoragePath())) {\n            this.externalInputPayloadStoragePath = workflow.getExternalInputPayloadStoragePath();\n        }\n        if (StringUtils.isNotBlank(workflow.getExternalOutputPayloadStoragePath())) {\n            this.externalOutputPayloadStoragePath = workflow.getExternalOutputPayloadStoragePath();\n        }\n    }\n\n    /**\n     * @return the workflowType\n     */\n    public String getWorkflowType() {\n        return workflowType;\n    }\n\n    /**\n     * @return the version\n     */\n    public int getVersion() {\n        return version;\n    }\n\n    /**\n     * @return the workflowId\n     */\n    public String getWorkflowId() {\n        return workflowId;\n    }\n\n    /**\n     * @return the correlationId\n     */\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    /**\n     * @return the startTime\n     */\n    public String getStartTime() {\n        return startTime;\n    }\n\n    /**\n     * @return the endTime\n     */\n    public String getEndTime() {\n        return endTime;\n    }\n\n    /**\n     * @return the status\n     */\n    public WorkflowStatus getStatus() {\n        return status;\n    }\n\n    /**\n     * @return the input\n     */\n    public String getInput() {\n        return input;\n    }\n\n    public long getInputSize() {\n        return input != null ? input.length() : 0;\n    }\n\n    /**\n     * @return the output\n     */\n    public String getOutput() {\n        return output;\n    }\n\n    public long getOutputSize() {\n        return output != null ? output.length() : 0;\n    }\n\n    /**\n     * @return the reasonForIncompletion\n     */\n    public String getReasonForIncompletion() {\n        return reasonForIncompletion;\n    }\n\n    /**\n     * @return the executionTime\n     */\n    public long getExecutionTime() {\n        return executionTime;\n    }\n\n    /**\n     * @return the updateTime\n     */\n    public String getUpdateTime() {\n        return updateTime;\n    }\n\n    /**\n     * @return The event\n     */\n    public String getEvent() {\n        return event;\n    }\n\n    /**\n     * @param event The event\n     */\n    public void setEvent(String event) {\n        this.event = event;\n    }\n\n    public String getFailedReferenceTaskNames() {\n        return failedReferenceTaskNames;\n    }\n\n    public void setFailedReferenceTaskNames(String failedReferenceTaskNames) {\n        this.failedReferenceTaskNames = failedReferenceTaskNames;\n    }\n\n    public Set<String> getFailedTaskNames() {\n        return failedTaskNames;\n    }\n\n    public void setFailedTaskNames(Set<String> failedTaskNames) {\n        this.failedTaskNames = failedTaskNames;\n    }\n\n    public void setWorkflowType(String workflowType) {\n        this.workflowType = workflowType;\n    }\n\n    public void setVersion(int version) {\n        this.version = version;\n    }\n\n    public void setWorkflowId(String workflowId) {\n        this.workflowId = workflowId;\n    }\n\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    public void setStartTime(String startTime) {\n        this.startTime = startTime;\n    }\n\n    public void setUpdateTime(String updateTime) {\n        this.updateTime = updateTime;\n    }\n\n    public void setEndTime(String endTime) {\n        this.endTime = endTime;\n    }\n\n    public void setStatus(WorkflowStatus status) {\n        this.status = status;\n    }\n\n    public void setInput(String input) {\n        this.input = input;\n    }\n\n    public void setOutput(String output) {\n        this.output = output;\n    }\n\n    public void setReasonForIncompletion(String reasonForIncompletion) {\n        this.reasonForIncompletion = reasonForIncompletion;\n    }\n\n    public void setExecutionTime(long executionTime) {\n        this.executionTime = executionTime;\n    }\n\n    /**\n     * @return the external storage path of the workflow input payload\n     */\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalInputPayloadStoragePath the external storage path where the workflow input\n     *     payload is stored\n     */\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    /**\n     * @return the external storage path of the workflow output payload\n     */\n    public String getExternalOutputPayloadStoragePath() {\n        return externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @param externalOutputPayloadStoragePath the external storage path where the workflow output\n     *     payload is stored\n     */\n    public void setExternalOutputPayloadStoragePath(String externalOutputPayloadStoragePath) {\n        this.externalOutputPayloadStoragePath = externalOutputPayloadStoragePath;\n    }\n\n    /**\n     * @return the priority to define on tasks\n     */\n    public int getPriority() {\n        return priority;\n    }\n\n    /**\n     * @param priority priority of tasks (between 0 and 99)\n     */\n    public void setPriority(int priority) {\n        this.priority = priority;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        WorkflowSummary that = (WorkflowSummary) o;\n        return getVersion() == that.getVersion()\n                && getExecutionTime() == that.getExecutionTime()\n                && getPriority() == that.getPriority()\n                && getWorkflowType().equals(that.getWorkflowType())\n                && getWorkflowId().equals(that.getWorkflowId())\n                && Objects.equals(getCorrelationId(), that.getCorrelationId())\n                && StringUtils.equals(getStartTime(), that.getStartTime())\n                && StringUtils.equals(getUpdateTime(), that.getUpdateTime())\n                && StringUtils.equals(getEndTime(), that.getEndTime())\n                && getStatus() == that.getStatus()\n                && Objects.equals(getReasonForIncompletion(), that.getReasonForIncompletion())\n                && Objects.equals(getEvent(), that.getEvent());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                getWorkflowType(),\n                getVersion(),\n                getWorkflowId(),\n                getCorrelationId(),\n                getStartTime(),\n                getUpdateTime(),\n                getEndTime(),\n                getStatus(),\n                getReasonForIncompletion(),\n                getExecutionTime(),\n                getEvent(),\n                getPriority());\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/run/WorkflowTestRequest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.run;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\n\npublic class WorkflowTestRequest extends StartWorkflowRequest {\n\n    // Map of task reference name to mock output for the task\n    private Map<String, List<TaskMock>> taskRefToMockOutput = new HashMap<>();\n\n    // If there are sub-workflows inside the workflow\n    // The map of task reference name to the mock for the sub-workflow\n    private Map<String, WorkflowTestRequest> subWorkflowTestRequest = new HashMap<>();\n\n    public static class TaskMock {\n        private TaskResult.Status status = TaskResult.Status.COMPLETED;\n        private Map<String, Object> output;\n        private long executionTime; // Time in millis for the execution of the task.  Useful for\n        // simulating timeout conditions\n        private long queueWaitTime; // Time in millis for the wait time in the queue.\n\n        public TaskMock() {}\n\n        public TaskMock(TaskResult.Status status, Map<String, Object> output) {\n            this.status = status;\n            this.output = output;\n        }\n\n        public TaskResult.Status getStatus() {\n            return status;\n        }\n\n        public void setStatus(TaskResult.Status status) {\n            this.status = status;\n        }\n\n        public Map<String, Object> getOutput() {\n            return output;\n        }\n\n        public void setOutput(Map<String, Object> output) {\n            this.output = output;\n        }\n\n        public long getExecutionTime() {\n            return executionTime;\n        }\n\n        public void setExecutionTime(long executionTime) {\n            this.executionTime = executionTime;\n        }\n\n        public long getQueueWaitTime() {\n            return queueWaitTime;\n        }\n\n        public void setQueueWaitTime(long queueWaitTime) {\n            this.queueWaitTime = queueWaitTime;\n        }\n    }\n\n    public Map<String, List<TaskMock>> getTaskRefToMockOutput() {\n        return taskRefToMockOutput;\n    }\n\n    public void setTaskRefToMockOutput(Map<String, List<TaskMock>> taskRefToMockOutput) {\n        this.taskRefToMockOutput = taskRefToMockOutput;\n    }\n\n    public Map<String, WorkflowTestRequest> getSubWorkflowTestRequest() {\n        return subWorkflowTestRequest;\n    }\n\n    public void setSubWorkflowTestRequest(Map<String, WorkflowTestRequest> subWorkflowTestRequest) {\n        this.subWorkflowTestRequest = subWorkflowTestRequest;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/utils/ConstraintParamUtil.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.utils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.utils.EnvUtils.SystemParameters;\n\n@SuppressWarnings(\"unchecked\")\npublic class ConstraintParamUtil {\n\n    /**\n     * Validates inputParam and returns a list of errors if input is not valid.\n     *\n     * @param input {@link Map} of inputParameters\n     * @param taskName TaskName of inputParameters\n     * @param workflow WorkflowDef\n     * @return {@link List} of error strings.\n     */\n    public static List<String> validateInputParam(\n            Map<String, Object> input, String taskName, WorkflowDef workflow) {\n        ArrayList<String> errorList = new ArrayList<>();\n\n        for (Entry<String, Object> e : input.entrySet()) {\n            Object value = e.getValue();\n            if (value instanceof String) {\n                errorList.addAll(\n                        extractParamPathComponentsFromString(\n                                e.getKey(), value.toString(), taskName, workflow));\n            } else if (value instanceof Map) {\n                // recursive call\n                errorList.addAll(\n                        validateInputParam((Map<String, Object>) value, taskName, workflow));\n            } else if (value instanceof List) {\n                errorList.addAll(\n                        extractListInputParam(e.getKey(), (List<?>) value, taskName, workflow));\n            } else {\n                e.setValue(value);\n            }\n        }\n        return errorList;\n    }\n\n    private static List<String> extractListInputParam(\n            String key, List<?> values, String taskName, WorkflowDef workflow) {\n        ArrayList<String> errorList = new ArrayList<>();\n        for (Object listVal : values) {\n            if (listVal instanceof String) {\n                errorList.addAll(\n                        extractParamPathComponentsFromString(\n                                key, listVal.toString(), taskName, workflow));\n            } else if (listVal instanceof Map) {\n                errorList.addAll(\n                        validateInputParam((Map<String, Object>) listVal, taskName, workflow));\n            } else if (listVal instanceof List) {\n                errorList.addAll(extractListInputParam(key, (List<?>) listVal, taskName, workflow));\n            }\n        }\n        return errorList;\n    }\n\n    private static List<String> extractParamPathComponentsFromString(\n            String key, String value, String taskName, WorkflowDef workflow) {\n        ArrayList<String> errorList = new ArrayList<>();\n\n        if (value == null) {\n            String message = String.format(\"key: %s input parameter value: is null\", key);\n            errorList.add(message);\n            return errorList;\n        }\n\n        String[] values = value.split(\"(?=(?<!\\\\$)\\\\$\\\\{)|(?<=\\\\})\");\n\n        for (String s : values) {\n            if (s.startsWith(\"${\") && s.endsWith(\"}\")) {\n                String paramPath = s.substring(2, s.length() - 1);\n\n                if (StringUtils.containsWhitespace(paramPath)) {\n                    String message =\n                            String.format(\n                                    \"key: %s input parameter value: %s is not valid\",\n                                    key, paramPath);\n                    errorList.add(message);\n                } else if (EnvUtils.isEnvironmentVariable(paramPath)) {\n                    // if it one of the predefined enums skip validation\n                    boolean isPredefinedEnum = false;\n\n                    for (SystemParameters systemParameters : SystemParameters.values()) {\n                        if (systemParameters.name().equals(paramPath)) {\n                            isPredefinedEnum = true;\n                            break;\n                        }\n                    }\n\n                    if (!isPredefinedEnum) {\n                        String sysValue = EnvUtils.getSystemParametersValue(paramPath, \"\");\n                        if (sysValue == null) {\n                            String errorMessage =\n                                    String.format(\n                                            \"environment variable: %s for given task: %s\"\n                                                    + \" input value: %s\"\n                                                    + \" of input parameter: %s is not valid\",\n                                            paramPath, taskName, key, value);\n                            errorList.add(errorMessage);\n                        }\n                    }\n                } // workflow, or task reference name\n                else {\n                    String[] components = paramPath.split(\"\\\\.\");\n                    if (!\"workflow\".equals(components[0])) {\n                        WorkflowTask task = workflow.getTaskByRefName(components[0]);\n                        if (task == null) {\n                            String message =\n                                    String.format(\n                                            \"taskReferenceName: %s for given task: %s input value: %s of input\"\n                                                    + \" parameter: %s\"\n                                                    + \" is not defined in workflow definition.\",\n                                            components[0], taskName, key, value);\n                            errorList.add(message);\n                        }\n                    }\n                }\n            }\n        }\n        return errorList;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/utils/EnvUtils.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.utils;\n\nimport java.util.Optional;\n\npublic class EnvUtils {\n\n    public enum SystemParameters {\n        CPEWF_TASK_ID,\n        NETFLIX_ENV,\n        NETFLIX_STACK\n    }\n\n    public static boolean isEnvironmentVariable(String test) {\n        for (SystemParameters c : SystemParameters.values()) {\n            if (c.name().equals(test)) {\n                return true;\n            }\n        }\n        String value =\n                Optional.ofNullable(System.getProperty(test)).orElseGet(() -> System.getenv(test));\n        return value != null;\n    }\n\n    public static String getSystemParametersValue(String sysParam, String taskId) {\n        if (\"CPEWF_TASK_ID\".equals(sysParam)) {\n            return taskId;\n        }\n\n        String value = System.getenv(sysParam);\n        if (value == null) {\n            value = System.getProperty(sysParam);\n        }\n        return value;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/utils/ExternalPayloadStorage.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.utils;\n\nimport java.io.InputStream;\n\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\n\n/**\n * Interface used to externalize the storage of large JSON payloads in workflow and task\n * input/output\n */\npublic interface ExternalPayloadStorage {\n\n    enum Operation {\n        READ,\n        WRITE\n    }\n\n    enum PayloadType {\n        WORKFLOW_INPUT,\n        WORKFLOW_OUTPUT,\n        TASK_INPUT,\n        TASK_OUTPUT\n    }\n\n    /**\n     * Obtain a uri used to store/access a json payload in external storage.\n     *\n     * @param operation the type of {@link Operation} to be performed with the uri\n     * @param payloadType the {@link PayloadType} that is being accessed at the uri\n     * @param path (optional) the relative path for which the external storage location object is to\n     *     be populated. If path is not specified, it will be computed and populated.\n     * @return a {@link ExternalStorageLocation} object which contains the uri and the path for the\n     *     json payload\n     */\n    ExternalStorageLocation getLocation(Operation operation, PayloadType payloadType, String path);\n\n    /**\n     * Obtain an uri used to store/access a json payload in external storage with deduplication of\n     * data based on payloadBytes digest.\n     *\n     * @param operation the type of {@link Operation} to be performed with the uri\n     * @param payloadType the {@link PayloadType} that is being accessed at the uri\n     * @param path (optional) the relative path for which the external storage location object is to\n     *     be populated. If path is not specified, it will be computed and populated.\n     * @param payloadBytes for calculating digest which is used for objectKey\n     * @return a {@link ExternalStorageLocation} object which contains the uri and the path for the\n     *     json payload\n     */\n    default ExternalStorageLocation getLocation(\n            Operation operation, PayloadType payloadType, String path, byte[] payloadBytes) {\n        return getLocation(operation, payloadType, path);\n    }\n\n    /**\n     * Upload a json payload to the specified external storage location.\n     *\n     * @param path the location to which the object is to be uploaded\n     * @param payload an {@link InputStream} containing the json payload which is to be uploaded\n     * @param payloadSize the size of the json payload in bytes\n     */\n    void upload(String path, InputStream payload, long payloadSize);\n\n    /**\n     * Download the json payload from the specified external storage location.\n     *\n     * @param path the location from where the object is to be downloaded\n     * @return an {@link InputStream} of the json payload at the specified location\n     */\n    InputStream download(String path);\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/utils/SummaryUtil.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.utils;\n\nimport java.util.Map;\n\nimport javax.annotation.PostConstruct;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n@Component\npublic class SummaryUtil {\n\n    private static final Logger logger = LoggerFactory.getLogger(SummaryUtil.class);\n    private static final ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    private static boolean isSummaryInputOutputJsonSerializationEnabled;\n\n    @Value(\"${conductor.app.summary-input-output-json-serialization.enabled:false}\")\n    private boolean isJsonSerializationEnabled;\n\n    @PostConstruct\n    public void init() {\n        isSummaryInputOutputJsonSerializationEnabled = isJsonSerializationEnabled;\n    }\n\n    /**\n     * Serializes the Workflow or Task's Input/Output object by Java's toString (default), or by a\n     * Json ObjectMapper (@see Configuration.isSummaryInputOutputJsonSerializationEnabled)\n     *\n     * @param object the Input or Output Object to serialize\n     * @return the serialized string of the Input or Output object\n     */\n    public static String serializeInputOutput(Map<String, Object> object) {\n        if (!isSummaryInputOutputJsonSerializationEnabled) {\n            return object.toString();\n        }\n\n        try {\n            return objectMapper.writeValueAsString(object);\n        } catch (JsonProcessingException e) {\n            logger.error(\n                    \"The provided value ({}) could not be serialized as Json\",\n                    object.toString(),\n                    e);\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/utils/TaskUtils.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.utils;\n\npublic class TaskUtils {\n\n    private static final String LOOP_TASK_DELIMITER = \"__\";\n\n    public static String appendIteration(String name, int iteration) {\n        return name + LOOP_TASK_DELIMITER + iteration;\n    }\n\n    public static String getLoopOverTaskRefNameSuffix(int iteration) {\n        return LOOP_TASK_DELIMITER + iteration;\n    }\n\n    public static String removeIterationFromTaskRefName(String referenceTaskName) {\n        String[] tokens = referenceTaskName.split(TaskUtils.LOOP_TASK_DELIMITER);\n        return tokens.length > 0 ? tokens[0] : referenceTaskName;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/validation/ErrorResponse.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.validation;\n\nimport java.util.List;\n\npublic class ErrorResponse {\n\n    private int status;\n    private String code;\n    private String message;\n    private String instance;\n    private boolean retryable;\n    private List<ValidationError> validationErrors;\n\n    public int getStatus() {\n        return status;\n    }\n\n    public void setStatus(int status) {\n        this.status = status;\n    }\n\n    public List<ValidationError> getValidationErrors() {\n        return validationErrors;\n    }\n\n    public void setValidationErrors(List<ValidationError> validationErrors) {\n        this.validationErrors = validationErrors;\n    }\n\n    public boolean isRetryable() {\n        return retryable;\n    }\n\n    public void setRetryable(boolean retryable) {\n        this.retryable = retryable;\n    }\n\n    public String getCode() {\n        return code;\n    }\n\n    public void setCode(String code) {\n        this.code = code;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n\n    public String getInstance() {\n        return instance;\n    }\n\n    public void setInstance(String instance) {\n        this.instance = instance;\n    }\n}\n"
  },
  {
    "path": "common/src/main/java/com/netflix/conductor/common/validation/ValidationError.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.validation;\n\nimport java.util.StringJoiner;\n\n/** Captures a validation error that can be returned in {@link ErrorResponse}. */\npublic class ValidationError {\n\n    private String path;\n    private String message;\n    private String invalidValue;\n\n    public ValidationError() {}\n\n    public ValidationError(String path, String message, String invalidValue) {\n        this.path = path;\n        this.message = message;\n        this.invalidValue = invalidValue;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public String getInvalidValue() {\n        return invalidValue;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n\n    public void setInvalidValue(String invalidValue) {\n        this.invalidValue = invalidValue;\n    }\n\n    @Override\n    public String toString() {\n        return new StringJoiner(\", \", ValidationError.class.getSimpleName() + \"[\", \"]\")\n                .add(\"path='\" + path + \"'\")\n                .add(\"message='\" + message + \"'\")\n                .add(\"invalidValue='\" + invalidValue + \"'\")\n                .toString();\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/config/TestObjectMapperConfiguration.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/** Supplies the standard Conductor {@link ObjectMapper} for tests that need them. */\n@Configuration\npublic class TestObjectMapperConfiguration {\n\n    @Bean\n    public ObjectMapper testObjectMapper() {\n        return new ObjectMapperProvider().getObjectMapper();\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/events/EventHandlerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.events;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class EventHandlerTest {\n\n    @Test\n    public void testWorkflowTaskName() {\n        EventHandler taskDef = new EventHandler(); // name is null\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(taskDef);\n        assertEquals(3, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"Missing event handler name\"));\n        assertTrue(validationErrors.contains(\"Missing event location\"));\n        assertTrue(\n                validationErrors.contains(\n                        \"No actions specified. Please specify at-least one action\"));\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/run/TaskSummaryTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.run;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.Task;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertNotNull;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class TaskSummaryTest {\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Test\n    public void testJsonSerializing() throws Exception {\n        Task task = new Task();\n        TaskSummary taskSummary = new TaskSummary(task);\n\n        String json = objectMapper.writeValueAsString(taskSummary);\n        TaskSummary read = objectMapper.readValue(json, TaskSummary.class);\n        assertNotNull(read);\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/tasks/TaskDefTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.tasks;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class TaskDefTest {\n\n    private Validator validator;\n\n    @Before\n    public void setup() {\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        this.validator = factory.getValidator();\n    }\n\n    @Test\n    public void test() {\n        String name = \"test1\";\n        String description = \"desc\";\n        int retryCount = 10;\n        int timeout = 100;\n        TaskDef def = new TaskDef(name, description, retryCount, timeout);\n        assertEquals(36_00, def.getResponseTimeoutSeconds());\n        assertEquals(name, def.getName());\n        assertEquals(description, def.getDescription());\n        assertEquals(retryCount, def.getRetryCount());\n        assertEquals(timeout, def.getTimeoutSeconds());\n    }\n\n    @Test\n    public void testTaskDef() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"task1\");\n        taskDef.setRetryCount(-1);\n        taskDef.setTimeoutSeconds(1000);\n        taskDef.setResponseTimeoutSeconds(1001);\n\n        Set<ConstraintViolation<Object>> result = validator.validate(taskDef);\n        assertEquals(3, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"TaskDef: task1 responseTimeoutSeconds: 1001 must be less than timeoutSeconds: 1000\"));\n        assertTrue(validationErrors.contains(\"TaskDef retryCount: 0 must be >= 0\"));\n        assertTrue(validationErrors.contains(\"ownerEmail cannot be empty\"));\n    }\n\n    @Test\n    public void testTaskDefNameAndOwnerNotSet() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setRetryCount(-1);\n        taskDef.setTimeoutSeconds(1000);\n        taskDef.setResponseTimeoutSeconds(1);\n\n        Set<ConstraintViolation<Object>> result = validator.validate(taskDef);\n        assertEquals(3, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"TaskDef retryCount: 0 must be >= 0\"));\n        assertTrue(validationErrors.contains(\"TaskDef name cannot be null or empty\"));\n        assertTrue(validationErrors.contains(\"ownerEmail cannot be empty\"));\n    }\n\n    @Test\n    public void testTaskDefInvalidEmail() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test-task\");\n        taskDef.setRetryCount(1);\n        taskDef.setTimeoutSeconds(1000);\n        taskDef.setResponseTimeoutSeconds(1);\n        taskDef.setOwnerEmail(\"owner\");\n\n        Set<ConstraintViolation<Object>> result = validator.validate(taskDef);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"ownerEmail should be valid email address\"));\n    }\n\n    @Test\n    public void testTaskDefValidEmail() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test-task\");\n        taskDef.setRetryCount(1);\n        taskDef.setTimeoutSeconds(1000);\n        taskDef.setResponseTimeoutSeconds(1);\n        taskDef.setOwnerEmail(\"owner@test.com\");\n\n        Set<ConstraintViolation<Object>> result = validator.validate(taskDef);\n        assertEquals(0, result.size());\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/tasks/TaskResultTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.tasks;\n\nimport java.util.HashMap;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class TaskResultTest {\n\n    private Task task;\n    private TaskResult taskResult;\n\n    @Before\n    public void setUp() {\n        task = new Task();\n        task.setWorkflowInstanceId(\"workflow-id\");\n        task.setTaskId(\"task-id\");\n        task.setReasonForIncompletion(\"reason\");\n        task.setCallbackAfterSeconds(10);\n        task.setWorkerId(\"worker-id\");\n        task.setOutputData(new HashMap<>());\n        task.setExternalOutputPayloadStoragePath(\"externalOutput\");\n    }\n\n    @Test\n    public void testCanceledTask() {\n        task.setStatus(Task.Status.CANCELED);\n        taskResult = new TaskResult(task);\n        validateTaskResult();\n        assertEquals(TaskResult.Status.FAILED, taskResult.getStatus());\n    }\n\n    @Test\n    public void testCompletedWithErrorsTask() {\n        task.setStatus(Task.Status.COMPLETED_WITH_ERRORS);\n        taskResult = new TaskResult(task);\n        validateTaskResult();\n        assertEquals(TaskResult.Status.FAILED, taskResult.getStatus());\n    }\n\n    @Test\n    public void testScheduledTask() {\n        task.setStatus(Task.Status.SCHEDULED);\n        taskResult = new TaskResult(task);\n        validateTaskResult();\n        assertEquals(TaskResult.Status.IN_PROGRESS, taskResult.getStatus());\n    }\n\n    @Test\n    public void testCompltetedTask() {\n        task.setStatus(Task.Status.COMPLETED);\n        taskResult = new TaskResult(task);\n        validateTaskResult();\n        assertEquals(TaskResult.Status.COMPLETED, taskResult.getStatus());\n    }\n\n    private void validateTaskResult() {\n        assertEquals(task.getWorkflowInstanceId(), taskResult.getWorkflowInstanceId());\n        assertEquals(task.getTaskId(), taskResult.getTaskId());\n        assertEquals(task.getReasonForIncompletion(), taskResult.getReasonForIncompletion());\n        assertEquals(task.getCallbackAfterSeconds(), taskResult.getCallbackAfterSeconds());\n        assertEquals(task.getWorkerId(), taskResult.getWorkerId());\n        assertEquals(task.getOutputData(), taskResult.getOutputData());\n        assertEquals(\n                task.getExternalOutputPayloadStoragePath(),\n                taskResult.getExternalOutputPayloadStoragePath());\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/tasks/TaskTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.tasks;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.Task.Status;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.google.protobuf.Any;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class TaskTest {\n\n    @Test\n    public void test() {\n\n        Task task = new Task();\n        task.setStatus(Status.FAILED);\n        assertEquals(Status.FAILED, task.getStatus());\n\n        Set<String> resultStatues =\n                Arrays.stream(TaskResult.Status.values())\n                        .map(Enum::name)\n                        .collect(Collectors.toSet());\n\n        for (Status status : Status.values()) {\n            if (resultStatues.contains(status.name())) {\n                TaskResult.Status trStatus = TaskResult.Status.valueOf(status.name());\n                assertEquals(status.name(), trStatus.name());\n\n                task = new Task();\n                task.setStatus(status);\n                assertEquals(status, task.getStatus());\n            }\n        }\n    }\n\n    @Test\n    public void testTaskDefinitionIfAvailable() {\n        Task task = new Task();\n        task.setStatus(Status.FAILED);\n        assertEquals(Status.FAILED, task.getStatus());\n\n        assertNull(task.getWorkflowTask());\n        assertFalse(task.getTaskDefinition().isPresent());\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        TaskDef taskDefinition = new TaskDef();\n        workflowTask.setTaskDefinition(taskDefinition);\n        task.setWorkflowTask(workflowTask);\n\n        assertTrue(task.getTaskDefinition().isPresent());\n        assertEquals(taskDefinition, task.getTaskDefinition().get());\n    }\n\n    @Test\n    public void testTaskQueueWaitTime() {\n        Task task = new Task();\n\n        long currentTimeMillis = System.currentTimeMillis();\n        task.setScheduledTime(currentTimeMillis - 30_000); // 30 seconds ago\n        task.setStartTime(currentTimeMillis - 25_000);\n\n        long queueWaitTime = task.getQueueWaitTime();\n        assertEquals(5000L, queueWaitTime);\n\n        task.setUpdateTime(currentTimeMillis - 20_000);\n        task.setCallbackAfterSeconds(10);\n        queueWaitTime = task.getQueueWaitTime();\n        assertTrue(queueWaitTime > 0);\n    }\n\n    @Test\n    public void testDeepCopyTask() {\n        final Task task = new Task();\n        // In order to avoid forgetting putting inside the copy method the newly added fields check\n        // the number of declared fields.\n        final int expectedTaskFieldsNumber = 40;\n        final int declaredFieldsNumber = task.getClass().getDeclaredFields().length;\n\n        assertEquals(expectedTaskFieldsNumber, declaredFieldsNumber);\n\n        task.setCallbackAfterSeconds(111L);\n        task.setCallbackFromWorker(false);\n        task.setCorrelationId(\"correlation_id\");\n        task.setInputData(new HashMap<>());\n        task.setOutputData(new HashMap<>());\n        task.setReferenceTaskName(\"ref_task_name\");\n        task.setStartDelayInSeconds(1);\n        task.setTaskDefName(\"task_def_name\");\n        task.setTaskType(\"dummy_task_type\");\n        task.setWorkflowInstanceId(\"workflowInstanceId\");\n        task.setWorkflowType(\"workflowType\");\n        task.setResponseTimeoutSeconds(11L);\n        task.setStatus(Status.COMPLETED);\n        task.setRetryCount(0);\n        task.setPollCount(0);\n        task.setTaskId(\"taskId\");\n        task.setWorkflowTask(new WorkflowTask());\n        task.setDomain(\"domain\");\n        task.setInputMessage(Any.getDefaultInstance());\n        task.setOutputMessage(Any.getDefaultInstance());\n        task.setRateLimitPerFrequency(11);\n        task.setRateLimitFrequencyInSeconds(11);\n        task.setExternalInputPayloadStoragePath(\"externalInputPayloadStoragePath\");\n        task.setExternalOutputPayloadStoragePath(\"externalOutputPayloadStoragePath\");\n        task.setWorkflowPriority(0);\n        task.setIteration(1);\n        task.setExecutionNameSpace(\"name_space\");\n        task.setIsolationGroupId(\"groupId\");\n        task.setStartTime(12L);\n        task.setEndTime(20L);\n        task.setScheduledTime(7L);\n        task.setRetried(false);\n        task.setReasonForIncompletion(\"\");\n        task.setWorkerId(\"\");\n        task.setSubWorkflowId(\"\");\n        task.setSubworkflowChanged(false);\n\n        final Task copy = task.deepCopy();\n        assertEquals(task, copy);\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/utils/ConstraintParamUtilTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.utils;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class ConstraintParamUtilTest {\n\n    @Before\n    public void before() {\n        System.setProperty(\"NETFLIX_STACK\", \"test\");\n        System.setProperty(\"NETFLIX_ENVIRONMENT\", \"test\");\n        System.setProperty(\"TEST_ENV\", \"test\");\n    }\n\n    private WorkflowDef constructWorkflowDef() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        return workflowDef;\n    }\n\n    @Test\n    public void testExtractParamPathComponents() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithMissingEnvVariable() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID} ${NETFLIX_STACK}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithValidEnvVariable() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID}  ${workflow.input.status}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithValidMap() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID}  ${workflow.input.status}\");\n        Map<String, Object> envInputParam = new HashMap<>();\n        envInputParam.put(\"packageId\", \"${workflow.input.packageId}\");\n        envInputParam.put(\"taskId\", \"${CPEWF_TASK_ID}\");\n        envInputParam.put(\"NETFLIX_STACK\", \"${NETFLIX_STACK}\");\n        envInputParam.put(\"NETFLIX_ENVIRONMENT\", \"${NETFLIX_ENVIRONMENT}\");\n        envInputParam.put(\"TEST_ENV\", \"${TEST_ENV}\");\n\n        inputParam.put(\"env\", envInputParam);\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithInvalidEnv() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID}  ${workflow.input.status}\");\n        Map<String, Object> envInputParam = new HashMap<>();\n        envInputParam.put(\"packageId\", \"${workflow.input.packageId}\");\n        envInputParam.put(\"taskId\", \"${CPEWF_TASK_ID}\");\n        envInputParam.put(\"TEST_ENV1\", \"${TEST_ENV1}\");\n\n        inputParam.put(\"env\", envInputParam);\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 1);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithInputParamEmpty() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"\");\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithListInputParamWithEmptyString() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", new String[] {\"\"});\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithInputFieldWithSpace() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID}  ${workflow.input.status sta}\");\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 1);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithPredefineEnums() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"NETFLIX_ENV\", \"${CPEWF_TASK_ID}\");\n        inputParam.put(\n                \"entryPoint\", \"/tools/pdfwatermarker_mux.py ${NETFLIX_ENV} ${CPEWF_TASK_ID} alpha\");\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n\n    @Test\n    public void testExtractParamPathComponentsWithEscapedChar() {\n        WorkflowDef workflowDef = constructWorkflowDef();\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"$${expression with spaces}\");\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        List<String> results =\n                ConstraintParamUtil.validateInputParam(inputParam, \"task_1\", workflowDef);\n        assertEquals(results.size(), 0);\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/utils/SummaryUtilTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.utils;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ContextConfiguration(\n        classes = {\n            TestObjectMapperConfiguration.class,\n            SummaryUtilTest.SummaryUtilTestConfiguration.class\n        })\n@RunWith(SpringRunner.class)\npublic class SummaryUtilTest {\n\n    @Configuration\n    static class SummaryUtilTestConfiguration {\n\n        @Bean\n        public SummaryUtil summaryUtil() {\n            return new SummaryUtil();\n        }\n    }\n\n    @Autowired private ObjectMapper objectMapper;\n\n    private Map<String, Object> testObject;\n\n    @Before\n    public void init() {\n        Map<String, Object> child = new HashMap<>();\n        child.put(\"testStr\", \"childTestStr\");\n\n        Map<String, Object> obj = new HashMap<>();\n        obj.put(\"testStr\", \"stringValue\");\n        obj.put(\"testArray\", new ArrayList<>(Arrays.asList(1, 2, 3)));\n        obj.put(\"testObj\", child);\n        obj.put(\"testNull\", null);\n\n        testObject = obj;\n    }\n\n    @Test\n    public void testSerializeInputOutput_defaultToString() throws Exception {\n        new ApplicationContextRunner()\n                .withPropertyValues(\n                        \"conductor.app.summary-input-output-json-serialization.enabled:false\")\n                .withUserConfiguration(SummaryUtilTestConfiguration.class)\n                .run(\n                        context -> {\n                            String serialized = SummaryUtil.serializeInputOutput(this.testObject);\n\n                            assertEquals(\n                                    this.testObject.toString(),\n                                    serialized,\n                                    \"The Java.toString() Serialization should match the serialized Test Object\");\n                        });\n    }\n\n    @Test\n    public void testSerializeInputOutput_jsonSerializationEnabled() throws Exception {\n        new ApplicationContextRunner()\n                .withPropertyValues(\n                        \"conductor.app.summary-input-output-json-serialization.enabled:true\")\n                .withUserConfiguration(SummaryUtilTestConfiguration.class)\n                .run(\n                        context -> {\n                            String serialized = SummaryUtil.serializeInputOutput(testObject);\n\n                            assertEquals(\n                                    objectMapper.writeValueAsString(testObject),\n                                    serialized,\n                                    \"The ObjectMapper Json Serialization should match the serialized Test Object\");\n                        });\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/workflow/SubWorkflowParamsTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.workflow;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.fasterxml.jackson.databind.MapperFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.SerializationFeature;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class SubWorkflowParamsTest {\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Test\n    public void testWorkflowTaskName() {\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams(); // name is null\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n\n        Set<ConstraintViolation<Object>> result = validator.validate(subWorkflowParams);\n        assertEquals(2, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"SubWorkflowParams name cannot be null\"));\n        assertTrue(validationErrors.contains(\"SubWorkflowParams name cannot be empty\"));\n    }\n\n    @Test\n    public void testWorkflowSetTaskToDomain() {\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(\"unit\", \"test\");\n        subWorkflowParams.setTaskToDomain(taskToDomain);\n        assertEquals(taskToDomain, subWorkflowParams.getTaskToDomain());\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testSetWorkflowDefinition() {\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(\"dummy-name\");\n        subWorkflowParams.setWorkflowDefinition(new Object());\n    }\n\n    @Test\n    public void testGetWorkflowDef() {\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(\"dummy-name\");\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test_workflow\");\n        def.setVersion(1);\n        WorkflowTask task = new WorkflowTask();\n        task.setName(\"test_task\");\n        task.setTaskReferenceName(\"t1\");\n        def.getTasks().add(task);\n        subWorkflowParams.setWorkflowDefinition(def);\n        assertEquals(def, subWorkflowParams.getWorkflowDefinition());\n        assertEquals(def, subWorkflowParams.getWorkflowDef());\n    }\n\n    @Test\n    public void testWorkflowDefJson() throws Exception {\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(\"dummy-name\");\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test_workflow\");\n        def.setVersion(1);\n        WorkflowTask task = new WorkflowTask();\n        task.setName(\"test_task\");\n        task.setTaskReferenceName(\"t1\");\n        def.getTasks().add(task);\n        subWorkflowParams.setWorkflowDefinition(def);\n\n        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);\n        objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);\n        objectMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);\n\n        String serializedParams =\n                objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(subWorkflowParams);\n        SubWorkflowParams deserializedParams =\n                objectMapper.readValue(serializedParams, SubWorkflowParams.class);\n        assertEquals(def, deserializedParams.getWorkflowDefinition());\n        assertEquals(def, deserializedParams.getWorkflowDef());\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/workflow/WorkflowDefValidatorTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.workflow;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\npublic class WorkflowDefValidatorTest {\n\n    @Before\n    public void before() {\n        System.setProperty(\"NETFLIX_STACK\", \"test\");\n        System.setProperty(\"NETFLIX_ENVIRONMENT\", \"test\");\n        System.setProperty(\"TEST_ENV\", \"test\");\n    }\n\n    @Test\n    public void testWorkflowDefConstraints() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(2);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(3, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"WorkflowDef name cannot be null or empty\"));\n        assertTrue(validationErrors.contains(\"WorkflowTask list cannot be empty\"));\n        assertTrue(validationErrors.contains(\"ownerEmail cannot be empty\"));\n        // assertTrue(validationErrors.contains(\"workflowDef schemaVersion: 1 should be >= 2\"));\n    }\n\n    @Test\n    public void testWorkflowDefConstraintsWithMultipleEnvVariable() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID}\");\n        inputParam.put(\n                \"entryPoint\",\n                \"${NETFLIX_ENVIRONMENT} ${NETFLIX_STACK} ${CPEWF_TASK_ID} ${workflow.input.status}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        WorkflowTask workflowTask_2 = new WorkflowTask();\n        workflowTask_2.setName(\"task_2\");\n        workflowTask_2.setTaskReferenceName(\"task_2\");\n        workflowTask_2.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam2 = new HashMap<>();\n        inputParam2.put(\"env\", inputParam);\n\n        workflowTask_2.setInputParameters(inputParam2);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n        tasks.add(workflowTask_2);\n\n        workflowDef.setTasks(tasks);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowDefConstraintsSingleEnvVariable() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowDefConstraintsDualEnvVariable() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID} ${NETFLIX_STACK}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowDefConstraintsWithMapAsInputParam() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"taskId\", \"${CPEWF_TASK_ID} ${NETFLIX_STACK}\");\n        Map<String, Object> envInputParam = new HashMap<>();\n        envInputParam.put(\"packageId\", \"${workflow.input.packageId}\");\n        envInputParam.put(\"taskId\", \"${CPEWF_TASK_ID}\");\n        envInputParam.put(\"NETFLIX_STACK\", \"${NETFLIX_STACK}\");\n        envInputParam.put(\"NETFLIX_ENVIRONMENT\", \"${NETFLIX_ENVIRONMENT}\");\n\n        inputParam.put(\"env\", envInputParam);\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskInputParamInvalid() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask = new WorkflowTask(); // name is null\n        workflowTask.setName(\"t1\");\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n        workflowTask.setTaskReferenceName(\"t1\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"blabla\", \"${workflow.input.Space Value}\");\n        workflowTask.setInputParameters(map);\n\n        workflowDef.getTasks().add(workflowTask);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"key: blabla input parameter value: workflow.input.Space Value is not valid\"));\n    }\n\n    @Test\n    public void testWorkflowTaskEmptyStringInputParamValue() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask = new WorkflowTask(); // name is null\n\n        workflowTask.setName(\"t1\");\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n        workflowTask.setTaskReferenceName(\"t1\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"blabla\", \"\");\n        workflowTask.setInputParameters(map);\n\n        workflowDef.getTasks().add(workflowTask);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTasklistInputParamWithEmptyString() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask = new WorkflowTask(); // name is null\n\n        workflowTask.setName(\"t1\");\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n        workflowTask.setTaskReferenceName(\"t1\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"blabla\", \"\");\n        map.put(\"foo\", new String[] {\"\"});\n        workflowTask.setInputParameters(map);\n\n        workflowDef.getTasks().add(workflowTask);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowSchemaVersion1() {\n        WorkflowDef workflowDef = new WorkflowDef(); // name is null\n        workflowDef.setSchemaVersion(3);\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask = new WorkflowTask();\n\n        workflowTask.setName(\"t1\");\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n        workflowTask.setTaskReferenceName(\"t1\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"blabla\", \"\");\n        workflowTask.setInputParameters(map);\n\n        workflowDef.getTasks().add(workflowTask);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"workflowDef schemaVersion: 2 is only supported\"));\n    }\n\n    @Test\n    public void testWorkflowOwnerInvalidEmail() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner\");\n\n        WorkflowTask workflowTask = new WorkflowTask();\n\n        workflowTask.setName(\"t1\");\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n        workflowTask.setTaskReferenceName(\"t1\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"blabla\", \"\");\n        workflowTask.setInputParameters(map);\n\n        workflowDef.getTasks().add(workflowTask);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"ownerEmail should be valid email address\"));\n    }\n\n    @Test\n    public void testWorkflowOwnerValidEmail() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test_env\");\n        workflowDef.setOwnerEmail(\"owner@test.com\");\n\n        WorkflowTask workflowTask = new WorkflowTask();\n\n        workflowTask.setName(\"t1\");\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n        workflowTask.setTaskReferenceName(\"t1\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"blabla\", \"\");\n        workflowTask.setInputParameters(map);\n\n        workflowDef.getTasks().add(workflowTask);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n}\n"
  },
  {
    "path": "common/src/test/java/com/netflix/conductor/common/workflow/WorkflowTaskTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.workflow;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class WorkflowTaskTest {\n\n    @Test\n    public void test() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setWorkflowTaskType(TaskType.DECISION);\n\n        assertNotNull(workflowTask.getType());\n        assertEquals(TaskType.DECISION.name(), workflowTask.getType());\n\n        workflowTask = new WorkflowTask();\n        workflowTask.setWorkflowTaskType(TaskType.SWITCH);\n\n        assertNotNull(workflowTask.getType());\n        assertEquals(TaskType.SWITCH.name(), workflowTask.getType());\n    }\n\n    @Test\n    public void testOptional() {\n        WorkflowTask task = new WorkflowTask();\n        assertFalse(task.isOptional());\n\n        task.setOptional(Boolean.FALSE);\n        assertFalse(task.isOptional());\n\n        task.setOptional(Boolean.TRUE);\n        assertTrue(task.isOptional());\n    }\n\n    @Test\n    public void testWorkflowTaskName() {\n        WorkflowTask taskDef = new WorkflowTask(); // name is null\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(taskDef);\n        assertEquals(2, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"WorkflowTask name cannot be empty or null\"));\n        assertTrue(\n                validationErrors.contains(\n                        \"WorkflowTask taskReferenceName name cannot be empty or null\"));\n    }\n}\n"
  },
  {
    "path": "core/build.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\napply plugin: 'groovy'\n\ndependencies {\n    implementation project(':conductor-common')\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n    compileOnly 'org.springframework.boot:spring-boot-starter-validation'\n    compileOnly 'org.springframework.retry:spring-retry'\n\n    implementation \"com.fasterxml.jackson.core:jackson-annotations:${revFasterXml}\"\n    implementation \"com.fasterxml.jackson.core:jackson-databind:${revFasterXml}\"\n\n    implementation \"commons-io:commons-io:${revCommonsIo}\"\n\n    implementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n\n    implementation \"org.apache.commons:commons-lang3\"\n\n    implementation \"com.fasterxml.jackson.core:jackson-core:${revFasterXml}\"\n\n    implementation \"com.spotify:completable-futures:${revSpotifyCompletableFutures}\"\n\n    implementation \"com.jayway.jsonpath:json-path:${revJsonPath}\"\n\n    implementation \"io.reactivex:rxjava:${revRxJava}\"\n\n    implementation \"com.netflix.spectator:spectator-api:${revSpectator}\"\n\n    implementation \"org.apache.bval:bval-jsr:${revBval}\"\n\n    implementation \"com.github.ben-manes.caffeine:caffeine\"\n\n    implementation \"org.openjdk.nashorn:nashorn-core:15.4\"\n\n    // JAXB is not bundled with Java 11, dependencies added explicitly\n    // These are needed by Apache BVAL\n    implementation \"jakarta.xml.bind:jakarta.xml.bind-api:${revJAXB}\"\n    implementation \"jakarta.activation:jakarta.activation-api:${revActivation}\"\n\n    // Only add it as a test dependency. The actual jaxb runtime provider is provided when building the server.\n    testImplementation \"org.glassfish.jaxb:jaxb-runtime:${revJAXB}\"\n\n    testImplementation 'org.springframework.boot:spring-boot-starter-validation'\n    testImplementation 'org.springframework.retry:spring-retry'\n    testImplementation project(':conductor-common').sourceSets.test.output\n\n    testImplementation \"org.codehaus.groovy:groovy-all:${revGroovy}\"\n    testImplementation \"org.spockframework:spock-core:${revSpock}\"\n    testImplementation \"org.spockframework:spock-spring:${revSpock}\"\n    testImplementation \"org.junit.vintage:junit-vintage-engine\"\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/annotations/Audit.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotations;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.TYPE;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/** Mark service for custom audit implementation */\n@Target({TYPE})\n@Retention(RUNTIME)\npublic @interface Audit {}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/annotations/Trace.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotations;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.TYPE;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n@Target({TYPE})\n@Retention(RUNTIME)\npublic @interface Trace {}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/annotations/VisibleForTesting.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.annotations;\n\nimport java.lang.annotation.*;\n\n/**\n * Annotates a program element that exists, or is more widely visible than otherwise necessary, only\n * for use in test code.\n */\n@Retention(RetentionPolicy.CLASS)\n@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})\n@Documented\npublic @interface VisibleForTesting {}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/LifecycleAwareComponent.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.SmartLifecycle;\n\npublic abstract class LifecycleAwareComponent implements SmartLifecycle {\n\n    private volatile boolean running = false;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(LifecycleAwareComponent.class);\n\n    @Override\n    public final void start() {\n        running = true;\n        LOGGER.info(\"{} started.\", getClass().getSimpleName());\n        doStart();\n    }\n\n    @Override\n    public final void stop() {\n        running = false;\n        LOGGER.info(\"{} stopped.\", getClass().getSimpleName());\n        doStop();\n    }\n\n    @Override\n    public final boolean isRunning() {\n        return running;\n    }\n\n    public void doStart() {}\n\n    public void doStop() {}\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/WorkflowContext.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core;\n\n/** Store the authentication context, app or username or both */\npublic class WorkflowContext {\n\n    public static final ThreadLocal<WorkflowContext> THREAD_LOCAL =\n            InheritableThreadLocal.withInitial(() -> new WorkflowContext(\"\", \"\"));\n\n    private final String clientApp;\n\n    private final String userName;\n\n    public WorkflowContext(String clientApp) {\n        this.clientApp = clientApp;\n        this.userName = null;\n    }\n\n    public WorkflowContext(String clientApp, String userName) {\n        this.clientApp = clientApp;\n        this.userName = userName;\n    }\n\n    public static WorkflowContext get() {\n        return THREAD_LOCAL.get();\n    }\n\n    public static void set(WorkflowContext ctx) {\n        THREAD_LOCAL.set(ctx);\n    }\n\n    public static void unset() {\n        THREAD_LOCAL.remove();\n    }\n\n    /**\n     * @return the clientApp\n     */\n    public String getClientApp() {\n        return clientApp;\n    }\n\n    /**\n     * @return the username\n     */\n    public String getUserName() {\n        return userName;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/config/ConductorCoreConfiguration.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.config;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.concurrent.BasicThreadFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.retry.support.RetryTemplate;\n\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.events.EventQueueProvider;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.execution.mapper.TaskMapper;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.core.listener.TaskStatusListener;\nimport com.netflix.conductor.core.listener.TaskStatusListenerStub;\nimport com.netflix.conductor.core.listener.WorkflowStatusListener;\nimport com.netflix.conductor.core.listener.WorkflowStatusListenerStub;\nimport com.netflix.conductor.core.storage.DummyPayloadStorage;\nimport com.netflix.conductor.core.sync.Lock;\nimport com.netflix.conductor.core.sync.noop.NoopLock;\n\nimport static com.netflix.conductor.core.events.EventQueues.EVENT_QUEUE_PROVIDERS_QUALIFIER;\nimport static com.netflix.conductor.core.execution.tasks.SystemTaskRegistry.ASYNC_SYSTEM_TASKS_QUALIFIER;\n\nimport static java.util.function.Function.identity;\n\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(ConductorProperties.class)\npublic class ConductorCoreConfiguration {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConductorCoreConfiguration.class);\n\n    @ConditionalOnProperty(\n            name = \"conductor.workflow-execution-lock.type\",\n            havingValue = \"noop_lock\",\n            matchIfMissing = true)\n    @Bean\n    public Lock provideLock() {\n        return new NoopLock();\n    }\n\n    @ConditionalOnProperty(\n            name = \"conductor.external-payload-storage.type\",\n            havingValue = \"dummy\",\n            matchIfMissing = true)\n    @Bean\n    public ExternalPayloadStorage dummyExternalPayloadStorage() {\n        LOGGER.info(\"Initialized dummy payload storage!\");\n        return new DummyPayloadStorage();\n    }\n\n    @ConditionalOnProperty(\n            name = \"conductor.workflow-status-listener.type\",\n            havingValue = \"stub\",\n            matchIfMissing = true)\n    @Bean\n    public WorkflowStatusListener workflowStatusListener() {\n        return new WorkflowStatusListenerStub();\n    }\n\n    @ConditionalOnProperty(\n            name = \"conductor.task-status-listener.type\",\n            havingValue = \"stub\",\n            matchIfMissing = true)\n    @Bean\n    public TaskStatusListener taskStatusListener() {\n        return new TaskStatusListenerStub();\n    }\n\n    @Bean\n    public ExecutorService executorService(ConductorProperties conductorProperties) {\n        ThreadFactory threadFactory =\n                new BasicThreadFactory.Builder()\n                        .namingPattern(\"conductor-worker-%d\")\n                        .daemon(true)\n                        .build();\n        return Executors.newFixedThreadPool(\n                conductorProperties.getExecutorServiceMaxThreadCount(), threadFactory);\n    }\n\n    @Bean\n    @Qualifier(\"taskMappersByTaskType\")\n    public Map<String, TaskMapper> getTaskMappers(List<TaskMapper> taskMappers) {\n        return taskMappers.stream().collect(Collectors.toMap(TaskMapper::getTaskType, identity()));\n    }\n\n    @Bean\n    @Qualifier(ASYNC_SYSTEM_TASKS_QUALIFIER)\n    public Set<WorkflowSystemTask> asyncSystemTasks(Set<WorkflowSystemTask> allSystemTasks) {\n        return allSystemTasks.stream()\n                .filter(WorkflowSystemTask::isAsync)\n                .collect(Collectors.toUnmodifiableSet());\n    }\n\n    @Bean\n    @Qualifier(EVENT_QUEUE_PROVIDERS_QUALIFIER)\n    public Map<String, EventQueueProvider> getEventQueueProviders(\n            List<EventQueueProvider> eventQueueProviders) {\n        return eventQueueProviders.stream()\n                .collect(Collectors.toMap(EventQueueProvider::getQueueType, identity()));\n    }\n\n    @Bean\n    public RetryTemplate onTransientErrorRetryTemplate() {\n        return RetryTemplate.builder()\n                .retryOn(TransientException.class)\n                .maxAttempts(3)\n                .noBackoff()\n                .build();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/config/ConductorProperties.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DataSizeUnit;\nimport org.springframework.boot.convert.DurationUnit;\nimport org.springframework.util.unit.DataSize;\nimport org.springframework.util.unit.DataUnit;\n\n@ConfigurationProperties(\"conductor.app\")\npublic class ConductorProperties {\n\n    /**\n     * Name of the stack within which the app is running. e.g. devint, testintg, staging, prod etc.\n     */\n    private String stack = \"test\";\n\n    /** The id with the app has been registered. */\n    private String appId = \"conductor\";\n\n    /** The maximum number of threads to be allocated to the executor service threadpool. */\n    private int executorServiceMaxThreadCount = 50;\n\n    /** The timeout duration to set when a workflow is pushed to the decider queue. */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration workflowOffsetTimeout = Duration.ofSeconds(30);\n\n    /** The number of threads to use to do background sweep on active workflows. */\n    private int sweeperThreadCount = Runtime.getRuntime().availableProcessors() * 2;\n\n    /** The timeout (in milliseconds) for the polling of workflows to be swept. */\n    private Duration sweeperWorkflowPollTimeout = Duration.ofMillis(2000);\n\n    /** The number of threads to configure the threadpool in the event processor. */\n    private int eventProcessorThreadCount = 2;\n\n    /** Used to enable/disable the indexing of messages within event payloads. */\n    private boolean eventMessageIndexingEnabled = true;\n\n    /** Used to enable/disable the indexing of event execution results. */\n    private boolean eventExecutionIndexingEnabled = true;\n\n    /** Used to enable/disable the workflow execution lock. */\n    private boolean workflowExecutionLockEnabled = false;\n\n    /** The time (in milliseconds) for which the lock is leased for. */\n    private Duration lockLeaseTime = Duration.ofMillis(60000);\n\n    /**\n     * The time (in milliseconds) for which the thread will block in an attempt to acquire the lock.\n     */\n    private Duration lockTimeToTry = Duration.ofMillis(500);\n\n    /**\n     * The time (in seconds) that is used to consider if a worker is actively polling for a task.\n     */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration activeWorkerLastPollTimeout = Duration.ofSeconds(10);\n\n    /**\n     * The time (in seconds) for which a task execution will be postponed if being rate limited or\n     * concurrent execution limited.\n     */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration taskExecutionPostponeDuration = Duration.ofSeconds(60);\n\n    /** Used to enable/disable the indexing of task execution logs. */\n    private boolean taskExecLogIndexingEnabled = true;\n\n    /** Used to enable/disable asynchronous indexing to elasticsearch. */\n    private boolean asyncIndexingEnabled = false;\n\n    /** The number of threads to be used within the threadpool for system task workers. */\n    private int systemTaskWorkerThreadCount = Runtime.getRuntime().availableProcessors() * 2;\n\n    /**\n     * The interval (in seconds) after which a system task will be checked by the system task worker\n     * for completion.\n     */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration systemTaskWorkerCallbackDuration = Duration.ofSeconds(30);\n\n    /**\n     * The interval (in milliseconds) at which system task queues will be polled by the system task\n     * workers.\n     */\n    private Duration systemTaskWorkerPollInterval = Duration.ofMillis(50);\n\n    /** The namespace for the system task workers to provide instance level isolation. */\n    private String systemTaskWorkerExecutionNamespace = \"\";\n\n    /**\n     * The number of threads to be used within the threadpool for system task workers in each\n     * isolation group.\n     */\n    private int isolatedSystemTaskWorkerThreadCount = 1;\n\n    /**\n     * The duration of workflow execution which qualifies a workflow as a short-running workflow\n     * when async indexing to elasticsearch is enabled.\n     */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration asyncUpdateShortRunningWorkflowDuration = Duration.ofSeconds(30);\n\n    /**\n     * The delay with which short-running workflows will be updated in the elasticsearch index when\n     * async indexing is enabled.\n     */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration asyncUpdateDelay = Duration.ofSeconds(60);\n\n    /**\n     * Used to control the validation for owner email field as mandatory within workflow and task\n     * definitions.\n     */\n    private boolean ownerEmailMandatory = true;\n\n    /**\n     * The number of threads to be usde in Scheduler used for polling events from multiple event\n     * queues. By default, a thread count equal to the number of CPU cores is chosen.\n     */\n    private int eventQueueSchedulerPollThreadCount = Runtime.getRuntime().availableProcessors();\n\n    /** The time interval (in milliseconds) at which the default event queues will be polled. */\n    private Duration eventQueuePollInterval = Duration.ofMillis(100);\n\n    /** The number of messages to be polled from a default event queue in a single operation. */\n    private int eventQueuePollCount = 10;\n\n    /** The timeout (in milliseconds) for the poll operation on the default event queue. */\n    private Duration eventQueueLongPollTimeout = Duration.ofMillis(1000);\n\n    /**\n     * The threshold of the workflow input payload size in KB beyond which the payload will be\n     * stored in {@link com.netflix.conductor.common.utils.ExternalPayloadStorage}.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize workflowInputPayloadSizeThreshold = DataSize.ofKilobytes(5120L);\n\n    /**\n     * The maximum threshold of the workflow input payload size in KB beyond which input will be\n     * rejected and the workflow will be marked as FAILED.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize maxWorkflowInputPayloadSizeThreshold = DataSize.ofKilobytes(10240L);\n\n    /**\n     * The threshold of the workflow output payload size in KB beyond which the payload will be\n     * stored in {@link com.netflix.conductor.common.utils.ExternalPayloadStorage}.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize workflowOutputPayloadSizeThreshold = DataSize.ofKilobytes(5120L);\n\n    /**\n     * The maximum threshold of the workflow output payload size in KB beyond which output will be\n     * rejected and the workflow will be marked as FAILED.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize maxWorkflowOutputPayloadSizeThreshold = DataSize.ofKilobytes(10240L);\n\n    /**\n     * The threshold of the task input payload size in KB beyond which the payload will be stored in\n     * {@link com.netflix.conductor.common.utils.ExternalPayloadStorage}.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize taskInputPayloadSizeThreshold = DataSize.ofKilobytes(3072L);\n\n    /**\n     * The maximum threshold of the task input payload size in KB beyond which the task input will\n     * be rejected and the task will be marked as FAILED_WITH_TERMINAL_ERROR.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize maxTaskInputPayloadSizeThreshold = DataSize.ofKilobytes(10240L);\n\n    /**\n     * The threshold of the task output payload size in KB beyond which the payload will be stored\n     * in {@link com.netflix.conductor.common.utils.ExternalPayloadStorage}.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize taskOutputPayloadSizeThreshold = DataSize.ofKilobytes(3072L);\n\n    /**\n     * The maximum threshold of the task output payload size in KB beyond which the task input will\n     * be rejected and the task will be marked as FAILED_WITH_TERMINAL_ERROR.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize maxTaskOutputPayloadSizeThreshold = DataSize.ofKilobytes(10240L);\n\n    /**\n     * The maximum threshold of the workflow variables payload size in KB beyond which the task\n     * changes will be rejected and the task will be marked as FAILED_WITH_TERMINAL_ERROR.\n     */\n    @DataSizeUnit(DataUnit.KILOBYTES)\n    private DataSize maxWorkflowVariablesPayloadSizeThreshold = DataSize.ofKilobytes(256L);\n\n    /** Used to limit the size of task execution logs. */\n    private int taskExecLogSizeLimit = 10;\n\n    public String getStack() {\n        return stack;\n    }\n\n    public void setStack(String stack) {\n        this.stack = stack;\n    }\n\n    public String getAppId() {\n        return appId;\n    }\n\n    public void setAppId(String appId) {\n        this.appId = appId;\n    }\n\n    public int getExecutorServiceMaxThreadCount() {\n        return executorServiceMaxThreadCount;\n    }\n\n    public void setExecutorServiceMaxThreadCount(int executorServiceMaxThreadCount) {\n        this.executorServiceMaxThreadCount = executorServiceMaxThreadCount;\n    }\n\n    public Duration getWorkflowOffsetTimeout() {\n        return workflowOffsetTimeout;\n    }\n\n    public void setWorkflowOffsetTimeout(Duration workflowOffsetTimeout) {\n        this.workflowOffsetTimeout = workflowOffsetTimeout;\n    }\n\n    public int getSweeperThreadCount() {\n        return sweeperThreadCount;\n    }\n\n    public void setSweeperThreadCount(int sweeperThreadCount) {\n        this.sweeperThreadCount = sweeperThreadCount;\n    }\n\n    public Duration getSweeperWorkflowPollTimeout() {\n        return sweeperWorkflowPollTimeout;\n    }\n\n    public void setSweeperWorkflowPollTimeout(Duration sweeperWorkflowPollTimeout) {\n        this.sweeperWorkflowPollTimeout = sweeperWorkflowPollTimeout;\n    }\n\n    public int getEventProcessorThreadCount() {\n        return eventProcessorThreadCount;\n    }\n\n    public void setEventProcessorThreadCount(int eventProcessorThreadCount) {\n        this.eventProcessorThreadCount = eventProcessorThreadCount;\n    }\n\n    public boolean isEventMessageIndexingEnabled() {\n        return eventMessageIndexingEnabled;\n    }\n\n    public void setEventMessageIndexingEnabled(boolean eventMessageIndexingEnabled) {\n        this.eventMessageIndexingEnabled = eventMessageIndexingEnabled;\n    }\n\n    public boolean isEventExecutionIndexingEnabled() {\n        return eventExecutionIndexingEnabled;\n    }\n\n    public void setEventExecutionIndexingEnabled(boolean eventExecutionIndexingEnabled) {\n        this.eventExecutionIndexingEnabled = eventExecutionIndexingEnabled;\n    }\n\n    public boolean isWorkflowExecutionLockEnabled() {\n        return workflowExecutionLockEnabled;\n    }\n\n    public void setWorkflowExecutionLockEnabled(boolean workflowExecutionLockEnabled) {\n        this.workflowExecutionLockEnabled = workflowExecutionLockEnabled;\n    }\n\n    public Duration getLockLeaseTime() {\n        return lockLeaseTime;\n    }\n\n    public void setLockLeaseTime(Duration lockLeaseTime) {\n        this.lockLeaseTime = lockLeaseTime;\n    }\n\n    public Duration getLockTimeToTry() {\n        return lockTimeToTry;\n    }\n\n    public void setLockTimeToTry(Duration lockTimeToTry) {\n        this.lockTimeToTry = lockTimeToTry;\n    }\n\n    public Duration getActiveWorkerLastPollTimeout() {\n        return activeWorkerLastPollTimeout;\n    }\n\n    public void setActiveWorkerLastPollTimeout(Duration activeWorkerLastPollTimeout) {\n        this.activeWorkerLastPollTimeout = activeWorkerLastPollTimeout;\n    }\n\n    public Duration getTaskExecutionPostponeDuration() {\n        return taskExecutionPostponeDuration;\n    }\n\n    public void setTaskExecutionPostponeDuration(Duration taskExecutionPostponeDuration) {\n        this.taskExecutionPostponeDuration = taskExecutionPostponeDuration;\n    }\n\n    public boolean isTaskExecLogIndexingEnabled() {\n        return taskExecLogIndexingEnabled;\n    }\n\n    public void setTaskExecLogIndexingEnabled(boolean taskExecLogIndexingEnabled) {\n        this.taskExecLogIndexingEnabled = taskExecLogIndexingEnabled;\n    }\n\n    public boolean isAsyncIndexingEnabled() {\n        return asyncIndexingEnabled;\n    }\n\n    public void setAsyncIndexingEnabled(boolean asyncIndexingEnabled) {\n        this.asyncIndexingEnabled = asyncIndexingEnabled;\n    }\n\n    public int getSystemTaskWorkerThreadCount() {\n        return systemTaskWorkerThreadCount;\n    }\n\n    public void setSystemTaskWorkerThreadCount(int systemTaskWorkerThreadCount) {\n        this.systemTaskWorkerThreadCount = systemTaskWorkerThreadCount;\n    }\n\n    public Duration getSystemTaskWorkerCallbackDuration() {\n        return systemTaskWorkerCallbackDuration;\n    }\n\n    public void setSystemTaskWorkerCallbackDuration(Duration systemTaskWorkerCallbackDuration) {\n        this.systemTaskWorkerCallbackDuration = systemTaskWorkerCallbackDuration;\n    }\n\n    public Duration getSystemTaskWorkerPollInterval() {\n        return systemTaskWorkerPollInterval;\n    }\n\n    public void setSystemTaskWorkerPollInterval(Duration systemTaskWorkerPollInterval) {\n        this.systemTaskWorkerPollInterval = systemTaskWorkerPollInterval;\n    }\n\n    public String getSystemTaskWorkerExecutionNamespace() {\n        return systemTaskWorkerExecutionNamespace;\n    }\n\n    public void setSystemTaskWorkerExecutionNamespace(String systemTaskWorkerExecutionNamespace) {\n        this.systemTaskWorkerExecutionNamespace = systemTaskWorkerExecutionNamespace;\n    }\n\n    public int getIsolatedSystemTaskWorkerThreadCount() {\n        return isolatedSystemTaskWorkerThreadCount;\n    }\n\n    public void setIsolatedSystemTaskWorkerThreadCount(int isolatedSystemTaskWorkerThreadCount) {\n        this.isolatedSystemTaskWorkerThreadCount = isolatedSystemTaskWorkerThreadCount;\n    }\n\n    public Duration getAsyncUpdateShortRunningWorkflowDuration() {\n        return asyncUpdateShortRunningWorkflowDuration;\n    }\n\n    public void setAsyncUpdateShortRunningWorkflowDuration(\n            Duration asyncUpdateShortRunningWorkflowDuration) {\n        this.asyncUpdateShortRunningWorkflowDuration = asyncUpdateShortRunningWorkflowDuration;\n    }\n\n    public Duration getAsyncUpdateDelay() {\n        return asyncUpdateDelay;\n    }\n\n    public void setAsyncUpdateDelay(Duration asyncUpdateDelay) {\n        this.asyncUpdateDelay = asyncUpdateDelay;\n    }\n\n    public boolean isOwnerEmailMandatory() {\n        return ownerEmailMandatory;\n    }\n\n    public void setOwnerEmailMandatory(boolean ownerEmailMandatory) {\n        this.ownerEmailMandatory = ownerEmailMandatory;\n    }\n\n    public int getEventQueueSchedulerPollThreadCount() {\n        return eventQueueSchedulerPollThreadCount;\n    }\n\n    public void setEventQueueSchedulerPollThreadCount(int eventQueueSchedulerPollThreadCount) {\n        this.eventQueueSchedulerPollThreadCount = eventQueueSchedulerPollThreadCount;\n    }\n\n    public Duration getEventQueuePollInterval() {\n        return eventQueuePollInterval;\n    }\n\n    public void setEventQueuePollInterval(Duration eventQueuePollInterval) {\n        this.eventQueuePollInterval = eventQueuePollInterval;\n    }\n\n    public int getEventQueuePollCount() {\n        return eventQueuePollCount;\n    }\n\n    public void setEventQueuePollCount(int eventQueuePollCount) {\n        this.eventQueuePollCount = eventQueuePollCount;\n    }\n\n    public Duration getEventQueueLongPollTimeout() {\n        return eventQueueLongPollTimeout;\n    }\n\n    public void setEventQueueLongPollTimeout(Duration eventQueueLongPollTimeout) {\n        this.eventQueueLongPollTimeout = eventQueueLongPollTimeout;\n    }\n\n    public DataSize getWorkflowInputPayloadSizeThreshold() {\n        return workflowInputPayloadSizeThreshold;\n    }\n\n    public void setWorkflowInputPayloadSizeThreshold(DataSize workflowInputPayloadSizeThreshold) {\n        this.workflowInputPayloadSizeThreshold = workflowInputPayloadSizeThreshold;\n    }\n\n    public DataSize getMaxWorkflowInputPayloadSizeThreshold() {\n        return maxWorkflowInputPayloadSizeThreshold;\n    }\n\n    public void setMaxWorkflowInputPayloadSizeThreshold(\n            DataSize maxWorkflowInputPayloadSizeThreshold) {\n        this.maxWorkflowInputPayloadSizeThreshold = maxWorkflowInputPayloadSizeThreshold;\n    }\n\n    public DataSize getWorkflowOutputPayloadSizeThreshold() {\n        return workflowOutputPayloadSizeThreshold;\n    }\n\n    public void setWorkflowOutputPayloadSizeThreshold(DataSize workflowOutputPayloadSizeThreshold) {\n        this.workflowOutputPayloadSizeThreshold = workflowOutputPayloadSizeThreshold;\n    }\n\n    public DataSize getMaxWorkflowOutputPayloadSizeThreshold() {\n        return maxWorkflowOutputPayloadSizeThreshold;\n    }\n\n    public void setMaxWorkflowOutputPayloadSizeThreshold(\n            DataSize maxWorkflowOutputPayloadSizeThreshold) {\n        this.maxWorkflowOutputPayloadSizeThreshold = maxWorkflowOutputPayloadSizeThreshold;\n    }\n\n    public DataSize getTaskInputPayloadSizeThreshold() {\n        return taskInputPayloadSizeThreshold;\n    }\n\n    public void setTaskInputPayloadSizeThreshold(DataSize taskInputPayloadSizeThreshold) {\n        this.taskInputPayloadSizeThreshold = taskInputPayloadSizeThreshold;\n    }\n\n    public DataSize getMaxTaskInputPayloadSizeThreshold() {\n        return maxTaskInputPayloadSizeThreshold;\n    }\n\n    public void setMaxTaskInputPayloadSizeThreshold(DataSize maxTaskInputPayloadSizeThreshold) {\n        this.maxTaskInputPayloadSizeThreshold = maxTaskInputPayloadSizeThreshold;\n    }\n\n    public DataSize getTaskOutputPayloadSizeThreshold() {\n        return taskOutputPayloadSizeThreshold;\n    }\n\n    public void setTaskOutputPayloadSizeThreshold(DataSize taskOutputPayloadSizeThreshold) {\n        this.taskOutputPayloadSizeThreshold = taskOutputPayloadSizeThreshold;\n    }\n\n    public DataSize getMaxTaskOutputPayloadSizeThreshold() {\n        return maxTaskOutputPayloadSizeThreshold;\n    }\n\n    public void setMaxTaskOutputPayloadSizeThreshold(DataSize maxTaskOutputPayloadSizeThreshold) {\n        this.maxTaskOutputPayloadSizeThreshold = maxTaskOutputPayloadSizeThreshold;\n    }\n\n    public DataSize getMaxWorkflowVariablesPayloadSizeThreshold() {\n        return maxWorkflowVariablesPayloadSizeThreshold;\n    }\n\n    public void setMaxWorkflowVariablesPayloadSizeThreshold(\n            DataSize maxWorkflowVariablesPayloadSizeThreshold) {\n        this.maxWorkflowVariablesPayloadSizeThreshold = maxWorkflowVariablesPayloadSizeThreshold;\n    }\n\n    public int getTaskExecLogSizeLimit() {\n        return taskExecLogSizeLimit;\n    }\n\n    public void setTaskExecLogSizeLimit(int taskExecLogSizeLimit) {\n        this.taskExecLogSizeLimit = taskExecLogSizeLimit;\n    }\n\n    /**\n     * @return Returns all the configurations in a map.\n     */\n    public Map<String, Object> getAll() {\n        Map<String, Object> map = new HashMap<>();\n        Properties props = System.getProperties();\n        props.forEach((key, value) -> map.put(key.toString(), value));\n        return map;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/config/SchedulerConfiguration.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.config;\n\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\n\nimport org.apache.commons.lang3.concurrent.BasicThreadFactory;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.scheduling.annotation.EnableScheduling;\nimport org.springframework.scheduling.annotation.SchedulingConfigurer;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;\nimport org.springframework.scheduling.config.ScheduledTaskRegistrar;\n\nimport rx.Scheduler;\nimport rx.schedulers.Schedulers;\n\n@Configuration(proxyBeanMethods = false)\n@EnableScheduling\n@EnableAsync\npublic class SchedulerConfiguration implements SchedulingConfigurer {\n\n    public static final String SWEEPER_EXECUTOR_NAME = \"WorkflowSweeperExecutor\";\n\n    /**\n     * Used by some {@link com.netflix.conductor.core.events.queue.ObservableQueue} implementations.\n     *\n     * @see com.netflix.conductor.core.events.queue.ConductorObservableQueue\n     */\n    @Bean\n    public Scheduler scheduler(ConductorProperties properties) {\n        ThreadFactory threadFactory =\n                new BasicThreadFactory.Builder()\n                        .namingPattern(\"event-queue-poll-scheduler-thread-%d\")\n                        .build();\n        Executor executorService =\n                Executors.newFixedThreadPool(\n                        properties.getEventQueueSchedulerPollThreadCount(), threadFactory);\n\n        return Schedulers.from(executorService);\n    }\n\n    @Bean(SWEEPER_EXECUTOR_NAME)\n    public Executor sweeperExecutor(ConductorProperties properties) {\n        if (properties.getSweeperThreadCount() <= 0) {\n            throw new IllegalStateException(\n                    \"conductor.app.sweeper-thread-count must be greater than 0.\");\n        }\n        ThreadFactory threadFactory =\n                new BasicThreadFactory.Builder().namingPattern(\"sweeper-thread-%d\").build();\n        return Executors.newFixedThreadPool(properties.getSweeperThreadCount(), threadFactory);\n    }\n\n    @Override\n    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {\n        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();\n        threadPoolTaskScheduler.setPoolSize(3); // equal to the number of scheduled jobs\n        threadPoolTaskScheduler.setThreadNamePrefix(\"scheduled-task-pool-\");\n        threadPoolTaskScheduler.initialize();\n        taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/dal/ExecutionDAOFacade.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.dal;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport javax.annotation.PreDestroy;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.dao.*;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.core.utils.Utils.DECIDER_QUEUE;\n\n/**\n * Service that acts as a facade for accessing execution data from the {@link ExecutionDAO}, {@link\n * RateLimitingDAO} and {@link IndexDAO} storage layers\n */\n@SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n@Component\npublic class ExecutionDAOFacade {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionDAOFacade.class);\n\n    private static final String ARCHIVED_FIELD = \"archived\";\n    private static final String RAW_JSON_FIELD = \"rawJSON\";\n\n    private final ExecutionDAO executionDAO;\n    private final QueueDAO queueDAO;\n    private final IndexDAO indexDAO;\n    private final RateLimitingDAO rateLimitingDao;\n    private final ConcurrentExecutionLimitDAO concurrentExecutionLimitDAO;\n    private final PollDataDAO pollDataDAO;\n    private final ObjectMapper objectMapper;\n    private final ConductorProperties properties;\n    private final ExternalPayloadStorageUtils externalPayloadStorageUtils;\n\n    private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;\n\n    public ExecutionDAOFacade(\n            ExecutionDAO executionDAO,\n            QueueDAO queueDAO,\n            IndexDAO indexDAO,\n            RateLimitingDAO rateLimitingDao,\n            ConcurrentExecutionLimitDAO concurrentExecutionLimitDAO,\n            PollDataDAO pollDataDAO,\n            ObjectMapper objectMapper,\n            ConductorProperties properties,\n            ExternalPayloadStorageUtils externalPayloadStorageUtils) {\n        this.executionDAO = executionDAO;\n        this.queueDAO = queueDAO;\n        this.indexDAO = indexDAO;\n        this.rateLimitingDao = rateLimitingDao;\n        this.concurrentExecutionLimitDAO = concurrentExecutionLimitDAO;\n        this.pollDataDAO = pollDataDAO;\n        this.objectMapper = objectMapper;\n        this.properties = properties;\n        this.externalPayloadStorageUtils = externalPayloadStorageUtils;\n        this.scheduledThreadPoolExecutor =\n                new ScheduledThreadPoolExecutor(\n                        4,\n                        (runnable, executor) -> {\n                            LOGGER.warn(\n                                    \"Request {} to delay updating index dropped in executor {}\",\n                                    runnable,\n                                    executor);\n                            Monitors.recordDiscardedIndexingCount(\"delayQueue\");\n                        });\n        this.scheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true);\n    }\n\n    @PreDestroy\n    public void shutdownExecutorService() {\n        try {\n            LOGGER.info(\"Gracefully shutdown executor service\");\n            scheduledThreadPoolExecutor.shutdown();\n            if (scheduledThreadPoolExecutor.awaitTermination(\n                    properties.getAsyncUpdateDelay().getSeconds(), TimeUnit.SECONDS)) {\n                LOGGER.debug(\"tasks completed, shutting down\");\n            } else {\n                LOGGER.warn(\n                        \"Forcing shutdown after waiting for {} seconds\",\n                        properties.getAsyncUpdateDelay());\n                scheduledThreadPoolExecutor.shutdownNow();\n            }\n        } catch (InterruptedException ie) {\n            LOGGER.warn(\n                    \"Shutdown interrupted, invoking shutdownNow on scheduledThreadPoolExecutor for delay queue\");\n            scheduledThreadPoolExecutor.shutdownNow();\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    public WorkflowModel getWorkflowModel(String workflowId, boolean includeTasks) {\n        WorkflowModel workflowModel = getWorkflowModelFromDataStore(workflowId, includeTasks);\n        populateWorkflowAndTaskPayloadData(workflowModel);\n        return workflowModel;\n    }\n\n    /**\n     * Fetches the {@link Workflow} object from the data store given the id. Attempts to fetch from\n     * {@link ExecutionDAO} first, if not found, attempts to fetch from {@link IndexDAO}.\n     *\n     * @param workflowId the id of the workflow to be fetched\n     * @param includeTasks if true, fetches the {@link Task} data in the workflow.\n     * @return the {@link Workflow} object\n     * @throws NotFoundException no such {@link Workflow} is found.\n     * @throws TransientException parsing the {@link Workflow} object fails.\n     */\n    public Workflow getWorkflow(String workflowId, boolean includeTasks) {\n        return getWorkflowModelFromDataStore(workflowId, includeTasks).toWorkflow();\n    }\n\n    private WorkflowModel getWorkflowModelFromDataStore(String workflowId, boolean includeTasks) {\n        WorkflowModel workflow = executionDAO.getWorkflow(workflowId, includeTasks);\n        if (workflow == null) {\n            LOGGER.debug(\"Workflow {} not found in executionDAO, checking indexDAO\", workflowId);\n            String json = indexDAO.get(workflowId, RAW_JSON_FIELD);\n            if (json == null) {\n                String errorMsg = String.format(\"No such workflow found by id: %s\", workflowId);\n                LOGGER.error(errorMsg);\n                throw new NotFoundException(errorMsg);\n            }\n\n            try {\n                workflow = objectMapper.readValue(json, WorkflowModel.class);\n                if (!includeTasks) {\n                    workflow.getTasks().clear();\n                }\n            } catch (IOException e) {\n                String errorMsg = String.format(\"Error reading workflow: %s\", workflowId);\n                LOGGER.error(errorMsg);\n                throw new TransientException(errorMsg, e);\n            }\n        }\n        return workflow;\n    }\n\n    /**\n     * Retrieve all workflow executions with the given correlationId and workflow type Uses the\n     * {@link IndexDAO} to search across workflows if the {@link ExecutionDAO} cannot perform\n     * searches across workflows.\n     *\n     * @param workflowName, workflow type to be queried\n     * @param correlationId the correlation id to be queried\n     * @param includeTasks if true, fetches the {@link Task} data within the workflows\n     * @return the list of {@link Workflow} executions matching the correlationId\n     */\n    public List<Workflow> getWorkflowsByCorrelationId(\n            String workflowName, String correlationId, boolean includeTasks) {\n        if (!executionDAO.canSearchAcrossWorkflows()) {\n            String query =\n                    \"correlationId='\" + correlationId + \"' AND workflowType='\" + workflowName + \"'\";\n            SearchResult<String> result = indexDAO.searchWorkflows(query, \"*\", 0, 1000, null);\n            return result.getResults().stream()\n                    .parallel()\n                    .map(\n                            workflowId -> {\n                                try {\n                                    return getWorkflow(workflowId, includeTasks);\n                                } catch (NotFoundException e) {\n                                    // This might happen when the workflow archival failed and the\n                                    // workflow was removed from primary datastore\n                                    LOGGER.error(\n                                            \"Error getting the workflow: {}  for correlationId: {} from datastore/index\",\n                                            workflowId,\n                                            correlationId,\n                                            e);\n                                    return null;\n                                }\n                            })\n                    .filter(Objects::nonNull)\n                    .collect(Collectors.toList());\n        }\n        return executionDAO\n                .getWorkflowsByCorrelationId(workflowName, correlationId, includeTasks)\n                .stream()\n                .map(WorkflowModel::toWorkflow)\n                .collect(Collectors.toList());\n    }\n\n    public List<Workflow> getWorkflowsByName(String workflowName, Long startTime, Long endTime) {\n        return executionDAO.getWorkflowsByType(workflowName, startTime, endTime).stream()\n                .map(WorkflowModel::toWorkflow)\n                .collect(Collectors.toList());\n    }\n\n    public List<Workflow> getPendingWorkflowsByName(String workflowName, int version) {\n        return executionDAO.getPendingWorkflowsByType(workflowName, version).stream()\n                .map(WorkflowModel::toWorkflow)\n                .collect(Collectors.toList());\n    }\n\n    public List<String> getRunningWorkflowIds(String workflowName, int version) {\n        return executionDAO.getRunningWorkflowIds(workflowName, version);\n    }\n\n    public long getPendingWorkflowCount(String workflowName) {\n        return executionDAO.getPendingWorkflowCount(workflowName);\n    }\n\n    /**\n     * Creates a new workflow in the data store\n     *\n     * @param workflowModel the workflow to be created\n     * @return the id of the created workflow\n     */\n    public String createWorkflow(WorkflowModel workflowModel) {\n        externalizeWorkflowData(workflowModel);\n        executionDAO.createWorkflow(workflowModel);\n        // Add to decider queue\n        queueDAO.push(\n                DECIDER_QUEUE,\n                workflowModel.getWorkflowId(),\n                workflowModel.getPriority(),\n                properties.getWorkflowOffsetTimeout().getSeconds());\n        if (properties.isAsyncIndexingEnabled()) {\n            indexDAO.asyncIndexWorkflow(new WorkflowSummary(workflowModel.toWorkflow()));\n        } else {\n            indexDAO.indexWorkflow(new WorkflowSummary(workflowModel.toWorkflow()));\n        }\n        return workflowModel.getWorkflowId();\n    }\n\n    private void externalizeTaskData(TaskModel taskModel) {\n        externalPayloadStorageUtils.verifyAndUpload(\n                taskModel, ExternalPayloadStorage.PayloadType.TASK_INPUT);\n        externalPayloadStorageUtils.verifyAndUpload(\n                taskModel, ExternalPayloadStorage.PayloadType.TASK_OUTPUT);\n    }\n\n    private void externalizeWorkflowData(WorkflowModel workflowModel) {\n        externalPayloadStorageUtils.verifyAndUpload(\n                workflowModel, ExternalPayloadStorage.PayloadType.WORKFLOW_INPUT);\n        externalPayloadStorageUtils.verifyAndUpload(\n                workflowModel, ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT);\n    }\n\n    /**\n     * Updates the given workflow in the data store\n     *\n     * @param workflowModel the workflow tp be updated\n     * @return the id of the updated workflow\n     */\n    public String updateWorkflow(WorkflowModel workflowModel) {\n        workflowModel.setUpdatedTime(System.currentTimeMillis());\n        if (workflowModel.getStatus().isTerminal()) {\n            workflowModel.setEndTime(System.currentTimeMillis());\n        }\n        externalizeWorkflowData(workflowModel);\n        executionDAO.updateWorkflow(workflowModel);\n        if (properties.isAsyncIndexingEnabled()) {\n            if (workflowModel.getStatus().isTerminal()\n                    && workflowModel.getEndTime() - workflowModel.getCreateTime()\n                            < properties.getAsyncUpdateShortRunningWorkflowDuration().toMillis()) {\n                final String workflowId = workflowModel.getWorkflowId();\n                DelayWorkflowUpdate delayWorkflowUpdate = new DelayWorkflowUpdate(workflowId);\n                LOGGER.debug(\n                        \"Delayed updating workflow: {} in the index by {} seconds\",\n                        workflowId,\n                        properties.getAsyncUpdateDelay());\n                scheduledThreadPoolExecutor.schedule(\n                        delayWorkflowUpdate,\n                        properties.getAsyncUpdateDelay().getSeconds(),\n                        TimeUnit.SECONDS);\n                Monitors.recordWorkerQueueSize(\n                        \"delayQueue\", scheduledThreadPoolExecutor.getQueue().size());\n            } else {\n                indexDAO.asyncIndexWorkflow(new WorkflowSummary(workflowModel.toWorkflow()));\n            }\n            if (workflowModel.getStatus().isTerminal()) {\n                workflowModel\n                        .getTasks()\n                        .forEach(\n                                taskModel ->\n                                        indexDAO.asyncIndexTask(\n                                                new TaskSummary(taskModel.toTask())));\n            }\n        } else {\n            indexDAO.indexWorkflow(new WorkflowSummary(workflowModel.toWorkflow()));\n        }\n        return workflowModel.getWorkflowId();\n    }\n\n    public void removeFromPendingWorkflow(String workflowType, String workflowId) {\n        executionDAO.removeFromPendingWorkflow(workflowType, workflowId);\n    }\n\n    /**\n     * Removes the workflow from the data store.\n     *\n     * @param workflowId the id of the workflow to be removed\n     * @param archiveWorkflow if true, the workflow and associated tasks will be archived in the\n     *     {@link IndexDAO} after removal from {@link ExecutionDAO}.\n     */\n    public void removeWorkflow(String workflowId, boolean archiveWorkflow) {\n        WorkflowModel workflow = getWorkflowModelFromDataStore(workflowId, true);\n\n        executionDAO.removeWorkflow(workflowId);\n        try {\n            removeWorkflowIndex(workflow, archiveWorkflow);\n        } catch (JsonProcessingException e) {\n            throw new TransientException(\"Workflow can not be serialized to json\", e);\n        }\n\n        workflow.getTasks()\n                .forEach(\n                        task -> {\n                            try {\n                                removeTaskIndex(workflow, task, archiveWorkflow);\n                            } catch (JsonProcessingException e) {\n                                throw new TransientException(\n                                        String.format(\n                                                \"Task %s of workflow %s can not be serialized to json\",\n                                                task.getTaskId(), workflow.getWorkflowId()),\n                                        e);\n                            }\n\n                            try {\n                                queueDAO.remove(QueueUtils.getQueueName(task), task.getTaskId());\n                            } catch (Exception e) {\n                                LOGGER.info(\n                                        \"Error removing task: {} of workflow: {} from {} queue\",\n                                        workflowId,\n                                        task.getTaskId(),\n                                        QueueUtils.getQueueName(task),\n                                        e);\n                            }\n                        });\n\n        try {\n            queueDAO.remove(DECIDER_QUEUE, workflowId);\n        } catch (Exception e) {\n            LOGGER.info(\"Error removing workflow: {} from decider queue\", workflowId, e);\n        }\n    }\n\n    private void removeWorkflowIndex(WorkflowModel workflow, boolean archiveWorkflow)\n            throws JsonProcessingException {\n        if (archiveWorkflow) {\n            if (workflow.getStatus().isTerminal()) {\n                // Only allow archival if workflow is in terminal state\n                // DO NOT archive async, since if archival errors out, workflow data will be lost\n                indexDAO.updateWorkflow(\n                        workflow.getWorkflowId(),\n                        new String[] {RAW_JSON_FIELD, ARCHIVED_FIELD},\n                        new Object[] {objectMapper.writeValueAsString(workflow), true});\n            } else {\n                throw new IllegalArgumentException(\n                        String.format(\n                                \"Cannot archive workflow: %s with status: %s\",\n                                workflow.getWorkflowId(), workflow.getStatus()));\n            }\n        } else {\n            // Not archiving, also remove workflow from index\n            indexDAO.asyncRemoveWorkflow(workflow.getWorkflowId());\n        }\n    }\n\n    public void removeWorkflowWithExpiry(\n            String workflowId, boolean archiveWorkflow, int ttlSeconds) {\n        try {\n            WorkflowModel workflow = getWorkflowModelFromDataStore(workflowId, true);\n\n            removeWorkflowIndex(workflow, archiveWorkflow);\n            // remove workflow from DAO with TTL\n            executionDAO.removeWorkflowWithExpiry(workflowId, ttlSeconds);\n        } catch (Exception e) {\n            Monitors.recordDaoError(\"executionDao\", \"removeWorkflow\");\n            throw new TransientException(\"Error removing workflow: \" + workflowId, e);\n        }\n    }\n\n    /**\n     * Reset the workflow state by removing from the {@link ExecutionDAO} and removing this workflow\n     * from the {@link IndexDAO}.\n     *\n     * @param workflowId the workflow id to be reset\n     */\n    public void resetWorkflow(String workflowId) {\n        getWorkflowModelFromDataStore(workflowId, true);\n        executionDAO.removeWorkflow(workflowId);\n        try {\n            if (properties.isAsyncIndexingEnabled()) {\n                indexDAO.asyncRemoveWorkflow(workflowId);\n            } else {\n                indexDAO.removeWorkflow(workflowId);\n            }\n        } catch (Exception e) {\n            throw new TransientException(\"Error resetting workflow state: \" + workflowId, e);\n        }\n    }\n\n    public List<TaskModel> createTasks(List<TaskModel> tasks) {\n        tasks.forEach(this::externalizeTaskData);\n        return executionDAO.createTasks(tasks);\n    }\n\n    public List<Task> getTasksForWorkflow(String workflowId) {\n        return executionDAO.getTasksForWorkflow(workflowId).stream()\n                .map(TaskModel::toTask)\n                .collect(Collectors.toList());\n    }\n\n    public TaskModel getTaskModel(String taskId) {\n        TaskModel taskModel = getTaskFromDatastore(taskId);\n        if (taskModel != null) {\n            populateTaskData(taskModel);\n        }\n        return taskModel;\n    }\n\n    public Task getTask(String taskId) {\n        TaskModel taskModel = getTaskFromDatastore(taskId);\n        if (taskModel != null) {\n            return taskModel.toTask();\n        }\n        return null;\n    }\n\n    private TaskModel getTaskFromDatastore(String taskId) {\n        return executionDAO.getTask(taskId);\n    }\n\n    public List<Task> getTasksByName(String taskName, String startKey, int count) {\n        return executionDAO.getTasks(taskName, startKey, count).stream()\n                .map(TaskModel::toTask)\n                .collect(Collectors.toList());\n    }\n\n    public List<Task> getPendingTasksForTaskType(String taskType) {\n        return executionDAO.getPendingTasksForTaskType(taskType).stream()\n                .map(TaskModel::toTask)\n                .collect(Collectors.toList());\n    }\n\n    public long getInProgressTaskCount(String taskDefName) {\n        return executionDAO.getInProgressTaskCount(taskDefName);\n    }\n\n    /**\n     * Sets the update time for the task. Sets the end time for the task (if task is in terminal\n     * state and end time is not set). Updates the task in the {@link ExecutionDAO} first, then\n     * stores it in the {@link IndexDAO}.\n     *\n     * @param taskModel the task to be updated in the data store\n     * @throws TransientException if the {@link IndexDAO} or {@link ExecutionDAO} operations fail.\n     * @throws com.netflix.conductor.core.exception.NonTransientException if the externalization of\n     *     payload fails.\n     */\n    public void updateTask(TaskModel taskModel) {\n        if (taskModel.getStatus() != null) {\n            if (!taskModel.getStatus().isTerminal()\n                    || (taskModel.getStatus().isTerminal() && taskModel.getUpdateTime() == 0)) {\n                taskModel.setUpdateTime(System.currentTimeMillis());\n            }\n            if (taskModel.getStatus().isTerminal() && taskModel.getEndTime() == 0) {\n                taskModel.setEndTime(System.currentTimeMillis());\n            }\n        }\n        externalizeTaskData(taskModel);\n        executionDAO.updateTask(taskModel);\n        try {\n            /*\n             * Indexing a task for every update adds a lot of volume. That is ok but if async indexing\n             * is enabled and tasks are stored in memory until a block has completed, we would lose a lot\n             * of tasks on a system failure. So only index for each update if async indexing is not enabled.\n             * If it *is* enabled, tasks will be indexed only when a workflow is in terminal state.\n             */\n            if (!properties.isAsyncIndexingEnabled()) {\n                indexDAO.indexTask(new TaskSummary(taskModel.toTask()));\n            }\n        } catch (TerminateWorkflowException e) {\n            // re-throw it so we can terminate the workflow\n            throw e;\n        } catch (Exception e) {\n            String errorMsg =\n                    String.format(\n                            \"Error updating task: %s in workflow: %s\",\n                            taskModel.getTaskId(), taskModel.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    public void updateTasks(List<TaskModel> tasks) {\n        tasks.forEach(this::updateTask);\n    }\n\n    public void removeTask(String taskId) {\n        executionDAO.removeTask(taskId);\n    }\n\n    private void removeTaskIndex(WorkflowModel workflow, TaskModel task, boolean archiveTask)\n            throws JsonProcessingException {\n        if (archiveTask) {\n            if (task.getStatus().isTerminal()) {\n                // Only allow archival if task is in terminal state\n                // DO NOT archive async, since if archival errors out, task data will be lost\n                indexDAO.updateTask(\n                        workflow.getWorkflowId(),\n                        task.getTaskId(),\n                        new String[] {ARCHIVED_FIELD},\n                        new Object[] {true});\n            } else {\n                throw new IllegalArgumentException(\n                        String.format(\n                                \"Cannot archive task: %s of workflow: %s with status: %s\",\n                                task.getTaskId(), workflow.getWorkflowId(), task.getStatus()));\n            }\n        } else {\n            // Not archiving, remove task from index\n            indexDAO.asyncRemoveTask(workflow.getWorkflowId(), task.getTaskId());\n        }\n    }\n\n    public void extendLease(TaskModel taskModel) {\n        taskModel.setUpdateTime(System.currentTimeMillis());\n        executionDAO.updateTask(taskModel);\n    }\n\n    public List<PollData> getTaskPollData(String taskName) {\n        return pollDataDAO.getPollData(taskName);\n    }\n\n    public List<PollData> getAllPollData() {\n        return pollDataDAO.getAllPollData();\n    }\n\n    public PollData getTaskPollDataByDomain(String taskName, String domain) {\n        try {\n            return pollDataDAO.getPollData(taskName, domain);\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Error fetching pollData for task: '{}', domain: '{}'\", taskName, domain, e);\n            return null;\n        }\n    }\n\n    public void updateTaskLastPoll(String taskName, String domain, String workerId) {\n        try {\n            pollDataDAO.updateLastPollData(taskName, domain, workerId);\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Error updating PollData for task: {} in domain: {} from worker: {}\",\n                    taskName,\n                    domain,\n                    workerId,\n                    e);\n            Monitors.error(this.getClass().getCanonicalName(), \"updateTaskLastPoll\");\n        }\n    }\n\n    /**\n     * Save the {@link EventExecution} to the data store Saves to {@link ExecutionDAO} first, if\n     * this succeeds then saves to the {@link IndexDAO}.\n     *\n     * @param eventExecution the {@link EventExecution} to be saved\n     * @return true if save succeeds, false otherwise.\n     */\n    public boolean addEventExecution(EventExecution eventExecution) {\n        boolean added = executionDAO.addEventExecution(eventExecution);\n\n        if (added) {\n            indexEventExecution(eventExecution);\n        }\n\n        return added;\n    }\n\n    public void updateEventExecution(EventExecution eventExecution) {\n        executionDAO.updateEventExecution(eventExecution);\n        indexEventExecution(eventExecution);\n    }\n\n    private void indexEventExecution(EventExecution eventExecution) {\n        if (properties.isEventExecutionIndexingEnabled()) {\n            if (properties.isAsyncIndexingEnabled()) {\n                indexDAO.asyncAddEventExecution(eventExecution);\n            } else {\n                indexDAO.addEventExecution(eventExecution);\n            }\n        }\n    }\n\n    public void removeEventExecution(EventExecution eventExecution) {\n        executionDAO.removeEventExecution(eventExecution);\n    }\n\n    public boolean exceedsInProgressLimit(TaskModel task) {\n        return concurrentExecutionLimitDAO.exceedsLimit(task);\n    }\n\n    public boolean exceedsRateLimitPerFrequency(TaskModel task, TaskDef taskDef) {\n        return rateLimitingDao.exceedsRateLimitPerFrequency(task, taskDef);\n    }\n\n    public void addTaskExecLog(List<TaskExecLog> logs) {\n        if (properties.isTaskExecLogIndexingEnabled() && !logs.isEmpty()) {\n            Monitors.recordTaskExecLogSize(logs.size());\n            int taskExecLogSizeLimit = properties.getTaskExecLogSizeLimit();\n            if (logs.size() > taskExecLogSizeLimit) {\n                LOGGER.warn(\n                        \"Task Execution log size: {} for taskId: {} exceeds the limit: {}\",\n                        logs.size(),\n                        logs.get(0).getTaskId(),\n                        taskExecLogSizeLimit);\n                logs = logs.stream().limit(taskExecLogSizeLimit).collect(Collectors.toList());\n            }\n            if (properties.isAsyncIndexingEnabled()) {\n                indexDAO.asyncAddTaskExecutionLogs(logs);\n            } else {\n                indexDAO.addTaskExecutionLogs(logs);\n            }\n        }\n    }\n\n    public void addMessage(String queue, Message message) {\n        if (properties.isAsyncIndexingEnabled()) {\n            indexDAO.asyncAddMessage(queue, message);\n        } else {\n            indexDAO.addMessage(queue, message);\n        }\n    }\n\n    public SearchResult<String> searchWorkflows(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return indexDAO.searchWorkflows(query, freeText, start, count, sort);\n    }\n\n    public SearchResult<WorkflowSummary> searchWorkflowSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return indexDAO.searchWorkflowSummary(query, freeText, start, count, sort);\n    }\n\n    public SearchResult<String> searchTasks(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return indexDAO.searchTasks(query, freeText, start, count, sort);\n    }\n\n    public SearchResult<TaskSummary> searchTaskSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return indexDAO.searchTaskSummary(query, freeText, start, count, sort);\n    }\n\n    public List<TaskExecLog> getTaskExecutionLogs(String taskId) {\n        return properties.isTaskExecLogIndexingEnabled()\n                ? indexDAO.getTaskExecutionLogs(taskId)\n                : Collections.emptyList();\n    }\n\n    /**\n     * Populates the workflow input data and the tasks input/output data if stored in external\n     * payload storage.\n     *\n     * @param workflowModel the workflowModel for which the payload data needs to be populated from\n     *     external storage (if applicable)\n     */\n    public void populateWorkflowAndTaskPayloadData(WorkflowModel workflowModel) {\n        if (StringUtils.isNotBlank(workflowModel.getExternalInputPayloadStoragePath())) {\n            Map<String, Object> workflowInputParams =\n                    externalPayloadStorageUtils.downloadPayload(\n                            workflowModel.getExternalInputPayloadStoragePath());\n            Monitors.recordExternalPayloadStorageUsage(\n                    workflowModel.getWorkflowName(),\n                    ExternalPayloadStorage.Operation.READ.toString(),\n                    ExternalPayloadStorage.PayloadType.WORKFLOW_INPUT.toString());\n            workflowModel.internalizeInput(workflowInputParams);\n        }\n\n        if (StringUtils.isNotBlank(workflowModel.getExternalOutputPayloadStoragePath())) {\n            Map<String, Object> workflowOutputParams =\n                    externalPayloadStorageUtils.downloadPayload(\n                            workflowModel.getExternalOutputPayloadStoragePath());\n            Monitors.recordExternalPayloadStorageUsage(\n                    workflowModel.getWorkflowName(),\n                    ExternalPayloadStorage.Operation.READ.toString(),\n                    ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT.toString());\n            workflowModel.internalizeOutput(workflowOutputParams);\n        }\n\n        workflowModel.getTasks().forEach(this::populateTaskData);\n    }\n\n    public void populateTaskData(TaskModel taskModel) {\n        if (StringUtils.isNotBlank(taskModel.getExternalOutputPayloadStoragePath())) {\n            Map<String, Object> outputData =\n                    externalPayloadStorageUtils.downloadPayload(\n                            taskModel.getExternalOutputPayloadStoragePath());\n            taskModel.internalizeOutput(outputData);\n            Monitors.recordExternalPayloadStorageUsage(\n                    taskModel.getTaskDefName(),\n                    ExternalPayloadStorage.Operation.READ.toString(),\n                    ExternalPayloadStorage.PayloadType.TASK_OUTPUT.toString());\n        }\n\n        if (StringUtils.isNotBlank(taskModel.getExternalInputPayloadStoragePath())) {\n            Map<String, Object> inputData =\n                    externalPayloadStorageUtils.downloadPayload(\n                            taskModel.getExternalInputPayloadStoragePath());\n            taskModel.internalizeInput(inputData);\n            Monitors.recordExternalPayloadStorageUsage(\n                    taskModel.getTaskDefName(),\n                    ExternalPayloadStorage.Operation.READ.toString(),\n                    ExternalPayloadStorage.PayloadType.TASK_INPUT.toString());\n        }\n    }\n\n    class DelayWorkflowUpdate implements Runnable {\n\n        private final String workflowId;\n\n        DelayWorkflowUpdate(String workflowId) {\n            this.workflowId = workflowId;\n        }\n\n        @Override\n        public void run() {\n            try {\n                WorkflowModel workflowModel = executionDAO.getWorkflow(workflowId, false);\n                indexDAO.asyncIndexWorkflow(new WorkflowSummary(workflowModel.toWorkflow()));\n            } catch (Exception e) {\n                LOGGER.error(\"Unable to update workflow: {}\", workflowId, e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/event/WorkflowCreationEvent.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.event;\n\nimport java.io.Serializable;\n\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\n\npublic class WorkflowCreationEvent implements Serializable {\n\n    private final StartWorkflowInput startWorkflowInput;\n\n    public WorkflowCreationEvent(StartWorkflowInput startWorkflowInput) {\n        this.startWorkflowInput = startWorkflowInput;\n    }\n\n    public StartWorkflowInput getStartWorkflowInput() {\n        return startWorkflowInput;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/event/WorkflowEvaluationEvent.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.event;\n\nimport java.io.Serializable;\n\nimport com.netflix.conductor.model.WorkflowModel;\n\npublic final class WorkflowEvaluationEvent implements Serializable {\n\n    private final WorkflowModel workflowModel;\n\n    public WorkflowEvaluationEvent(WorkflowModel workflowModel) {\n        this.workflowModel = workflowModel;\n    }\n\n    public WorkflowModel getWorkflowModel() {\n        return workflowModel;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/ActionProcessor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.Map;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\n\npublic interface ActionProcessor {\n\n    Map<String, Object> execute(\n            EventHandler.Action action, Object payloadObject, String event, String messageId);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/DefaultEventProcessor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.concurrent.BasicThreadFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.retry.support.RetryTemplate;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.CollectionUtils;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.events.EventExecution.Status;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.core.utils.JsonUtils;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.service.ExecutionService;\nimport com.netflix.conductor.service.MetadataService;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.spotify.futures.CompletableFutures;\n\nimport static com.netflix.conductor.core.utils.Utils.isTransientException;\n\n/**\n * Event Processor is used to dispatch actions configured in the event handlers, based on incoming\n * events to the event queues.\n *\n * <p><code>Set conductor.default-event-processor.enabled=false</code> to disable event processing.\n */\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.default-event-processor.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class DefaultEventProcessor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultEventProcessor.class);\n\n    private final MetadataService metadataService;\n    private final ExecutionService executionService;\n    private final ActionProcessor actionProcessor;\n\n    private final ExecutorService eventActionExecutorService;\n    private final ObjectMapper objectMapper;\n    private final JsonUtils jsonUtils;\n    private final boolean isEventMessageIndexingEnabled;\n    private final Map<String, Evaluator> evaluators;\n    private final RetryTemplate retryTemplate;\n\n    public DefaultEventProcessor(\n            ExecutionService executionService,\n            MetadataService metadataService,\n            ActionProcessor actionProcessor,\n            JsonUtils jsonUtils,\n            ConductorProperties properties,\n            ObjectMapper objectMapper,\n            Map<String, Evaluator> evaluators,\n            @Qualifier(\"onTransientErrorRetryTemplate\") RetryTemplate retryTemplate) {\n        this.executionService = executionService;\n        this.metadataService = metadataService;\n        this.actionProcessor = actionProcessor;\n        this.objectMapper = objectMapper;\n        this.jsonUtils = jsonUtils;\n        this.evaluators = evaluators;\n        this.retryTemplate = retryTemplate;\n\n        if (properties.getEventProcessorThreadCount() <= 0) {\n            throw new IllegalStateException(\n                    \"Cannot set event processor thread count to <=0. To disable event \"\n                            + \"processing, set conductor.default-event-processor.enabled=false.\");\n        }\n        ThreadFactory threadFactory =\n                new BasicThreadFactory.Builder()\n                        .namingPattern(\"event-action-executor-thread-%d\")\n                        .build();\n        eventActionExecutorService =\n                Executors.newFixedThreadPool(\n                        properties.getEventProcessorThreadCount(), threadFactory);\n\n        this.isEventMessageIndexingEnabled = properties.isEventMessageIndexingEnabled();\n        LOGGER.info(\"Event Processing is ENABLED\");\n    }\n\n    public void handle(ObservableQueue queue, Message msg) {\n        List<EventExecution> transientFailures = null;\n        boolean executionFailed = false;\n        try {\n            if (isEventMessageIndexingEnabled) {\n                executionService.addMessage(queue.getName(), msg);\n            }\n            String event = queue.getType() + \":\" + queue.getName();\n            LOGGER.debug(\"Evaluating message: {} for event: {}\", msg.getId(), event);\n            transientFailures = executeEvent(event, msg);\n        } catch (Exception e) {\n            executionFailed = true;\n            LOGGER.error(\"Error handling message: {} on queue:{}\", msg, queue.getName(), e);\n            Monitors.recordEventQueueMessagesError(queue.getType(), queue.getName());\n        } finally {\n            if (!executionFailed && CollectionUtils.isEmpty(transientFailures)) {\n                queue.ack(Collections.singletonList(msg));\n                LOGGER.debug(\"Message: {} acked on queue: {}\", msg.getId(), queue.getName());\n            } else if (queue.rePublishIfNoAck() || !CollectionUtils.isEmpty(transientFailures)) {\n                // re-submit this message to the queue, to be retried later\n                // This is needed for queues with no unack timeout, since messages are removed\n                // from the queue\n                queue.publish(Collections.singletonList(msg));\n                LOGGER.debug(\"Message: {} published to queue: {}\", msg.getId(), queue.getName());\n            } else {\n                queue.nack(Collections.singletonList(msg));\n                LOGGER.debug(\"Message: {} nacked on queue: {}\", msg.getId(), queue.getName());\n            }\n            Monitors.recordEventQueueMessagesHandled(queue.getType(), queue.getName());\n        }\n    }\n\n    /**\n     * Executes all the actions configured on all the event handlers triggered by the {@link\n     * Message} on the queue If any of the actions on an event handler fails due to a transient\n     * failure, the execution is not persisted such that it can be retried\n     *\n     * @return a list of {@link EventExecution} that failed due to transient failures.\n     */\n    protected List<EventExecution> executeEvent(String event, Message msg) throws Exception {\n        List<EventHandler> eventHandlerList;\n        List<EventExecution> transientFailures = new ArrayList<>();\n\n        try {\n            eventHandlerList = metadataService.getEventHandlersForEvent(event, true);\n        } catch (TransientException transientException) {\n            transientFailures.add(new EventExecution(event, msg.getId()));\n            return transientFailures;\n        }\n\n        Object payloadObject = getPayloadObject(msg.getPayload());\n        for (EventHandler eventHandler : eventHandlerList) {\n            String condition = eventHandler.getCondition();\n            String evaluatorType = eventHandler.getEvaluatorType();\n            // Set default to true so that if condition is not specified, it falls through\n            // to process the event.\n            boolean success = true;\n            if (StringUtils.isNotEmpty(condition) && evaluators.get(evaluatorType) != null) {\n                Object result =\n                        evaluators\n                                .get(evaluatorType)\n                                .evaluate(condition, jsonUtils.expand(payloadObject));\n                success = ScriptEvaluator.toBoolean(result);\n            } else if (StringUtils.isNotEmpty(condition)) {\n                LOGGER.debug(\"Checking condition: {} for event: {}\", condition, event);\n                success = ScriptEvaluator.evalBool(condition, jsonUtils.expand(payloadObject));\n            }\n\n            if (!success) {\n                String id = msg.getId() + \"_\" + 0;\n                EventExecution eventExecution = new EventExecution(id, msg.getId());\n                eventExecution.setCreated(System.currentTimeMillis());\n                eventExecution.setEvent(eventHandler.getEvent());\n                eventExecution.setName(eventHandler.getName());\n                eventExecution.setStatus(Status.SKIPPED);\n                eventExecution.getOutput().put(\"msg\", msg.getPayload());\n                eventExecution.getOutput().put(\"condition\", condition);\n                executionService.addEventExecution(eventExecution);\n                LOGGER.debug(\n                        \"Condition: {} not successful for event: {} with payload: {}\",\n                        condition,\n                        eventHandler.getEvent(),\n                        msg.getPayload());\n                continue;\n            }\n\n            CompletableFuture<List<EventExecution>> future =\n                    executeActionsForEventHandler(eventHandler, msg);\n            future.whenComplete(\n                            (result, error) ->\n                                    result.forEach(\n                                            eventExecution -> {\n                                                if (error != null\n                                                        || eventExecution.getStatus()\n                                                                == Status.IN_PROGRESS) {\n                                                    transientFailures.add(eventExecution);\n                                                } else {\n                                                    executionService.updateEventExecution(\n                                                            eventExecution);\n                                                }\n                                            }))\n                    .get();\n        }\n        return processTransientFailures(transientFailures);\n    }\n\n    /**\n     * Remove the event executions which failed temporarily.\n     *\n     * @param eventExecutions The event executions which failed with a transient error.\n     * @return The event executions which failed with a transient error.\n     */\n    protected List<EventExecution> processTransientFailures(List<EventExecution> eventExecutions) {\n        eventExecutions.forEach(executionService::removeEventExecution);\n        return eventExecutions;\n    }\n\n    /**\n     * @param eventHandler the {@link EventHandler} for which the actions are to be executed\n     * @param msg the {@link Message} that triggered the event\n     * @return a {@link CompletableFuture} holding a list of {@link EventExecution}s for the {@link\n     *     Action}s executed in the event handler\n     */\n    protected CompletableFuture<List<EventExecution>> executeActionsForEventHandler(\n            EventHandler eventHandler, Message msg) {\n        List<CompletableFuture<EventExecution>> futuresList = new ArrayList<>();\n        int i = 0;\n        for (Action action : eventHandler.getActions()) {\n            String id = msg.getId() + \"_\" + i++;\n            EventExecution eventExecution = new EventExecution(id, msg.getId());\n            eventExecution.setCreated(System.currentTimeMillis());\n            eventExecution.setEvent(eventHandler.getEvent());\n            eventExecution.setName(eventHandler.getName());\n            eventExecution.setAction(action.getAction());\n            eventExecution.setStatus(Status.IN_PROGRESS);\n            if (executionService.addEventExecution(eventExecution)) {\n                futuresList.add(\n                        CompletableFuture.supplyAsync(\n                                () ->\n                                        execute(\n                                                eventExecution,\n                                                action,\n                                                getPayloadObject(msg.getPayload())),\n                                eventActionExecutorService));\n            } else {\n                LOGGER.warn(\"Duplicate delivery/execution of message: {}\", msg.getId());\n            }\n        }\n        return CompletableFutures.allAsList(futuresList);\n    }\n\n    /**\n     * @param eventExecution the instance of {@link EventExecution}\n     * @param action the {@link Action} to be executed for the event\n     * @param payload the {@link Message#getPayload()}\n     * @return the event execution updated with execution output, if the execution is\n     *     completed/failed with non-transient error the input event execution, if the execution\n     *     failed due to transient error\n     */\n    protected EventExecution execute(EventExecution eventExecution, Action action, Object payload) {\n        try {\n            LOGGER.debug(\n                    \"Executing action: {} for event: {} with messageId: {} with payload: {}\",\n                    action.getAction(),\n                    eventExecution.getId(),\n                    eventExecution.getMessageId(),\n                    payload);\n\n            // TODO: Switch to @Retryable annotation on SimpleActionProcessor.execute()\n            Map<String, Object> output =\n                    retryTemplate.execute(\n                            context ->\n                                    actionProcessor.execute(\n                                            action,\n                                            payload,\n                                            eventExecution.getEvent(),\n                                            eventExecution.getMessageId()));\n            if (output != null) {\n                eventExecution.getOutput().putAll(output);\n            }\n            eventExecution.setStatus(Status.COMPLETED);\n            Monitors.recordEventExecutionSuccess(\n                    eventExecution.getEvent(),\n                    eventExecution.getName(),\n                    eventExecution.getAction().name());\n        } catch (RuntimeException e) {\n            LOGGER.error(\n                    \"Error executing action: {} for event: {} with messageId: {}\",\n                    action.getAction(),\n                    eventExecution.getEvent(),\n                    eventExecution.getMessageId(),\n                    e);\n            if (!isTransientException(e)) {\n                // not a transient error, fail the event execution\n                eventExecution.setStatus(Status.FAILED);\n                eventExecution.getOutput().put(\"exception\", e.getMessage());\n                Monitors.recordEventExecutionError(\n                        eventExecution.getEvent(),\n                        eventExecution.getName(),\n                        eventExecution.getAction().name(),\n                        e.getClass().getSimpleName());\n            }\n        }\n        return eventExecution;\n    }\n\n    private Object getPayloadObject(String payload) {\n        Object payloadObject = null;\n        if (payload != null) {\n            try {\n                payloadObject = objectMapper.readValue(payload, Object.class);\n            } catch (Exception e) {\n                payloadObject = payload;\n            }\n        }\n        return payloadObject;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/DefaultEventQueueManager.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.Lifecycle;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.core.LifecycleAwareComponent;\nimport com.netflix.conductor.core.events.queue.DefaultEventQueueProcessor;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.dao.EventHandlerDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel.Status;\n\n/**\n * Manages the event queues registered in the system and sets up listeners for these.\n *\n * <p>Manages the lifecycle of -\n *\n * <ul>\n *   <li>Queues registered with event handlers\n *   <li>Default event queues that Conductor listens on\n * </ul>\n *\n * @see DefaultEventQueueProcessor\n */\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.default-event-processor.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class DefaultEventQueueManager extends LifecycleAwareComponent implements EventQueueManager {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultEventQueueManager.class);\n\n    private final EventHandlerDAO eventHandlerDAO;\n    private final EventQueues eventQueues;\n    private final DefaultEventProcessor defaultEventProcessor;\n    private final Map<String, ObservableQueue> eventToQueueMap = new ConcurrentHashMap<>();\n    private final Map<Status, ObservableQueue> defaultQueues;\n\n    public DefaultEventQueueManager(\n            Map<Status, ObservableQueue> defaultQueues,\n            EventHandlerDAO eventHandlerDAO,\n            EventQueues eventQueues,\n            DefaultEventProcessor defaultEventProcessor) {\n        this.defaultQueues = defaultQueues;\n        this.eventHandlerDAO = eventHandlerDAO;\n        this.eventQueues = eventQueues;\n        this.defaultEventProcessor = defaultEventProcessor;\n    }\n\n    /**\n     * @return Returns a map of queues which are active. Key is event name and value is queue URI\n     */\n    @Override\n    public Map<String, String> getQueues() {\n        Map<String, String> queues = new HashMap<>();\n        eventToQueueMap.forEach((key, value) -> queues.put(key, value.getName()));\n        return queues;\n    }\n\n    @Override\n    public Map<String, Map<String, Long>> getQueueSizes() {\n        Map<String, Map<String, Long>> queues = new HashMap<>();\n        eventToQueueMap.forEach(\n                (key, value) -> {\n                    Map<String, Long> size = new HashMap<>();\n                    size.put(value.getName(), value.size());\n                    queues.put(key, size);\n                });\n        return queues;\n    }\n\n    @Override\n    public void doStart() {\n        eventToQueueMap.forEach(\n                (event, queue) -> {\n                    LOGGER.info(\"Start listening for events: {}\", event);\n                    queue.start();\n                });\n        defaultQueues.forEach(\n                (status, queue) -> {\n                    LOGGER.info(\n                            \"Start listening on default queue {} for status {}\",\n                            queue.getName(),\n                            status);\n                    queue.start();\n                });\n    }\n\n    @Override\n    public void doStop() {\n        eventToQueueMap.forEach(\n                (event, queue) -> {\n                    LOGGER.info(\"Stop listening for events: {}\", event);\n                    queue.stop();\n                });\n        defaultQueues.forEach(\n                (status, queue) -> {\n                    LOGGER.info(\n                            \"Stop listening on default queue {} for status {}\",\n                            status,\n                            queue.getName());\n                    queue.stop();\n                });\n    }\n\n    @Scheduled(fixedDelay = 60_000)\n    public void refreshEventQueues() {\n        try {\n            Set<String> events =\n                    eventHandlerDAO.getAllEventHandlers().stream()\n                            .filter(EventHandler::isActive)\n                            .map(EventHandler::getEvent)\n                            .collect(Collectors.toSet());\n\n            List<ObservableQueue> createdQueues = new LinkedList<>();\n            events.forEach(\n                    event ->\n                            eventToQueueMap.computeIfAbsent(\n                                    event,\n                                    s -> {\n                                        ObservableQueue q = eventQueues.getQueue(event);\n                                        createdQueues.add(q);\n                                        return q;\n                                    }));\n\n            // start listening on all of the created queues\n            createdQueues.stream()\n                    .filter(Objects::nonNull)\n                    .peek(Lifecycle::start)\n                    .forEach(this::listen);\n\n            Set<String> removed = new HashSet<>(eventToQueueMap.keySet());\n            removed.removeAll(events);\n            removed.forEach(\n                    key -> {\n                        ObservableQueue queue = eventToQueueMap.remove(key);\n                        try {\n                            queue.stop();\n                        } catch (Exception e) {\n                            LOGGER.error(\"Failed to stop queue: \" + queue, e);\n                        }\n                    });\n\n            LOGGER.debug(\"Event queues: {}\", eventToQueueMap.keySet());\n            LOGGER.debug(\"Stored queue: {}\", events);\n            LOGGER.debug(\"Removed queue: {}\", removed);\n\n        } catch (Exception e) {\n            Monitors.error(getClass().getSimpleName(), \"refresh\");\n            LOGGER.error(\"refresh event queues failed\", e);\n        }\n    }\n\n    private void listen(ObservableQueue queue) {\n        queue.observe().subscribe((Message msg) -> defaultEventProcessor.handle(queue, msg));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/EventQueueManager.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.Map;\n\npublic interface EventQueueManager {\n\n    Map<String, String> getQueues();\n\n    Map<String, Map<String, Long>> getQueueSizes();\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/EventQueueProvider.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport org.springframework.lang.NonNull;\n\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\n\npublic interface EventQueueProvider {\n\n    String getQueueType();\n\n    /**\n     * Creates or reads the {@link ObservableQueue} for the given <code>queueURI</code>.\n     *\n     * @param queueURI The URI of the queue.\n     * @return The {@link ObservableQueue} implementation for the <code>queueURI</code>.\n     * @throws IllegalArgumentException thrown when an {@link ObservableQueue} can not be created\n     *     for the <code>queueURI</code>.\n     */\n    @NonNull\n    ObservableQueue getQueue(String queueURI) throws IllegalArgumentException;\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/EventQueues.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.lang.NonNull;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.core.utils.ParametersUtils;\n\n/** Holders for internal event queues */\n@Component\npublic class EventQueues {\n\n    public static final String EVENT_QUEUE_PROVIDERS_QUALIFIER = \"EventQueueProviders\";\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(EventQueues.class);\n\n    private final ParametersUtils parametersUtils;\n    private final Map<String, EventQueueProvider> providers;\n\n    @Autowired\n    public EventQueues(\n            @Qualifier(EVENT_QUEUE_PROVIDERS_QUALIFIER) Map<String, EventQueueProvider> providers,\n            ParametersUtils parametersUtils) {\n        this.providers = providers;\n        this.parametersUtils = parametersUtils;\n    }\n\n    public List<String> getProviders() {\n        return providers.values().stream()\n                .map(p -> p.getClass().getName())\n                .collect(Collectors.toList());\n    }\n\n    @NonNull\n    public ObservableQueue getQueue(String eventType) {\n        String event = parametersUtils.replace(eventType).toString();\n        int index = event.indexOf(':');\n        if (index == -1) {\n            throw new IllegalArgumentException(\"Illegal event \" + event);\n        }\n\n        String type = event.substring(0, index);\n        String queueURI = event.substring(index + 1);\n        EventQueueProvider provider = providers.get(type);\n        if (provider != null) {\n            return provider.getQueue(queueURI);\n        } else {\n            throw new IllegalArgumentException(\"Unknown queue type \" + type);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/ScriptEvaluator.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport javax.script.Bindings;\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptEngineManager;\nimport javax.script.ScriptException;\n\npublic class ScriptEvaluator {\n\n    private static ScriptEngine engine;\n\n    private ScriptEvaluator() {}\n\n    /**\n     * Evaluates the script with the help of input provided but converts the result to a boolean\n     * value.\n     *\n     * @param script Script to be evaluated.\n     * @param input Input parameters.\n     * @throws ScriptException\n     * @return True or False based on the result of the evaluated expression.\n     */\n    public static Boolean evalBool(String script, Object input) throws ScriptException {\n        return toBoolean(eval(script, input));\n    }\n\n    /**\n     * Evaluates the script with the help of input provided.\n     *\n     * @param script Script to be evaluated.\n     * @param input Input parameters.\n     * @throws ScriptException\n     * @return Generic object, the result of the evaluated expression.\n     */\n    public static Object eval(String script, Object input) throws ScriptException {\n        if (engine == null) {\n            engine = new ScriptEngineManager().getEngineByName(\"Nashorn\");\n        }\n        if (engine == null) {\n            throw new RuntimeException(\n                    \"missing nashorn engine.  Ensure you are running supported JVM\");\n        }\n        Bindings bindings = engine.createBindings();\n        bindings.put(\"$\", input);\n        return engine.eval(script, bindings);\n    }\n\n    /**\n     * Converts a generic object into boolean value. Checks if the Object is of type Boolean and\n     * returns the value of the Boolean object. Checks if the Object is of type Number and returns\n     * True if the value is greater than 0.\n     *\n     * @param input Generic object that will be inspected to return a boolean value.\n     * @return True or False based on the input provided.\n     */\n    public static Boolean toBoolean(Object input) {\n        if (input instanceof Boolean) {\n            return ((Boolean) input);\n        } else if (input instanceof Number) {\n            return ((Number) input).doubleValue() > 0;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/SimpleActionProcessor.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action;\nimport com.netflix.conductor.common.metadata.events.EventHandler.StartWorkflow;\nimport com.netflix.conductor.common.metadata.events.EventHandler.TaskDetails;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.core.utils.JsonUtils;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * Action Processor subscribes to the Event Actions queue and processes the actions (e.g. start\n * workflow etc)\n */\n@Component\npublic class SimpleActionProcessor implements ActionProcessor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleActionProcessor.class);\n\n    private final WorkflowExecutor workflowExecutor;\n    private final ParametersUtils parametersUtils;\n    private final JsonUtils jsonUtils;\n    private final StartWorkflowOperation startWorkflowOperation;\n\n    public SimpleActionProcessor(\n            WorkflowExecutor workflowExecutor,\n            ParametersUtils parametersUtils,\n            JsonUtils jsonUtils,\n            StartWorkflowOperation startWorkflowOperation) {\n        this.workflowExecutor = workflowExecutor;\n        this.parametersUtils = parametersUtils;\n        this.jsonUtils = jsonUtils;\n        this.startWorkflowOperation = startWorkflowOperation;\n    }\n\n    public Map<String, Object> execute(\n            Action action, Object payloadObject, String event, String messageId) {\n\n        LOGGER.debug(\n                \"Executing action: {} for event: {} with messageId:{}\",\n                action.getAction(),\n                event,\n                messageId);\n\n        Object jsonObject = payloadObject;\n        if (action.isExpandInlineJSON()) {\n            jsonObject = jsonUtils.expand(payloadObject);\n        }\n\n        switch (action.getAction()) {\n            case start_workflow:\n                return startWorkflow(action, jsonObject, event, messageId);\n            case complete_task:\n                return completeTask(\n                        action,\n                        jsonObject,\n                        action.getComplete_task(),\n                        TaskModel.Status.COMPLETED,\n                        event,\n                        messageId);\n            case fail_task:\n                return completeTask(\n                        action,\n                        jsonObject,\n                        action.getFail_task(),\n                        TaskModel.Status.FAILED,\n                        event,\n                        messageId);\n            default:\n                break;\n        }\n        throw new UnsupportedOperationException(\n                \"Action not supported \" + action.getAction() + \" for event \" + event);\n    }\n\n    private Map<String, Object> completeTask(\n            Action action,\n            Object payload,\n            TaskDetails taskDetails,\n            TaskModel.Status status,\n            String event,\n            String messageId) {\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"workflowId\", taskDetails.getWorkflowId());\n        input.put(\"taskId\", taskDetails.getTaskId());\n        input.put(\"taskRefName\", taskDetails.getTaskRefName());\n        input.putAll(taskDetails.getOutput());\n\n        Map<String, Object> replaced = parametersUtils.replace(input, payload);\n        String workflowId = (String) replaced.get(\"workflowId\");\n        String taskId = (String) replaced.get(\"taskId\");\n        String taskRefName = (String) replaced.get(\"taskRefName\");\n\n        TaskModel taskModel = null;\n        if (StringUtils.isNotEmpty(taskId)) {\n            taskModel = workflowExecutor.getTask(taskId);\n        } else if (StringUtils.isNotEmpty(workflowId) && StringUtils.isNotEmpty(taskRefName)) {\n            WorkflowModel workflow = workflowExecutor.getWorkflow(workflowId, true);\n            if (workflow == null) {\n                replaced.put(\"error\", \"No workflow found with ID: \" + workflowId);\n                return replaced;\n            }\n            taskModel = workflow.getTaskByRefName(taskRefName);\n            // Task can be loopover task.In such case find corresponding task and update\n            List<TaskModel> loopOverTaskList =\n                    workflow.getTasks().stream()\n                            .filter(\n                                    t ->\n                                            TaskUtils.removeIterationFromTaskRefName(\n                                                            t.getReferenceTaskName())\n                                                    .equals(taskRefName))\n                            .collect(Collectors.toList());\n            if (!loopOverTaskList.isEmpty()) {\n                // Find loopover task with the highest iteration value\n                taskModel =\n                        loopOverTaskList.stream()\n                                .sorted(Comparator.comparingInt(TaskModel::getIteration).reversed())\n                                .findFirst()\n                                .get();\n            }\n        }\n\n        if (taskModel == null) {\n            replaced.put(\n                    \"error\",\n                    \"No task found with taskId: \"\n                            + taskId\n                            + \", reference name: \"\n                            + taskRefName\n                            + \", workflowId: \"\n                            + workflowId);\n            return replaced;\n        }\n\n        taskModel.setStatus(status);\n        taskModel.setOutputData(replaced);\n        taskModel.setOutputMessage(taskDetails.getOutputMessage());\n        taskModel.addOutput(\"conductor.event.messageId\", messageId);\n        taskModel.addOutput(\"conductor.event.name\", event);\n\n        try {\n            workflowExecutor.updateTask(new TaskResult(taskModel.toTask()));\n            LOGGER.debug(\n                    \"Updated task: {} in workflow:{} with status: {} for event: {} for message:{}\",\n                    taskId,\n                    workflowId,\n                    status,\n                    event,\n                    messageId);\n        } catch (RuntimeException e) {\n            Monitors.recordEventActionError(\n                    action.getAction().name(), taskModel.getTaskType(), event);\n            LOGGER.error(\n                    \"Error updating task: {} in workflow: {} in action: {} for event: {} for message: {}\",\n                    taskDetails.getTaskRefName(),\n                    taskDetails.getWorkflowId(),\n                    action.getAction(),\n                    event,\n                    messageId,\n                    e);\n            replaced.put(\"error\", e.getMessage());\n            throw e;\n        }\n        return replaced;\n    }\n\n    private Map<String, Object> startWorkflow(\n            Action action, Object payload, String event, String messageId) {\n        StartWorkflow params = action.getStart_workflow();\n        Map<String, Object> output = new HashMap<>();\n        try {\n            Map<String, Object> inputParams = params.getInput();\n            Map<String, Object> workflowInput = parametersUtils.replace(inputParams, payload);\n\n            Map<String, Object> paramsMap = new HashMap<>();\n            Optional.ofNullable(params.getCorrelationId())\n                    .ifPresent(value -> paramsMap.put(\"correlationId\", value));\n            Map<String, Object> replaced = parametersUtils.replace(paramsMap, payload);\n\n            workflowInput.put(\"conductor.event.messageId\", messageId);\n            workflowInput.put(\"conductor.event.name\", event);\n\n            StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n            startWorkflowInput.setName(params.getName());\n            startWorkflowInput.setVersion(params.getVersion());\n            startWorkflowInput.setCorrelationId(\n                    Optional.ofNullable(replaced.get(\"correlationId\"))\n                            .map(Object::toString)\n                            .orElse(params.getCorrelationId()));\n            startWorkflowInput.setWorkflowInput(workflowInput);\n            startWorkflowInput.setEvent(event);\n            startWorkflowInput.setTaskToDomain(params.getTaskToDomain());\n\n            String workflowId = startWorkflowOperation.execute(startWorkflowInput);\n\n            output.put(\"workflowId\", workflowId);\n            LOGGER.debug(\n                    \"Started workflow: {}/{}/{} for event: {} for message:{}\",\n                    params.getName(),\n                    params.getVersion(),\n                    workflowId,\n                    event,\n                    messageId);\n\n        } catch (RuntimeException e) {\n            Monitors.recordEventActionError(action.getAction().name(), params.getName(), event);\n            LOGGER.error(\n                    \"Error starting workflow: {}, version: {}, for event: {} for message: {}\",\n                    params.getName(),\n                    params.getVersion(),\n                    event,\n                    messageId,\n                    e);\n            output.put(\"error\", e.getMessage());\n            throw e;\n        }\n        return output;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/queue/ConductorEventQueueProvider.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events.queue;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.lang.NonNull;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.events.EventQueueProvider;\nimport com.netflix.conductor.dao.QueueDAO;\n\nimport rx.Scheduler;\n\n/**\n * Default provider for {@link com.netflix.conductor.core.events.queue.ObservableQueue} that listens\n * on the <i>conductor</i> queue prefix.\n *\n * <p><code>Set conductor.event-queues.default.enabled=false</code> to disable the default queue.\n *\n * @see ConductorObservableQueue\n */\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.event-queues.default.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class ConductorEventQueueProvider implements EventQueueProvider {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConductorEventQueueProvider.class);\n    private final Map<String, ObservableQueue> queues = new ConcurrentHashMap<>();\n    private final QueueDAO queueDAO;\n    private final ConductorProperties properties;\n    private final Scheduler scheduler;\n\n    public ConductorEventQueueProvider(\n            QueueDAO queueDAO, ConductorProperties properties, Scheduler scheduler) {\n        this.queueDAO = queueDAO;\n        this.properties = properties;\n        this.scheduler = scheduler;\n    }\n\n    @Override\n    public String getQueueType() {\n        return \"conductor\";\n    }\n\n    @Override\n    @NonNull\n    public ObservableQueue getQueue(String queueURI) {\n        return queues.computeIfAbsent(\n                queueURI,\n                q -> new ConductorObservableQueue(queueURI, queueDAO, properties, scheduler));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/queue/ConductorObservableQueue.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events.queue;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport rx.Observable;\nimport rx.Observable.OnSubscribe;\nimport rx.Scheduler;\n\n/**\n * An {@link ObservableQueue} implementation using the underlying {@link QueueDAO} implementation.\n */\npublic class ConductorObservableQueue implements ObservableQueue {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConductorObservableQueue.class);\n\n    private static final String QUEUE_TYPE = \"conductor\";\n\n    private final String queueName;\n    private final QueueDAO queueDAO;\n    private final long pollTimeMS;\n    private final int longPollTimeout;\n    private final int pollCount;\n    private final Scheduler scheduler;\n    private volatile boolean running;\n\n    ConductorObservableQueue(\n            String queueName,\n            QueueDAO queueDAO,\n            ConductorProperties properties,\n            Scheduler scheduler) {\n        this.queueName = queueName;\n        this.queueDAO = queueDAO;\n        this.pollTimeMS = properties.getEventQueuePollInterval().toMillis();\n        this.pollCount = properties.getEventQueuePollCount();\n        this.longPollTimeout = (int) properties.getEventQueueLongPollTimeout().toMillis();\n        this.scheduler = scheduler;\n    }\n\n    @Override\n    public Observable<Message> observe() {\n        OnSubscribe<Message> subscriber = getOnSubscribe();\n        return Observable.create(subscriber);\n    }\n\n    @Override\n    public List<String> ack(List<Message> messages) {\n        for (Message msg : messages) {\n            queueDAO.ack(queueName, msg.getId());\n        }\n        return messages.stream().map(Message::getId).collect(Collectors.toList());\n    }\n\n    public void setUnackTimeout(Message message, long unackTimeout) {\n        queueDAO.setUnackTimeout(queueName, message.getId(), unackTimeout);\n    }\n\n    @Override\n    public void publish(List<Message> messages) {\n        queueDAO.push(queueName, messages);\n    }\n\n    @Override\n    public long size() {\n        return queueDAO.getSize(queueName);\n    }\n\n    @Override\n    public String getType() {\n        return QUEUE_TYPE;\n    }\n\n    @Override\n    public String getName() {\n        return queueName;\n    }\n\n    @Override\n    public String getURI() {\n        return queueName;\n    }\n\n    private List<Message> receiveMessages() {\n        try {\n            List<Message> messages = queueDAO.pollMessages(queueName, pollCount, longPollTimeout);\n            Monitors.recordEventQueueMessagesProcessed(QUEUE_TYPE, queueName, messages.size());\n            Monitors.recordEventQueuePollSize(queueName, messages.size());\n            return messages;\n        } catch (Exception exception) {\n            LOGGER.error(\"Exception while getting messages from  queueDAO\", exception);\n            Monitors.recordObservableQMessageReceivedErrors(QUEUE_TYPE);\n        }\n        return new ArrayList<>();\n    }\n\n    private OnSubscribe<Message> getOnSubscribe() {\n        return subscriber -> {\n            Observable<Long> interval =\n                    Observable.interval(pollTimeMS, TimeUnit.MILLISECONDS, scheduler);\n            interval.flatMap(\n                            (Long x) -> {\n                                if (!isRunning()) {\n                                    LOGGER.debug(\n                                            \"Component stopped, skip listening for messages from Conductor Queue\");\n                                    return Observable.from(Collections.emptyList());\n                                }\n                                List<Message> messages = receiveMessages();\n                                return Observable.from(messages);\n                            })\n                    .subscribe(subscriber::onNext, subscriber::onError);\n        };\n    }\n\n    @Override\n    public void start() {\n        LOGGER.info(\"Started listening to {}:{}\", getClass().getSimpleName(), queueName);\n        running = true;\n    }\n\n    @Override\n    public void stop() {\n        LOGGER.info(\"Stopped listening to {}:{}\", getClass().getSimpleName(), queueName);\n        running = false;\n    }\n\n    @Override\n    public boolean isRunning() {\n        return running;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/queue/DefaultEventQueueProcessor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events.queue;\n\nimport java.util.*;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.TaskModel.Status;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.JsonParseException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_WAIT;\n\n/**\n * Monitors and processes messages on the default event queues that Conductor listens on.\n *\n * <p>The default event queue type is controlled using the property: <code>\n * conductor.default-event-queue.type</code>\n */\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.default-event-queue-processor.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class DefaultEventQueueProcessor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultEventQueueProcessor.class);\n    private final Map<Status, ObservableQueue> queues;\n    private final WorkflowExecutor workflowExecutor;\n    private static final TypeReference<Map<String, Object>> _mapType = new TypeReference<>() {};\n    private final ObjectMapper objectMapper;\n\n    public DefaultEventQueueProcessor(\n            Map<Status, ObservableQueue> queues,\n            WorkflowExecutor workflowExecutor,\n            ObjectMapper objectMapper) {\n        this.queues = queues;\n        this.workflowExecutor = workflowExecutor;\n        this.objectMapper = objectMapper;\n        queues.forEach(this::startMonitor);\n        LOGGER.info(\n                \"DefaultEventQueueProcessor initialized with {} queues\", queues.entrySet().size());\n    }\n\n    private void startMonitor(Status status, ObservableQueue queue) {\n\n        queue.observe()\n                .subscribe(\n                        (Message msg) -> {\n                            try {\n                                LOGGER.debug(\"Got message {}\", msg.getPayload());\n                                String payload = msg.getPayload();\n                                JsonNode payloadJSON = objectMapper.readTree(payload);\n                                String externalId = getValue(\"externalId\", payloadJSON);\n                                if (externalId == null || \"\".equals(externalId)) {\n                                    LOGGER.error(\"No external Id found in the payload {}\", payload);\n                                    queue.ack(Collections.singletonList(msg));\n                                    return;\n                                }\n\n                                JsonNode json = objectMapper.readTree(externalId);\n                                String workflowId = getValue(\"workflowId\", json);\n                                String taskRefName = getValue(\"taskRefName\", json);\n                                String taskId = getValue(\"taskId\", json);\n                                if (workflowId == null || \"\".equals(workflowId)) {\n                                    // This is a bad message, we cannot process it\n                                    LOGGER.error(\n                                            \"No workflow id found in the message. {}\", payload);\n                                    queue.ack(Collections.singletonList(msg));\n                                    return;\n                                }\n                                WorkflowModel workflow =\n                                        workflowExecutor.getWorkflow(workflowId, true);\n                                Optional<TaskModel> optionalTaskModel;\n                                if (StringUtils.isNotEmpty(taskId)) {\n                                    optionalTaskModel =\n                                            workflow.getTasks().stream()\n                                                    .filter(\n                                                            task ->\n                                                                    !task.getStatus().isTerminal()\n                                                                            && task.getTaskId()\n                                                                                    .equals(taskId))\n                                                    .findFirst();\n                                } else if (StringUtils.isEmpty(taskRefName)) {\n                                    LOGGER.error(\n                                            \"No taskRefName found in the message. If there is only one WAIT task, will mark it as completed. {}\",\n                                            payload);\n                                    optionalTaskModel =\n                                            workflow.getTasks().stream()\n                                                    .filter(\n                                                            task ->\n                                                                    !task.getStatus().isTerminal()\n                                                                            && task.getTaskType()\n                                                                                    .equals(\n                                                                                            TASK_TYPE_WAIT))\n                                                    .findFirst();\n                                } else {\n                                    optionalTaskModel =\n                                            workflow.getTasks().stream()\n                                                    .filter(\n                                                            task ->\n                                                                    !task.getStatus().isTerminal()\n                                                                            && TaskUtils\n                                                                                    .removeIterationFromTaskRefName(\n                                                                                            task\n                                                                                                    .getReferenceTaskName())\n                                                                                    .equals(\n                                                                                            taskRefName))\n                                                    .findFirst();\n                                }\n\n                                if (optionalTaskModel.isEmpty()) {\n                                    LOGGER.error(\n                                            \"No matching tasks found to be marked as completed for workflow {}, taskRefName {}, taskId {}\",\n                                            workflowId,\n                                            taskRefName,\n                                            taskId);\n                                    queue.ack(Collections.singletonList(msg));\n                                    return;\n                                }\n\n                                Task task = optionalTaskModel.get().toTask();\n                                task.setStatus(TaskModel.mapToTaskStatus(status));\n                                task.getOutputData()\n                                        .putAll(objectMapper.convertValue(payloadJSON, _mapType));\n                                workflowExecutor.updateTask(new TaskResult(task));\n\n                                List<String> failures = queue.ack(Collections.singletonList(msg));\n                                if (!failures.isEmpty()) {\n                                    LOGGER.error(\"Not able to ack the messages {}\", failures);\n                                }\n                            } catch (JsonParseException e) {\n                                LOGGER.error(\"Bad message? : {} \", msg, e);\n                                queue.ack(Collections.singletonList(msg));\n                            } catch (NotFoundException nfe) {\n                                LOGGER.error(\n                                        \"Workflow ID specified is not valid for this environment\");\n                                queue.ack(Collections.singletonList(msg));\n                            } catch (Exception e) {\n                                LOGGER.error(\"Error processing message: {}\", msg, e);\n                            }\n                        },\n                        (Throwable t) -> LOGGER.error(t.getMessage(), t));\n        LOGGER.info(\"QueueListener::STARTED...listening for \" + queue.getName());\n    }\n\n    private String getValue(String fieldName, JsonNode json) {\n        JsonNode node = json.findValue(fieldName);\n        if (node == null) {\n            return null;\n        }\n        return node.textValue();\n    }\n\n    public Map<String, Long> size() {\n        Map<String, Long> size = new HashMap<>();\n        queues.forEach((key, queue) -> size.put(queue.getName(), queue.size()));\n        return size;\n    }\n\n    public Map<Status, String> queues() {\n        Map<Status, String> size = new HashMap<>();\n        queues.forEach((key, queue) -> size.put(key, queue.getURI()));\n        return size;\n    }\n\n    public void updateByTaskRefName(\n            String workflowId, String taskRefName, Map<String, Object> output, Status status)\n            throws Exception {\n        Map<String, Object> externalIdMap = new HashMap<>();\n        externalIdMap.put(\"workflowId\", workflowId);\n        externalIdMap.put(\"taskRefName\", taskRefName);\n\n        update(externalIdMap, output, status);\n    }\n\n    public void updateByTaskId(\n            String workflowId, String taskId, Map<String, Object> output, Status status)\n            throws Exception {\n        Map<String, Object> externalIdMap = new HashMap<>();\n        externalIdMap.put(\"workflowId\", workflowId);\n        externalIdMap.put(\"taskId\", taskId);\n\n        update(externalIdMap, output, status);\n    }\n\n    private void update(\n            Map<String, Object> externalIdMap, Map<String, Object> output, Status status)\n            throws Exception {\n        Map<String, Object> outputMap = new HashMap<>();\n\n        outputMap.put(\"externalId\", objectMapper.writeValueAsString(externalIdMap));\n        outputMap.putAll(output);\n\n        Message msg =\n                new Message(\n                        UUID.randomUUID().toString(),\n                        objectMapper.writeValueAsString(outputMap),\n                        null);\n        ObservableQueue queue = queues.get(status);\n        if (queue == null) {\n            throw new IllegalArgumentException(\n                    \"There is no queue for handling \" + status.toString() + \" status\");\n        }\n        queue.publish(Collections.singletonList(msg));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/queue/Message.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events.queue;\n\nimport java.util.Objects;\n\npublic class Message {\n\n    private String payload;\n    private String id;\n    private String receipt;\n    private int priority;\n\n    public Message() {}\n\n    public Message(String id, String payload, String receipt) {\n        this.payload = payload;\n        this.id = id;\n        this.receipt = receipt;\n    }\n\n    public Message(String id, String payload, String receipt, int priority) {\n        this.payload = payload;\n        this.id = id;\n        this.receipt = receipt;\n        this.priority = priority;\n    }\n\n    /**\n     * @return the payload\n     */\n    public String getPayload() {\n        return payload;\n    }\n\n    /**\n     * @param payload the payload to set\n     */\n    public void setPayload(String payload) {\n        this.payload = payload;\n    }\n\n    /**\n     * @return the id\n     */\n    public String getId() {\n        return id;\n    }\n\n    /**\n     * @param id the id to set\n     */\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    /**\n     * @return Receipt attached to the message\n     */\n    public String getReceipt() {\n        return receipt;\n    }\n\n    /**\n     * @param receipt Receipt attached to the message\n     */\n    public void setReceipt(String receipt) {\n        this.receipt = receipt;\n    }\n\n    /**\n     * Gets the message priority\n     *\n     * @return priority of message.\n     */\n    public int getPriority() {\n        return priority;\n    }\n\n    /**\n     * Sets the message priority (between 0 and 99). Higher priority message is retrieved ahead of\n     * lower priority ones.\n     *\n     * @param priority the priority of message (between 0 and 99)\n     */\n    public void setPriority(int priority) {\n        this.priority = priority;\n    }\n\n    @Override\n    public String toString() {\n        return id;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Message message = (Message) o;\n        return Objects.equals(payload, message.payload)\n                && Objects.equals(id, message.id)\n                && Objects.equals(priority, message.priority)\n                && Objects.equals(receipt, message.receipt);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(payload, id, receipt, priority);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/events/queue/ObservableQueue.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events.queue;\n\nimport java.util.List;\n\nimport org.springframework.context.Lifecycle;\n\nimport rx.Observable;\n\npublic interface ObservableQueue extends Lifecycle {\n\n    /**\n     * @return An observable for the given queue\n     */\n    Observable<Message> observe();\n\n    /**\n     * @return Type of the queue\n     */\n    String getType();\n\n    /**\n     * @return Name of the queue\n     */\n    String getName();\n\n    /**\n     * @return URI identifier for the queue.\n     */\n    String getURI();\n\n    /**\n     * @param messages to be ack'ed\n     * @return the id of the ones which could not be ack'ed\n     */\n    List<String> ack(List<Message> messages);\n\n    /**\n     * @param messages to be Nack'ed\n     */\n    default void nack(List<Message> messages) {}\n\n    /**\n     * @param messages Messages to be published\n     */\n    void publish(List<Message> messages);\n\n    /**\n     * Used to determine if the queue supports unack/visibility timeout such that the messages will\n     * re-appear on the queue after a specific period and are available to be picked up again and\n     * retried.\n     *\n     * @return - false if the queue message need not be re-published to the queue for retriability -\n     *     true if the message must be re-published to the queue for retriability\n     */\n    default boolean rePublishIfNoAck() {\n        return false;\n    }\n\n    /**\n     * Extend the lease of the unacknowledged message for longer period.\n     *\n     * @param message Message for which the timeout has to be changed\n     * @param unackTimeout timeout in milliseconds for which the unack lease should be extended.\n     *     (replaces the current value with this value)\n     */\n    void setUnackTimeout(Message message, long unackTimeout);\n\n    /**\n     * @return Size of the queue - no. messages pending. Note: Depending upon the implementation,\n     *     this can be an approximation\n     */\n    long size();\n\n    /** Used to close queue instance prior to remove from queues */\n    default void close() {}\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/exception/ConflictException.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.exception;\n\npublic class ConflictException extends RuntimeException {\n\n    public ConflictException(String message) {\n        super(message);\n    }\n\n    public ConflictException(String message, Object... args) {\n        super(String.format(message, args));\n    }\n\n    public ConflictException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/exception/NonTransientException.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.exception;\n\npublic class NonTransientException extends RuntimeException {\n\n    public NonTransientException(String message) {\n        super(message);\n    }\n\n    public NonTransientException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/exception/NotFoundException.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.exception;\n\npublic class NotFoundException extends RuntimeException {\n\n    public NotFoundException(String message) {\n        super(message);\n    }\n\n    public NotFoundException(String message, Object... args) {\n        super(String.format(message, args));\n    }\n\n    public NotFoundException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/exception/TerminateWorkflowException.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.exception;\n\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.model.WorkflowModel.Status.FAILED;\n\npublic class TerminateWorkflowException extends RuntimeException {\n\n    private final WorkflowModel.Status workflowStatus;\n    private final TaskModel task;\n\n    public TerminateWorkflowException(String reason) {\n        this(reason, FAILED);\n    }\n\n    public TerminateWorkflowException(String reason, WorkflowModel.Status workflowStatus) {\n        this(reason, workflowStatus, null);\n    }\n\n    public TerminateWorkflowException(\n            String reason, WorkflowModel.Status workflowStatus, TaskModel task) {\n        super(reason);\n        this.workflowStatus = workflowStatus;\n        this.task = task;\n    }\n\n    public WorkflowModel.Status getWorkflowStatus() {\n        return workflowStatus;\n    }\n\n    public TaskModel getTask() {\n        return task;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/exception/TransientException.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.exception;\n\npublic class TransientException extends RuntimeException {\n\n    public TransientException(String message) {\n        super(message);\n    }\n\n    public TransientException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/AsyncSystemTaskExecutor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n@Component\npublic class AsyncSystemTaskExecutor {\n\n    private final ExecutionDAOFacade executionDAOFacade;\n    private final QueueDAO queueDAO;\n    private final MetadataDAO metadataDAO;\n    private final long queueTaskMessagePostponeSecs;\n    private final long systemTaskCallbackTime;\n    private final WorkflowExecutor workflowExecutor;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(AsyncSystemTaskExecutor.class);\n\n    public AsyncSystemTaskExecutor(\n            ExecutionDAOFacade executionDAOFacade,\n            QueueDAO queueDAO,\n            MetadataDAO metadataDAO,\n            ConductorProperties conductorProperties,\n            WorkflowExecutor workflowExecutor) {\n        this.executionDAOFacade = executionDAOFacade;\n        this.queueDAO = queueDAO;\n        this.metadataDAO = metadataDAO;\n        this.workflowExecutor = workflowExecutor;\n        this.systemTaskCallbackTime =\n                conductorProperties.getSystemTaskWorkerCallbackDuration().getSeconds();\n        this.queueTaskMessagePostponeSecs =\n                conductorProperties.getTaskExecutionPostponeDuration().getSeconds();\n    }\n\n    /**\n     * Executes and persists the results of an async {@link WorkflowSystemTask}.\n     *\n     * @param systemTask The {@link WorkflowSystemTask} to be executed.\n     * @param taskId The id of the {@link TaskModel} object.\n     */\n    public void execute(WorkflowSystemTask systemTask, String taskId) {\n        TaskModel task = loadTaskQuietly(taskId);\n        if (task == null) {\n            LOGGER.error(\"TaskId: {} could not be found while executing {}\", taskId, systemTask);\n            try {\n                LOGGER.debug(\n                        \"Cleaning up dead task from queue message: taskQueue={}, taskId={}\",\n                        systemTask.getTaskType(),\n                        taskId);\n                queueDAO.remove(systemTask.getTaskType(), taskId);\n            } catch (Exception e) {\n                LOGGER.error(\n                        \"Failed to remove dead task from queue message: taskQueue={}, taskId={}\",\n                        systemTask.getTaskType(),\n                        taskId);\n            }\n            return;\n        }\n\n        LOGGER.debug(\"Task: {} fetched from execution DAO for taskId: {}\", task, taskId);\n        String queueName = QueueUtils.getQueueName(task);\n        if (task.getStatus().isTerminal()) {\n            // Tune the SystemTaskWorkerCoordinator's queues - if the queue size is very big this\n            // can happen!\n            LOGGER.info(\"Task {}/{} was already completed.\", task.getTaskType(), task.getTaskId());\n            queueDAO.remove(queueName, task.getTaskId());\n            return;\n        }\n\n        if (task.getStatus().equals(TaskModel.Status.SCHEDULED)) {\n            if (executionDAOFacade.exceedsInProgressLimit(task)) {\n                LOGGER.warn(\n                        \"Concurrent Execution limited for {}:{}\", taskId, task.getTaskDefName());\n                postponeQuietly(queueName, task);\n                return;\n            }\n            if (task.getRateLimitPerFrequency() > 0\n                    && executionDAOFacade.exceedsRateLimitPerFrequency(\n                            task, metadataDAO.getTaskDef(task.getTaskDefName()))) {\n                LOGGER.warn(\n                        \"RateLimit Execution limited for {}:{}, limit:{}\",\n                        taskId,\n                        task.getTaskDefName(),\n                        task.getRateLimitPerFrequency());\n                postponeQuietly(queueName, task);\n                return;\n            }\n        }\n\n        boolean hasTaskExecutionCompleted = false;\n        boolean shouldRemoveTaskFromQueue = false;\n        String workflowId = task.getWorkflowInstanceId();\n        // if we are here the Task object is updated and needs to be persisted regardless of an\n        // exception\n        try {\n            WorkflowModel workflow =\n                    executionDAOFacade.getWorkflowModel(\n                            workflowId, systemTask.isTaskRetrievalRequired());\n\n            if (workflow.getStatus().isTerminal()) {\n                LOGGER.info(\n                        \"Workflow {} has been completed for {}/{}\",\n                        workflow.toShortString(),\n                        systemTask,\n                        task.getTaskId());\n                if (!task.getStatus().isTerminal()) {\n                    task.setStatus(TaskModel.Status.CANCELED);\n                    task.setReasonForIncompletion(\n                            String.format(\n                                    \"Workflow is in %s state\", workflow.getStatus().toString()));\n                }\n                shouldRemoveTaskFromQueue = true;\n                return;\n            }\n\n            LOGGER.debug(\n                    \"Executing {}/{} in {} state\",\n                    task.getTaskType(),\n                    task.getTaskId(),\n                    task.getStatus());\n\n            boolean isTaskAsyncComplete = systemTask.isAsyncComplete(task);\n            if (task.getStatus() == TaskModel.Status.SCHEDULED || !isTaskAsyncComplete) {\n                task.incrementPollCount();\n            }\n\n            if (task.getStatus() == TaskModel.Status.SCHEDULED) {\n                task.setStartTime(System.currentTimeMillis());\n                Monitors.recordQueueWaitTime(task.getTaskType(), task.getQueueWaitTime());\n                systemTask.start(workflow, task, workflowExecutor);\n            } else if (task.getStatus() == TaskModel.Status.IN_PROGRESS) {\n                systemTask.execute(workflow, task, workflowExecutor);\n            }\n\n            // Update message in Task queue based on Task status\n            // Remove asyncComplete system tasks from the queue that are not in SCHEDULED state\n            if (isTaskAsyncComplete && task.getStatus() != TaskModel.Status.SCHEDULED) {\n                shouldRemoveTaskFromQueue = true;\n                hasTaskExecutionCompleted = true;\n            } else if (task.getStatus().isTerminal()) {\n                task.setEndTime(System.currentTimeMillis());\n                shouldRemoveTaskFromQueue = true;\n                hasTaskExecutionCompleted = true;\n            } else {\n                task.setCallbackAfterSeconds(systemTaskCallbackTime);\n                systemTask\n                        .getEvaluationOffset(task, systemTaskCallbackTime)\n                        .ifPresentOrElse(\n                                task::setCallbackAfterSeconds,\n                                () -> task.setCallbackAfterSeconds(systemTaskCallbackTime));\n                queueDAO.postpone(\n                        queueName,\n                        task.getTaskId(),\n                        task.getWorkflowPriority(),\n                        task.getCallbackAfterSeconds());\n                LOGGER.debug(\"{} postponed in queue: {}\", task, queueName);\n            }\n\n            LOGGER.debug(\n                    \"Finished execution of {}/{}-{}\",\n                    systemTask,\n                    task.getTaskId(),\n                    task.getStatus());\n        } catch (Exception e) {\n            Monitors.error(AsyncSystemTaskExecutor.class.getSimpleName(), \"executeSystemTask\");\n            LOGGER.error(\"Error executing system task - {}, with id: {}\", systemTask, taskId, e);\n        } finally {\n            executionDAOFacade.updateTask(task);\n            if (shouldRemoveTaskFromQueue) {\n                queueDAO.remove(queueName, task.getTaskId());\n                LOGGER.debug(\"{} removed from queue: {}\", task, queueName);\n            }\n            // if the current task execution has completed, then the workflow needs to be evaluated\n            if (hasTaskExecutionCompleted) {\n                workflowExecutor.decide(workflowId);\n            }\n        }\n    }\n\n    private void postponeQuietly(String queueName, TaskModel task) {\n        try {\n            queueDAO.postpone(\n                    queueName,\n                    task.getTaskId(),\n                    task.getWorkflowPriority(),\n                    queueTaskMessagePostponeSecs);\n        } catch (Exception e) {\n            LOGGER.error(\"Error postponing task: {} in queue: {}\", task.getTaskId(), queueName);\n        }\n    }\n\n    private TaskModel loadTaskQuietly(String taskId) {\n        try {\n            return executionDAOFacade.getTaskModel(taskId);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/DeciderService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport java.time.Duration;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage.Operation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.mapper.TaskMapper;\nimport com.netflix.conductor.core.execution.mapper.TaskMapperContext;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TERMINATE;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.USER_DEFINED;\nimport static com.netflix.conductor.model.TaskModel.Status.*;\n\n/**\n * Decider evaluates the state of the workflow by inspecting the current state along with the\n * blueprint. The result of the evaluation is either to schedule further tasks, complete/fail the\n * workflow or do nothing.\n */\n@Service\n@Trace\npublic class DeciderService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DeciderService.class);\n\n    private final IDGenerator idGenerator;\n    private final ParametersUtils parametersUtils;\n    private final ExternalPayloadStorageUtils externalPayloadStorageUtils;\n    private final MetadataDAO metadataDAO;\n    private final SystemTaskRegistry systemTaskRegistry;\n    private final long taskPendingTimeThresholdMins;\n\n    private final Map<String, TaskMapper> taskMappers;\n\n    public DeciderService(\n            IDGenerator idGenerator,\n            ParametersUtils parametersUtils,\n            MetadataDAO metadataDAO,\n            ExternalPayloadStorageUtils externalPayloadStorageUtils,\n            SystemTaskRegistry systemTaskRegistry,\n            @Qualifier(\"taskMappersByTaskType\") Map<String, TaskMapper> taskMappers,\n            @Value(\"${conductor.app.taskPendingTimeThreshold:60m}\")\n                    Duration taskPendingTimeThreshold) {\n        this.idGenerator = idGenerator;\n        this.metadataDAO = metadataDAO;\n        this.parametersUtils = parametersUtils;\n        this.taskMappers = taskMappers;\n        this.externalPayloadStorageUtils = externalPayloadStorageUtils;\n        this.taskPendingTimeThresholdMins = taskPendingTimeThreshold.toMinutes();\n        this.systemTaskRegistry = systemTaskRegistry;\n    }\n\n    public DeciderOutcome decide(WorkflowModel workflow) throws TerminateWorkflowException {\n\n        // In case of a new workflow the list of tasks will be empty.\n        final List<TaskModel> tasks = workflow.getTasks();\n        // Filter the list of tasks and include only tasks that are not executed,\n        // not marked to be skipped and not ready for rerun.\n        // For a new workflow, the list of unprocessedTasks will be empty\n        List<TaskModel> unprocessedTasks =\n                tasks.stream()\n                        .filter(t -> !t.getStatus().equals(SKIPPED) && !t.isExecuted())\n                        .collect(Collectors.toList());\n\n        List<TaskModel> tasksToBeScheduled = new LinkedList<>();\n        if (unprocessedTasks.isEmpty()) {\n            // this is the flow that the new workflow will go through\n            tasksToBeScheduled = startWorkflow(workflow);\n            if (tasksToBeScheduled == null) {\n                tasksToBeScheduled = new LinkedList<>();\n            }\n        }\n        return decide(workflow, tasksToBeScheduled);\n    }\n\n    private DeciderOutcome decide(final WorkflowModel workflow, List<TaskModel> preScheduledTasks)\n            throws TerminateWorkflowException {\n\n        DeciderOutcome outcome = new DeciderOutcome();\n\n        if (workflow.getStatus().isTerminal()) {\n            // you cannot evaluate a terminal workflow\n            LOGGER.debug(\n                    \"Workflow {} is already finished. Reason: {}\",\n                    workflow,\n                    workflow.getReasonForIncompletion());\n            return outcome;\n        }\n\n        checkWorkflowTimeout(workflow);\n\n        if (workflow.getStatus().equals(WorkflowModel.Status.PAUSED)) {\n            LOGGER.debug(\"Workflow \" + workflow.getWorkflowId() + \" is paused\");\n            return outcome;\n        }\n\n        List<TaskModel> pendingTasks = new ArrayList<>();\n        Set<String> executedTaskRefNames = new HashSet<>();\n        boolean hasSuccessfulTerminateTask = false;\n        for (TaskModel task : workflow.getTasks()) {\n\n            // Filter the list of tasks and include only tasks that are not retried, not executed\n            // marked to be skipped and not part of System tasks that is DECISION, FORK, JOIN\n            // This list will be empty for a new workflow being started\n            if (!task.isRetried() && !task.getStatus().equals(SKIPPED) && !task.isExecuted()) {\n                pendingTasks.add(task);\n            }\n\n            // Get all the tasks that have not completed their lifecycle yet\n            // This list will be empty for a new workflow\n            if (task.isExecuted()) {\n                executedTaskRefNames.add(task.getReferenceTaskName());\n            }\n\n            if (TERMINATE.name().equals(task.getTaskType())\n                    && task.getStatus().isTerminal()\n                    && task.getStatus().isSuccessful()) {\n                hasSuccessfulTerminateTask = true;\n                outcome.terminateTask = task;\n            }\n        }\n\n        Map<String, TaskModel> tasksToBeScheduled = new LinkedHashMap<>();\n\n        preScheduledTasks.forEach(\n                preScheduledTask -> {\n                    tasksToBeScheduled.put(\n                            preScheduledTask.getReferenceTaskName(), preScheduledTask);\n                });\n\n        // A new workflow does not enter this code branch\n        for (TaskModel pendingTask : pendingTasks) {\n\n            if (systemTaskRegistry.isSystemTask(pendingTask.getTaskType())\n                    && !pendingTask.getStatus().isTerminal()) {\n                tasksToBeScheduled.putIfAbsent(pendingTask.getReferenceTaskName(), pendingTask);\n                executedTaskRefNames.remove(pendingTask.getReferenceTaskName());\n            }\n\n            Optional<TaskDef> taskDefinition = pendingTask.getTaskDefinition();\n            if (taskDefinition.isEmpty()) {\n                taskDefinition =\n                        Optional.ofNullable(\n                                        workflow.getWorkflowDefinition()\n                                                .getTaskByRefName(\n                                                        pendingTask.getReferenceTaskName()))\n                                .map(WorkflowTask::getTaskDefinition);\n            }\n\n            if (taskDefinition.isPresent()) {\n                checkTaskTimeout(taskDefinition.get(), pendingTask);\n                checkTaskPollTimeout(taskDefinition.get(), pendingTask);\n                // If the task has not been updated for \"responseTimeoutSeconds\" then mark task as\n                // TIMED_OUT\n                if (isResponseTimedOut(taskDefinition.get(), pendingTask)) {\n                    timeoutTask(taskDefinition.get(), pendingTask);\n                }\n            }\n\n            if (!pendingTask.getStatus().isSuccessful()) {\n                WorkflowTask workflowTask = pendingTask.getWorkflowTask();\n                if (workflowTask == null) {\n                    workflowTask =\n                            workflow.getWorkflowDefinition()\n                                    .getTaskByRefName(pendingTask.getReferenceTaskName());\n                }\n\n                Optional<TaskModel> retryTask =\n                        retry(taskDefinition.orElse(null), workflowTask, pendingTask, workflow);\n                if (retryTask.isPresent()) {\n                    tasksToBeScheduled.put(retryTask.get().getReferenceTaskName(), retryTask.get());\n                    executedTaskRefNames.remove(retryTask.get().getReferenceTaskName());\n                    outcome.tasksToBeUpdated.add(pendingTask);\n                } else {\n                    pendingTask.setStatus(COMPLETED_WITH_ERRORS);\n                }\n            }\n\n            if (!pendingTask.isExecuted()\n                    && !pendingTask.isRetried()\n                    && pendingTask.getStatus().isTerminal()) {\n                pendingTask.setExecuted(true);\n                List<TaskModel> nextTasks = getNextTask(workflow, pendingTask);\n                if (pendingTask.isLoopOverTask()\n                        && !TaskType.DO_WHILE.name().equals(pendingTask.getTaskType())\n                        && !nextTasks.isEmpty()) {\n                    nextTasks = filterNextLoopOverTasks(nextTasks, pendingTask, workflow);\n                }\n                nextTasks.forEach(\n                        nextTask ->\n                                tasksToBeScheduled.putIfAbsent(\n                                        nextTask.getReferenceTaskName(), nextTask));\n                outcome.tasksToBeUpdated.add(pendingTask);\n                LOGGER.debug(\n                        \"Scheduling Tasks from {}, next = {} for workflowId: {}\",\n                        pendingTask.getTaskDefName(),\n                        nextTasks.stream()\n                                .map(TaskModel::getTaskDefName)\n                                .collect(Collectors.toList()),\n                        workflow.getWorkflowId());\n            }\n        }\n\n        // All the tasks that need to scheduled are added to the outcome, in case of\n        List<TaskModel> unScheduledTasks =\n                tasksToBeScheduled.values().stream()\n                        .filter(task -> !executedTaskRefNames.contains(task.getReferenceTaskName()))\n                        .collect(Collectors.toList());\n        if (!unScheduledTasks.isEmpty()) {\n            LOGGER.debug(\n                    \"Scheduling Tasks: {} for workflow: {}\",\n                    unScheduledTasks.stream()\n                            .map(TaskModel::getTaskDefName)\n                            .collect(Collectors.toList()),\n                    workflow.getWorkflowId());\n            outcome.tasksToBeScheduled.addAll(unScheduledTasks);\n        }\n        if (hasSuccessfulTerminateTask\n                || (outcome.tasksToBeScheduled.isEmpty() && checkForWorkflowCompletion(workflow))) {\n            LOGGER.debug(\"Marking workflow: {} as complete.\", workflow);\n            outcome.isComplete = true;\n        }\n\n        return outcome;\n    }\n\n    @VisibleForTesting\n    List<TaskModel> filterNextLoopOverTasks(\n            List<TaskModel> tasks, TaskModel pendingTask, WorkflowModel workflow) {\n\n        // Update the task reference name and iteration\n        tasks.forEach(\n                nextTask -> {\n                    nextTask.setReferenceTaskName(\n                            TaskUtils.appendIteration(\n                                    nextTask.getReferenceTaskName(), pendingTask.getIteration()));\n                    nextTask.setIteration(pendingTask.getIteration());\n                });\n\n        List<String> tasksInWorkflow =\n                workflow.getTasks().stream()\n                        .filter(\n                                runningTask ->\n                                        runningTask.getStatus().equals(TaskModel.Status.IN_PROGRESS)\n                                                || runningTask.getStatus().isTerminal())\n                        .map(TaskModel::getReferenceTaskName)\n                        .collect(Collectors.toList());\n\n        return tasks.stream()\n                .filter(\n                        runningTask ->\n                                !tasksInWorkflow.contains(runningTask.getReferenceTaskName()))\n                .collect(Collectors.toList());\n    }\n\n    private List<TaskModel> startWorkflow(WorkflowModel workflow)\n            throws TerminateWorkflowException {\n        final WorkflowDef workflowDef = workflow.getWorkflowDefinition();\n\n        LOGGER.debug(\"Starting workflow: {}\", workflow);\n\n        // The tasks will be empty in case of new workflow\n        List<TaskModel> tasks = workflow.getTasks();\n        // Check if the workflow is a re-run case or if it is a new workflow execution\n        if (workflow.getReRunFromWorkflowId() == null || tasks.isEmpty()) {\n\n            if (workflowDef.getTasks().isEmpty()) {\n                throw new TerminateWorkflowException(\n                        \"No tasks found to be executed\", WorkflowModel.Status.COMPLETED);\n            }\n\n            WorkflowTask taskToSchedule =\n                    workflowDef\n                            .getTasks()\n                            .get(0); // Nothing is running yet - so schedule the first task\n            // Loop until a non-skipped task is found\n            while (isTaskSkipped(taskToSchedule, workflow)) {\n                taskToSchedule = workflowDef.getNextTask(taskToSchedule.getTaskReferenceName());\n            }\n\n            // In case of a new workflow, the first non-skippable task will be scheduled\n            return getTasksToBeScheduled(workflow, taskToSchedule, 0);\n        }\n\n        // Get the first task to schedule\n        TaskModel rerunFromTask =\n                tasks.stream()\n                        .findFirst()\n                        .map(\n                                task -> {\n                                    task.setStatus(SCHEDULED);\n                                    task.setRetried(true);\n                                    task.setRetryCount(0);\n                                    return task;\n                                })\n                        .orElseThrow(\n                                () -> {\n                                    String reason =\n                                            String.format(\n                                                    \"The workflow %s is marked for re-run from %s but could not find the starting task\",\n                                                    workflow.getWorkflowId(),\n                                                    workflow.getReRunFromWorkflowId());\n                                    return new TerminateWorkflowException(reason);\n                                });\n\n        return Collections.singletonList(rerunFromTask);\n    }\n\n    /**\n     * Updates the workflow output.\n     *\n     * @param workflow the workflow instance\n     * @param task if not null, the output of this task will be copied to workflow output if no\n     *     output parameters are specified in the workflow definition if null, the output of the\n     *     last task in the workflow will be copied to workflow output of no output parameters are\n     *     specified in the workflow definition\n     */\n    void updateWorkflowOutput(final WorkflowModel workflow, TaskModel task) {\n        List<TaskModel> allTasks = workflow.getTasks();\n        if (allTasks.isEmpty()) {\n            return;\n        }\n\n        Map<String, Object> output = new HashMap<>();\n        Optional<TaskModel> optionalTask =\n                allTasks.stream()\n                        .filter(\n                                t ->\n                                        TaskType.TERMINATE.name().equals(t.getTaskType())\n                                                && t.getStatus().isTerminal()\n                                                && t.getStatus().isSuccessful())\n                        .findFirst();\n        if (optionalTask.isPresent()) {\n            TaskModel terminateTask = optionalTask.get();\n            if (StringUtils.isNotBlank(terminateTask.getExternalOutputPayloadStoragePath())) {\n                output =\n                        externalPayloadStorageUtils.downloadPayload(\n                                terminateTask.getExternalOutputPayloadStoragePath());\n                Monitors.recordExternalPayloadStorageUsage(\n                        terminateTask.getTaskDefName(),\n                        Operation.READ.toString(),\n                        PayloadType.TASK_OUTPUT.toString());\n            } else if (!terminateTask.getOutputData().isEmpty()) {\n                output = terminateTask.getOutputData();\n            }\n        } else {\n            TaskModel last = Optional.ofNullable(task).orElse(allTasks.get(allTasks.size() - 1));\n            WorkflowDef workflowDef = workflow.getWorkflowDefinition();\n            if (workflowDef.getOutputParameters() != null\n                    && !workflowDef.getOutputParameters().isEmpty()) {\n                output =\n                        parametersUtils.getTaskInput(\n                                workflowDef.getOutputParameters(), workflow, null, null);\n            } else if (StringUtils.isNotBlank(last.getExternalOutputPayloadStoragePath())) {\n                output =\n                        externalPayloadStorageUtils.downloadPayload(\n                                last.getExternalOutputPayloadStoragePath());\n                Monitors.recordExternalPayloadStorageUsage(\n                        last.getTaskDefName(),\n                        Operation.READ.toString(),\n                        PayloadType.TASK_OUTPUT.toString());\n            } else {\n                output = last.getOutputData();\n            }\n        }\n        workflow.setOutput(output);\n    }\n\n    public boolean checkForWorkflowCompletion(final WorkflowModel workflow)\n            throws TerminateWorkflowException {\n\n        Map<String, TaskModel.Status> taskStatusMap = new HashMap<>();\n        List<TaskModel> nonExecutedTasks = new ArrayList<>();\n        for (TaskModel task : workflow.getTasks()) {\n            taskStatusMap.put(task.getReferenceTaskName(), task.getStatus());\n            if (!task.getStatus().isTerminal()) {\n                return false;\n            }\n\n            // If there is a TERMINATE task that has been executed successfuly then the workflow\n            // should be marked as completed.\n            if (TERMINATE.name().equals(task.getTaskType())\n                    && task.getStatus().isTerminal()\n                    && task.getStatus().isSuccessful()) {\n                return true;\n            }\n            if (!task.isRetried() || !task.isExecuted()) {\n                nonExecutedTasks.add(task);\n            }\n        }\n\n        // If there are no tasks executed, then we are not done yet\n        if (taskStatusMap.isEmpty()) {\n            return false;\n        }\n\n        List<WorkflowTask> workflowTasks = workflow.getWorkflowDefinition().getTasks();\n\n        for (WorkflowTask wftask : workflowTasks) {\n            TaskModel.Status status = taskStatusMap.get(wftask.getTaskReferenceName());\n            if (status == null || !status.isTerminal()) {\n                return false;\n            }\n            // if we reach here, the task has been completed.\n            // Was the task successful in completion?\n            if (!status.isSuccessful()) {\n                return false;\n            }\n        }\n\n        boolean noPendingSchedule =\n                nonExecutedTasks.stream()\n                        .parallel()\n                        .noneMatch(\n                                wftask -> {\n                                    String next = getNextTasksToBeScheduled(workflow, wftask);\n                                    return next != null && !taskStatusMap.containsKey(next);\n                                });\n\n        return noPendingSchedule;\n    }\n\n    List<TaskModel> getNextTask(WorkflowModel workflow, TaskModel task) {\n        final WorkflowDef workflowDef = workflow.getWorkflowDefinition();\n\n        // Get the following task after the last completed task\n        if (systemTaskRegistry.isSystemTask(task.getTaskType())\n                && (TaskType.TASK_TYPE_DECISION.equals(task.getTaskType())\n                        || TaskType.TASK_TYPE_SWITCH.equals(task.getTaskType()))) {\n            if (task.getInputData().get(\"hasChildren\") != null) {\n                return Collections.emptyList();\n            }\n        }\n\n        String taskReferenceName =\n                task.isLoopOverTask()\n                        ? TaskUtils.removeIterationFromTaskRefName(task.getReferenceTaskName())\n                        : task.getReferenceTaskName();\n        WorkflowTask taskToSchedule = workflowDef.getNextTask(taskReferenceName);\n        while (isTaskSkipped(taskToSchedule, workflow)) {\n            taskToSchedule = workflowDef.getNextTask(taskToSchedule.getTaskReferenceName());\n        }\n        if (taskToSchedule != null && TaskType.DO_WHILE.name().equals(taskToSchedule.getType())) {\n            // check if already has this DO_WHILE task, ignore it if it already exists\n            String nextTaskReferenceName = taskToSchedule.getTaskReferenceName();\n            if (workflow.getTasks().stream()\n                    .anyMatch(\n                            runningTask ->\n                                    runningTask\n                                            .getReferenceTaskName()\n                                            .equals(nextTaskReferenceName))) {\n                return Collections.emptyList();\n            }\n        }\n        if (taskToSchedule != null) {\n            return getTasksToBeScheduled(workflow, taskToSchedule, 0);\n        }\n\n        return Collections.emptyList();\n    }\n\n    private String getNextTasksToBeScheduled(WorkflowModel workflow, TaskModel task) {\n        final WorkflowDef def = workflow.getWorkflowDefinition();\n\n        String taskReferenceName = task.getReferenceTaskName();\n        WorkflowTask taskToSchedule = def.getNextTask(taskReferenceName);\n        while (isTaskSkipped(taskToSchedule, workflow)) {\n            taskToSchedule = def.getNextTask(taskToSchedule.getTaskReferenceName());\n        }\n        return taskToSchedule == null ? null : taskToSchedule.getTaskReferenceName();\n    }\n\n    @VisibleForTesting\n    Optional<TaskModel> retry(\n            TaskDef taskDefinition,\n            WorkflowTask workflowTask,\n            TaskModel task,\n            WorkflowModel workflow)\n            throws TerminateWorkflowException {\n\n        int retryCount = task.getRetryCount();\n\n        if (taskDefinition == null) {\n            taskDefinition = metadataDAO.getTaskDef(task.getTaskDefName());\n        }\n\n        final int expectedRetryCount =\n                taskDefinition == null\n                        ? 0\n                        : Optional.ofNullable(workflowTask)\n                                .map(WorkflowTask::getRetryCount)\n                                .orElse(taskDefinition.getRetryCount());\n        if (!task.getStatus().isRetriable()\n                || TaskType.isBuiltIn(task.getTaskType())\n                || expectedRetryCount <= retryCount) {\n            if (workflowTask != null && workflowTask.isOptional()) {\n                return Optional.empty();\n            }\n            WorkflowModel.Status status;\n            switch (task.getStatus()) {\n                case CANCELED:\n                    status = WorkflowModel.Status.TERMINATED;\n                    break;\n                case TIMED_OUT:\n                    status = WorkflowModel.Status.TIMED_OUT;\n                    break;\n                default:\n                    status = WorkflowModel.Status.FAILED;\n                    break;\n            }\n            updateWorkflowOutput(workflow, task);\n            final String errMsg =\n                    String.format(\n                            \"Task %s failed with status: %s and reason: '%s'\",\n                            task.getTaskId(), status, task.getReasonForIncompletion());\n            throw new TerminateWorkflowException(errMsg, status, task);\n        }\n\n        // retry... - but not immediately - put a delay...\n        int startDelay = taskDefinition.getRetryDelaySeconds();\n        switch (taskDefinition.getRetryLogic()) {\n            case FIXED:\n                startDelay = taskDefinition.getRetryDelaySeconds();\n                break;\n            case LINEAR_BACKOFF:\n                int linearRetryDelaySeconds =\n                        taskDefinition.getRetryDelaySeconds()\n                                * taskDefinition.getBackoffScaleFactor()\n                                * (task.getRetryCount() + 1);\n                // Reset integer overflow to max value\n                startDelay =\n                        linearRetryDelaySeconds < 0 ? Integer.MAX_VALUE : linearRetryDelaySeconds;\n                break;\n            case EXPONENTIAL_BACKOFF:\n                int exponentialRetryDelaySeconds =\n                        taskDefinition.getRetryDelaySeconds()\n                                * (int) Math.pow(2, task.getRetryCount());\n                // Reset integer overflow to max value\n                startDelay =\n                        exponentialRetryDelaySeconds < 0\n                                ? Integer.MAX_VALUE\n                                : exponentialRetryDelaySeconds;\n                break;\n        }\n\n        task.setRetried(true);\n\n        TaskModel rescheduled = task.copy();\n        rescheduled.setStartDelayInSeconds(startDelay);\n        rescheduled.setCallbackAfterSeconds(startDelay);\n        rescheduled.setRetryCount(task.getRetryCount() + 1);\n        rescheduled.setRetried(false);\n        rescheduled.setTaskId(idGenerator.generate());\n        rescheduled.setRetriedTaskId(task.getTaskId());\n        rescheduled.setStatus(SCHEDULED);\n        rescheduled.setPollCount(0);\n        rescheduled.setInputData(new HashMap<>(task.getInputData()));\n        rescheduled.setReasonForIncompletion(null);\n        rescheduled.setSubWorkflowId(null);\n        rescheduled.setSeq(0);\n        rescheduled.setScheduledTime(0);\n        rescheduled.setStartTime(0);\n        rescheduled.setEndTime(0);\n        rescheduled.setWorkerId(null);\n\n        if (StringUtils.isNotBlank(task.getExternalInputPayloadStoragePath())) {\n            rescheduled.setExternalInputPayloadStoragePath(\n                    task.getExternalInputPayloadStoragePath());\n        } else {\n            rescheduled.addInput(task.getInputData());\n        }\n        if (workflowTask != null && workflow.getWorkflowDefinition().getSchemaVersion() > 1) {\n            Map<String, Object> taskInput =\n                    parametersUtils.getTaskInputV2(\n                            workflowTask.getInputParameters(),\n                            workflow,\n                            rescheduled.getTaskId(),\n                            taskDefinition);\n            rescheduled.addInput(taskInput);\n        }\n        // for the schema version 1, we do not have to recompute the inputs\n        return Optional.of(rescheduled);\n    }\n\n    @VisibleForTesting\n    void checkWorkflowTimeout(WorkflowModel workflow) {\n        WorkflowDef workflowDef = workflow.getWorkflowDefinition();\n        if (workflowDef == null) {\n            LOGGER.warn(\"Missing workflow definition : {}\", workflow.getWorkflowId());\n            return;\n        }\n        if (workflow.getStatus().isTerminal() || workflowDef.getTimeoutSeconds() <= 0) {\n            return;\n        }\n\n        long timeout = 1000L * workflowDef.getTimeoutSeconds();\n        long now = System.currentTimeMillis();\n        long elapsedTime =\n                workflow.getLastRetriedTime() > 0\n                        ? now - workflow.getLastRetriedTime()\n                        : now - workflow.getCreateTime();\n\n        if (elapsedTime < timeout) {\n            return;\n        }\n\n        String reason =\n                String.format(\n                        \"Workflow timed out after %d seconds. Timeout configured as %d seconds. \"\n                                + \"Timeout policy configured to %s\",\n                        elapsedTime / 1000L,\n                        workflowDef.getTimeoutSeconds(),\n                        workflowDef.getTimeoutPolicy().name());\n\n        switch (workflowDef.getTimeoutPolicy()) {\n            case ALERT_ONLY:\n                LOGGER.info(\"{} {}\", workflow.getWorkflowId(), reason);\n                Monitors.recordWorkflowTermination(\n                        workflow.getWorkflowName(),\n                        WorkflowModel.Status.TIMED_OUT,\n                        workflow.getOwnerApp());\n                return;\n            case TIME_OUT_WF:\n                throw new TerminateWorkflowException(reason, WorkflowModel.Status.TIMED_OUT);\n        }\n    }\n\n    @VisibleForTesting\n    void checkTaskTimeout(TaskDef taskDef, TaskModel task) {\n\n        if (taskDef == null) {\n            LOGGER.warn(\n                    \"Missing task definition for task:{}/{} in workflow:{}\",\n                    task.getTaskId(),\n                    task.getTaskDefName(),\n                    task.getWorkflowInstanceId());\n            return;\n        }\n        if (task.getStatus().isTerminal()\n                || taskDef.getTimeoutSeconds() <= 0\n                || task.getStartTime() <= 0) {\n            return;\n        }\n\n        long timeout = 1000L * taskDef.getTimeoutSeconds();\n        long now = System.currentTimeMillis();\n        long elapsedTime =\n                now - (task.getStartTime() + ((long) task.getStartDelayInSeconds() * 1000L));\n\n        if (elapsedTime < timeout) {\n            return;\n        }\n\n        String reason =\n                String.format(\n                        \"Task timed out after %d seconds. Timeout configured as %d seconds. \"\n                                + \"Timeout policy configured to %s\",\n                        elapsedTime / 1000L,\n                        taskDef.getTimeoutSeconds(),\n                        taskDef.getTimeoutPolicy().name());\n        timeoutTaskWithTimeoutPolicy(reason, taskDef, task);\n    }\n\n    @VisibleForTesting\n    void checkTaskPollTimeout(TaskDef taskDef, TaskModel task) {\n        if (taskDef == null) {\n            LOGGER.warn(\n                    \"Missing task definition for task:{}/{} in workflow:{}\",\n                    task.getTaskId(),\n                    task.getTaskDefName(),\n                    task.getWorkflowInstanceId());\n            return;\n        }\n        if (taskDef.getPollTimeoutSeconds() == null\n                || taskDef.getPollTimeoutSeconds() <= 0\n                || !task.getStatus().equals(SCHEDULED)) {\n            return;\n        }\n\n        final long pollTimeout = 1000L * taskDef.getPollTimeoutSeconds();\n        final long adjustedPollTimeout = pollTimeout + task.getCallbackAfterSeconds() * 1000L;\n        final long now = System.currentTimeMillis();\n        final long pollElapsedTime =\n                now - (task.getScheduledTime() + ((long) task.getStartDelayInSeconds() * 1000L));\n\n        if (pollElapsedTime < adjustedPollTimeout) {\n            return;\n        }\n\n        String reason =\n                String.format(\n                        \"Task poll timed out after %d seconds. Poll timeout configured as %d seconds. Timeout policy configured to %s\",\n                        pollElapsedTime / 1000L,\n                        pollTimeout / 1000L,\n                        taskDef.getTimeoutPolicy().name());\n        timeoutTaskWithTimeoutPolicy(reason, taskDef, task);\n    }\n\n    void timeoutTaskWithTimeoutPolicy(String reason, TaskDef taskDef, TaskModel task) {\n        Monitors.recordTaskTimeout(task.getTaskDefName());\n\n        switch (taskDef.getTimeoutPolicy()) {\n            case ALERT_ONLY:\n                LOGGER.info(reason);\n                return;\n            case RETRY:\n                task.setStatus(TIMED_OUT);\n                task.setReasonForIncompletion(reason);\n                return;\n            case TIME_OUT_WF:\n                task.setStatus(TIMED_OUT);\n                task.setReasonForIncompletion(reason);\n                throw new TerminateWorkflowException(reason, WorkflowModel.Status.TIMED_OUT, task);\n        }\n    }\n\n    @VisibleForTesting\n    boolean isResponseTimedOut(TaskDef taskDefinition, TaskModel task) {\n        if (taskDefinition == null) {\n            LOGGER.warn(\n                    \"missing task type : {}, workflowId= {}\",\n                    task.getTaskDefName(),\n                    task.getWorkflowInstanceId());\n            return false;\n        }\n\n        if (task.getStatus().isTerminal() || isAyncCompleteSystemTask(task)) {\n            return false;\n        }\n\n        // calculate pendingTime\n        long now = System.currentTimeMillis();\n        long callbackTime = 1000L * task.getCallbackAfterSeconds();\n        long referenceTime =\n                task.getUpdateTime() > 0 ? task.getUpdateTime() : task.getScheduledTime();\n        long pendingTime = now - (referenceTime + callbackTime);\n        Monitors.recordTaskPendingTime(task.getTaskType(), task.getWorkflowType(), pendingTime);\n        long thresholdMS = taskPendingTimeThresholdMins * 60 * 1000;\n        if (pendingTime > thresholdMS) {\n            LOGGER.warn(\n                    \"Task: {} of type: {} in workflow: {}/{} is in pending state for longer than {} ms\",\n                    task.getTaskId(),\n                    task.getTaskType(),\n                    task.getWorkflowInstanceId(),\n                    task.getWorkflowType(),\n                    thresholdMS);\n        }\n\n        if (!task.getStatus().equals(IN_PROGRESS)\n                || taskDefinition.getResponseTimeoutSeconds() == 0) {\n            return false;\n        }\n\n        LOGGER.debug(\n                \"Evaluating responseTimeOut for Task: {}, with Task Definition: {}\",\n                task,\n                taskDefinition);\n        long responseTimeout = 1000L * taskDefinition.getResponseTimeoutSeconds();\n        long adjustedResponseTimeout = responseTimeout + callbackTime;\n        long noResponseTime = now - task.getUpdateTime();\n\n        if (noResponseTime < adjustedResponseTimeout) {\n            LOGGER.debug(\n                    \"Current responseTime: {} has not exceeded the configured responseTimeout of {} for the Task: {} with Task Definition: {}\",\n                    pendingTime,\n                    responseTimeout,\n                    task,\n                    taskDefinition);\n            return false;\n        }\n\n        Monitors.recordTaskResponseTimeout(task.getTaskDefName());\n        return true;\n    }\n\n    private void timeoutTask(TaskDef taskDef, TaskModel task) {\n        String reason =\n                \"responseTimeout: \"\n                        + taskDef.getResponseTimeoutSeconds()\n                        + \" exceeded for the taskId: \"\n                        + task.getTaskId()\n                        + \" with Task Definition: \"\n                        + task.getTaskDefName();\n        LOGGER.debug(reason);\n        task.setStatus(TIMED_OUT);\n        task.setReasonForIncompletion(reason);\n    }\n\n    public List<TaskModel> getTasksToBeScheduled(\n            WorkflowModel workflow, WorkflowTask taskToSchedule, int retryCount) {\n        return getTasksToBeScheduled(workflow, taskToSchedule, retryCount, null);\n    }\n\n    public List<TaskModel> getTasksToBeScheduled(\n            WorkflowModel workflow,\n            WorkflowTask taskToSchedule,\n            int retryCount,\n            String retriedTaskId) {\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        taskToSchedule.getInputParameters(), workflow, null, null);\n\n        String type = taskToSchedule.getType();\n\n        // get tasks already scheduled (in progress/terminal) for  this workflow instance\n        List<String> tasksInWorkflow =\n                workflow.getTasks().stream()\n                        .filter(\n                                runningTask ->\n                                        runningTask.getStatus().equals(TaskModel.Status.IN_PROGRESS)\n                                                || runningTask.getStatus().isTerminal())\n                        .map(TaskModel::getReferenceTaskName)\n                        .collect(Collectors.toList());\n\n        String taskId = idGenerator.generate();\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(taskToSchedule.getTaskDefinition())\n                        .withWorkflowTask(taskToSchedule)\n                        .withTaskInput(input)\n                        .withRetryCount(retryCount)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .withDeciderService(this)\n                        .build();\n\n        // For static forks, each branch of the fork creates a join task upon completion for\n        // dynamic forks, a join task is created with the fork and also with each branch of the\n        // fork.\n        // A new task must only be scheduled if a task, with the same reference name is not already\n        // in this workflow instance\n        return taskMappers\n                .getOrDefault(type, taskMappers.get(USER_DEFINED.name()))\n                .getMappedTasks(taskMapperContext)\n                .stream()\n                .filter(task -> !tasksInWorkflow.contains(task.getReferenceTaskName()))\n                .collect(Collectors.toList());\n    }\n\n    private boolean isTaskSkipped(WorkflowTask taskToSchedule, WorkflowModel workflow) {\n        try {\n            boolean isTaskSkipped = false;\n            if (taskToSchedule != null) {\n                TaskModel t = workflow.getTaskByRefName(taskToSchedule.getTaskReferenceName());\n                if (t == null) {\n                    isTaskSkipped = false;\n                } else if (t.getStatus().equals(SKIPPED)) {\n                    isTaskSkipped = true;\n                }\n            }\n            return isTaskSkipped;\n        } catch (Exception e) {\n            throw new TerminateWorkflowException(e.getMessage());\n        }\n    }\n\n    private boolean isAyncCompleteSystemTask(TaskModel task) {\n        return systemTaskRegistry.isSystemTask(task.getTaskType())\n                && systemTaskRegistry.get(task.getTaskType()).isAsyncComplete(task);\n    }\n\n    public static class DeciderOutcome {\n\n        List<TaskModel> tasksToBeScheduled = new LinkedList<>();\n        List<TaskModel> tasksToBeUpdated = new LinkedList<>();\n        boolean isComplete;\n        TaskModel terminateTask;\n\n        private DeciderOutcome() {}\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/StartWorkflowInput.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport java.util.Map;\nimport java.util.Objects;\n\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\n\npublic class StartWorkflowInput {\n\n    private String name;\n    private Integer version;\n    private WorkflowDef workflowDefinition;\n    private Map<String, Object> workflowInput;\n    private String externalInputPayloadStoragePath;\n    private String correlationId;\n    private Integer priority;\n    private String parentWorkflowId;\n    private String parentWorkflowTaskId;\n    private String event;\n    private Map<String, String> taskToDomain;\n    private String workflowId;\n    private String triggeringWorkflowId;\n\n    public StartWorkflowInput() {}\n\n    public StartWorkflowInput(StartWorkflowRequest startWorkflowRequest) {\n        this.name = startWorkflowRequest.getName();\n        this.version = startWorkflowRequest.getVersion();\n        this.workflowDefinition = startWorkflowRequest.getWorkflowDef();\n        this.correlationId = startWorkflowRequest.getCorrelationId();\n        this.priority = startWorkflowRequest.getPriority();\n        this.workflowInput = startWorkflowRequest.getInput();\n        this.externalInputPayloadStoragePath =\n                startWorkflowRequest.getExternalInputPayloadStoragePath();\n        this.taskToDomain = startWorkflowRequest.getTaskToDomain();\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public Integer getVersion() {\n        return version;\n    }\n\n    public void setVersion(Integer version) {\n        this.version = version;\n    }\n\n    public WorkflowDef getWorkflowDefinition() {\n        return workflowDefinition;\n    }\n\n    public void setWorkflowDefinition(WorkflowDef workflowDefinition) {\n        this.workflowDefinition = workflowDefinition;\n    }\n\n    public Map<String, Object> getWorkflowInput() {\n        return workflowInput;\n    }\n\n    public void setWorkflowInput(Map<String, Object> workflowInput) {\n        this.workflowInput = workflowInput;\n    }\n\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    public Integer getPriority() {\n        return priority;\n    }\n\n    public void setPriority(Integer priority) {\n        this.priority = priority;\n    }\n\n    public String getParentWorkflowId() {\n        return parentWorkflowId;\n    }\n\n    public void setParentWorkflowId(String parentWorkflowId) {\n        this.parentWorkflowId = parentWorkflowId;\n    }\n\n    public String getParentWorkflowTaskId() {\n        return parentWorkflowTaskId;\n    }\n\n    public void setParentWorkflowTaskId(String parentWorkflowTaskId) {\n        this.parentWorkflowTaskId = parentWorkflowTaskId;\n    }\n\n    public String getEvent() {\n        return event;\n    }\n\n    public void setEvent(String event) {\n        this.event = event;\n    }\n\n    public Map<String, String> getTaskToDomain() {\n        return taskToDomain;\n    }\n\n    public void setTaskToDomain(Map<String, String> taskToDomain) {\n        this.taskToDomain = taskToDomain;\n    }\n\n    public String getWorkflowId() {\n        return workflowId;\n    }\n\n    public void setWorkflowId(String workflowId) {\n        this.workflowId = workflowId;\n    }\n\n    public String getTriggeringWorkflowId() {\n        return triggeringWorkflowId;\n    }\n\n    public void setTriggeringWorkflowId(String triggeringWorkflowId) {\n        this.triggeringWorkflowId = triggeringWorkflowId;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        StartWorkflowInput that = (StartWorkflowInput) o;\n        return Objects.equals(name, that.name)\n                && Objects.equals(version, that.version)\n                && Objects.equals(workflowDefinition, that.workflowDefinition)\n                && Objects.equals(workflowInput, that.workflowInput)\n                && Objects.equals(\n                        externalInputPayloadStoragePath, that.externalInputPayloadStoragePath)\n                && Objects.equals(correlationId, that.correlationId)\n                && Objects.equals(priority, that.priority)\n                && Objects.equals(parentWorkflowId, that.parentWorkflowId)\n                && Objects.equals(parentWorkflowTaskId, that.parentWorkflowTaskId)\n                && Objects.equals(event, that.event)\n                && Objects.equals(taskToDomain, that.taskToDomain)\n                && Objects.equals(triggeringWorkflowId, that.triggeringWorkflowId)\n                && Objects.equals(workflowId, that.workflowId);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                name,\n                version,\n                workflowDefinition,\n                workflowInput,\n                externalInputPayloadStoragePath,\n                correlationId,\n                priority,\n                parentWorkflowId,\n                parentWorkflowTaskId,\n                event,\n                taskToDomain,\n                triggeringWorkflowId,\n                workflowId);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/WorkflowExecutor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport java.util.*;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.time.StopWatch;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.*;\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.SkipTaskRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.WorkflowContext;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.event.WorkflowCreationEvent;\nimport com.netflix.conductor.core.event.WorkflowEvaluationEvent;\nimport com.netflix.conductor.core.exception.*;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.core.execution.tasks.Terminate;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.core.listener.TaskStatusListener;\nimport com.netflix.conductor.core.listener.WorkflowStatusListener;\nimport com.netflix.conductor.core.metadata.MetadataMapperService;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.conductor.service.ExecutionLockService;\n\nimport static com.netflix.conductor.core.utils.Utils.DECIDER_QUEUE;\nimport static com.netflix.conductor.model.TaskModel.Status.*;\n\n/** Workflow services provider interface */\n@Trace\n@Component\npublic class WorkflowExecutor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowExecutor.class);\n    private static final int EXPEDITED_PRIORITY = 10;\n    private static final String CLASS_NAME = WorkflowExecutor.class.getSimpleName();\n    private static final Predicate<TaskModel> UNSUCCESSFUL_TERMINAL_TASK =\n            task -> !task.getStatus().isSuccessful() && task.getStatus().isTerminal();\n    private static final Predicate<TaskModel> UNSUCCESSFUL_JOIN_TASK =\n            UNSUCCESSFUL_TERMINAL_TASK.and(t -> TaskType.TASK_TYPE_JOIN.equals(t.getTaskType()));\n    private static final Predicate<TaskModel> NON_TERMINAL_TASK =\n            task -> !task.getStatus().isTerminal();\n    private final MetadataDAO metadataDAO;\n    private final QueueDAO queueDAO;\n    private final DeciderService deciderService;\n    private final ConductorProperties properties;\n    private final MetadataMapperService metadataMapperService;\n    private final ExecutionDAOFacade executionDAOFacade;\n    private final ParametersUtils parametersUtils;\n    private final IDGenerator idGenerator;\n    private final WorkflowStatusListener workflowStatusListener;\n    private final TaskStatusListener taskStatusListener;\n    private final SystemTaskRegistry systemTaskRegistry;\n    private final ApplicationEventPublisher eventPublisher;\n    private long activeWorkerLastPollMs;\n    private final ExecutionLockService executionLockService;\n\n    private final Predicate<PollData> validateLastPolledTime =\n            pollData ->\n                    pollData.getLastPollTime()\n                            > System.currentTimeMillis() - activeWorkerLastPollMs;\n\n    public WorkflowExecutor(\n            DeciderService deciderService,\n            MetadataDAO metadataDAO,\n            QueueDAO queueDAO,\n            MetadataMapperService metadataMapperService,\n            WorkflowStatusListener workflowStatusListener,\n            TaskStatusListener taskStatusListener,\n            ExecutionDAOFacade executionDAOFacade,\n            ConductorProperties properties,\n            ExecutionLockService executionLockService,\n            SystemTaskRegistry systemTaskRegistry,\n            ParametersUtils parametersUtils,\n            IDGenerator idGenerator,\n            ApplicationEventPublisher eventPublisher) {\n        this.deciderService = deciderService;\n        this.metadataDAO = metadataDAO;\n        this.queueDAO = queueDAO;\n        this.properties = properties;\n        this.metadataMapperService = metadataMapperService;\n        this.executionDAOFacade = executionDAOFacade;\n        this.activeWorkerLastPollMs = properties.getActiveWorkerLastPollTimeout().toMillis();\n        this.workflowStatusListener = workflowStatusListener;\n        this.taskStatusListener = taskStatusListener;\n        this.executionLockService = executionLockService;\n        this.parametersUtils = parametersUtils;\n        this.idGenerator = idGenerator;\n        this.systemTaskRegistry = systemTaskRegistry;\n        this.eventPublisher = eventPublisher;\n    }\n\n    /**\n     * @param workflowId the id of the workflow for which task callbacks are to be reset\n     * @throws ConflictException if the workflow is in terminal state\n     */\n    public void resetCallbacksForWorkflow(String workflowId) {\n        WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n        if (workflow.getStatus().isTerminal()) {\n            throw new ConflictException(\n                    \"Workflow is in terminal state. Status = %s\", workflow.getStatus());\n        }\n\n        // Get SIMPLE tasks in SCHEDULED state that have callbackAfterSeconds > 0 and set the\n        // callbackAfterSeconds to 0\n        workflow.getTasks().stream()\n                .filter(\n                        task ->\n                                !systemTaskRegistry.isSystemTask(task.getTaskType())\n                                        && SCHEDULED == task.getStatus()\n                                        && task.getCallbackAfterSeconds() > 0)\n                .forEach(\n                        task -> {\n                            if (queueDAO.resetOffsetTime(\n                                    QueueUtils.getQueueName(task), task.getTaskId())) {\n                                task.setCallbackAfterSeconds(0);\n                                executionDAOFacade.updateTask(task);\n                            }\n                        });\n    }\n\n    public String rerun(RerunWorkflowRequest request) {\n        Utils.checkNotNull(request.getReRunFromWorkflowId(), \"reRunFromWorkflowId is missing\");\n        if (!rerunWF(\n                request.getReRunFromWorkflowId(),\n                request.getReRunFromTaskId(),\n                request.getTaskInput(),\n                request.getWorkflowInput(),\n                request.getCorrelationId())) {\n            throw new IllegalArgumentException(\n                    \"Task \" + request.getReRunFromTaskId() + \" not found\");\n        }\n        return request.getReRunFromWorkflowId();\n    }\n\n    /**\n     * @param workflowId the id of the workflow to be restarted\n     * @param useLatestDefinitions if true, use the latest workflow and task definitions upon\n     *     restart\n     * @throws ConflictException Workflow is not in a terminal state.\n     * @throws NotFoundException Workflow definition is not found or Workflow is deemed\n     *     non-restartable as per workflow definition.\n     */\n    public void restart(String workflowId, boolean useLatestDefinitions) {\n        final WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n\n        if (!workflow.getStatus().isTerminal()) {\n            String errorMsg =\n                    String.format(\n                            \"Workflow: %s is not in terminal state, unable to restart.\", workflow);\n            LOGGER.error(errorMsg);\n            throw new ConflictException(errorMsg);\n        }\n\n        WorkflowDef workflowDef;\n        if (useLatestDefinitions) {\n            workflowDef =\n                    metadataDAO\n                            .getLatestWorkflowDef(workflow.getWorkflowName())\n                            .orElseThrow(\n                                    () ->\n                                            new NotFoundException(\n                                                    \"Unable to find latest definition for %s\",\n                                                    workflowId));\n            workflow.setWorkflowDefinition(workflowDef);\n            workflowDef = metadataMapperService.populateTaskDefinitions(workflowDef);\n        } else {\n            workflowDef =\n                    Optional.ofNullable(workflow.getWorkflowDefinition())\n                            .orElseGet(\n                                    () ->\n                                            metadataDAO\n                                                    .getWorkflowDef(\n                                                            workflow.getWorkflowName(),\n                                                            workflow.getWorkflowVersion())\n                                                    .orElseThrow(\n                                                            () ->\n                                                                    new NotFoundException(\n                                                                            \"Unable to find definition for %s\",\n                                                                            workflowId)));\n        }\n\n        if (!workflowDef.isRestartable()\n                && workflow.getStatus()\n                        .equals(\n                                WorkflowModel.Status\n                                        .COMPLETED)) { // Can only restart non-completed workflows\n            // when the configuration is set to false\n            throw new NotFoundException(\"Workflow: %s is non-restartable\", workflow);\n        }\n\n        // Reset the workflow in the primary datastore and remove from indexer; then re-create it\n        executionDAOFacade.resetWorkflow(workflowId);\n\n        workflow.getTasks().clear();\n        workflow.setReasonForIncompletion(null);\n        workflow.setFailedTaskId(null);\n        workflow.setCreateTime(System.currentTimeMillis());\n        workflow.setEndTime(0);\n        workflow.setLastRetriedTime(0);\n        // Change the status to running\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setOutput(null);\n        workflow.setExternalOutputPayloadStoragePath(null);\n\n        try {\n            executionDAOFacade.createWorkflow(workflow);\n        } catch (Exception e) {\n            Monitors.recordWorkflowStartError(\n                    workflowDef.getName(), WorkflowContext.get().getClientApp());\n            LOGGER.error(\"Unable to restart workflow: {}\", workflowDef.getName(), e);\n            terminateWorkflow(workflowId, \"Error when restarting the workflow\");\n            throw e;\n        }\n\n        metadataMapperService.populateWorkflowWithDefinitions(workflow);\n        decide(workflowId);\n\n        updateAndPushParents(workflow, \"restarted\");\n    }\n\n    /**\n     * Gets the last instance of each failed task and reschedule each Gets all cancelled tasks and\n     * schedule all of them except JOIN (join should change status to INPROGRESS) Switch workflow\n     * back to RUNNING status and call decider.\n     *\n     * @param workflowId the id of the workflow to be retried\n     */\n    public void retry(String workflowId, boolean resumeSubworkflowTasks) {\n        WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n        if (!workflow.getStatus().isTerminal()) {\n            throw new NotFoundException(\n                    \"Workflow is still running.  status=%s\", workflow.getStatus());\n        }\n        if (workflow.getTasks().isEmpty()) {\n            throw new ConflictException(\"Workflow has not started yet\");\n        }\n\n        if (resumeSubworkflowTasks) {\n            Optional<TaskModel> taskToRetry =\n                    workflow.getTasks().stream().filter(UNSUCCESSFUL_TERMINAL_TASK).findFirst();\n            if (taskToRetry.isPresent()) {\n                workflow = findLastFailedSubWorkflowIfAny(taskToRetry.get(), workflow);\n                retry(workflow);\n                updateAndPushParents(workflow, \"retried\");\n            }\n        } else {\n            retry(workflow);\n            updateAndPushParents(workflow, \"retried\");\n        }\n    }\n\n    private void updateAndPushParents(WorkflowModel workflow, String operation) {\n        String workflowIdentifier = \"\";\n        while (workflow.hasParent()) {\n            // update parent's sub workflow task\n            TaskModel subWorkflowTask =\n                    executionDAOFacade.getTaskModel(workflow.getParentWorkflowTaskId());\n            if (subWorkflowTask.getWorkflowTask().isOptional()) {\n                // break out\n                LOGGER.info(\n                        \"Sub workflow task {} is optional, skip updating parents\", subWorkflowTask);\n                break;\n            }\n            subWorkflowTask.setSubworkflowChanged(true);\n            subWorkflowTask.setStatus(IN_PROGRESS);\n            executionDAOFacade.updateTask(subWorkflowTask);\n\n            // add an execution log\n            String currentWorkflowIdentifier = workflow.toShortString();\n            workflowIdentifier =\n                    !workflowIdentifier.equals(\"\")\n                            ? String.format(\n                                    \"%s -> %s\", currentWorkflowIdentifier, workflowIdentifier)\n                            : currentWorkflowIdentifier;\n            TaskExecLog log =\n                    new TaskExecLog(\n                            String.format(\"Sub workflow %s %s.\", workflowIdentifier, operation));\n            log.setTaskId(subWorkflowTask.getTaskId());\n            executionDAOFacade.addTaskExecLog(Collections.singletonList(log));\n            LOGGER.info(\"Task {} updated. {}\", log.getTaskId(), log.getLog());\n\n            // push the parent workflow to decider queue for asynchronous 'decide'\n            String parentWorkflowId = workflow.getParentWorkflowId();\n            WorkflowModel parentWorkflow =\n                    executionDAOFacade.getWorkflowModel(parentWorkflowId, true);\n            parentWorkflow.setStatus(WorkflowModel.Status.RUNNING);\n            parentWorkflow.setLastRetriedTime(System.currentTimeMillis());\n            executionDAOFacade.updateWorkflow(parentWorkflow);\n            expediteLazyWorkflowEvaluation(parentWorkflowId);\n\n            workflow = parentWorkflow;\n        }\n    }\n\n    private void retry(WorkflowModel workflow) {\n        // Get all FAILED or CANCELED tasks that are not COMPLETED (or reach other terminal states)\n        // on further executions.\n        // // Eg: for Seq of tasks task1.CANCELED, task1.COMPLETED, task1 shouldn't be retried.\n        // Throw an exception if there are no FAILED tasks.\n        // Handle JOIN task CANCELED status as special case.\n        Map<String, TaskModel> retriableMap = new HashMap<>();\n        for (TaskModel task : workflow.getTasks()) {\n            switch (task.getStatus()) {\n                case FAILED:\n                case FAILED_WITH_TERMINAL_ERROR:\n                case TIMED_OUT:\n                    retriableMap.put(task.getReferenceTaskName(), task);\n                    break;\n                case CANCELED:\n                    if (task.getTaskType().equalsIgnoreCase(TaskType.JOIN.toString())\n                            || task.getTaskType().equalsIgnoreCase(TaskType.DO_WHILE.toString())) {\n                        task.setStatus(IN_PROGRESS);\n                        addTaskToQueue(task);\n                        // Task doesn't have to be updated yet. Will be updated along with other\n                        // Workflow tasks downstream.\n                    } else {\n                        retriableMap.put(task.getReferenceTaskName(), task);\n                    }\n                    break;\n                default:\n                    retriableMap.remove(task.getReferenceTaskName());\n                    break;\n            }\n        }\n\n        // if workflow TIMED_OUT due to timeoutSeconds configured in the workflow definition,\n        // it may not have any unsuccessful tasks that can be retried\n        if (retriableMap.values().size() == 0\n                && workflow.getStatus() != WorkflowModel.Status.TIMED_OUT) {\n            throw new ConflictException(\n                    \"There are no retryable tasks! Use restart if you want to attempt entire workflow execution again.\");\n        }\n\n        // Update Workflow with new status.\n        // This should load Workflow from archive, if archived.\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setLastRetriedTime(System.currentTimeMillis());\n        String lastReasonForIncompletion = workflow.getReasonForIncompletion();\n        workflow.setReasonForIncompletion(null);\n        // Add to decider queue\n        queueDAO.push(\n                DECIDER_QUEUE,\n                workflow.getWorkflowId(),\n                workflow.getPriority(),\n                properties.getWorkflowOffsetTimeout().getSeconds());\n        executionDAOFacade.updateWorkflow(workflow);\n        LOGGER.info(\n                \"Workflow {} that failed due to '{}' was retried\",\n                workflow.toShortString(),\n                lastReasonForIncompletion);\n\n        // taskToBeRescheduled would set task `retried` to true, and hence it's important to\n        // updateTasks after obtaining task copy from taskToBeRescheduled.\n        final WorkflowModel finalWorkflow = workflow;\n        List<TaskModel> retriableTasks =\n                retriableMap.values().stream()\n                        .sorted(Comparator.comparingInt(TaskModel::getSeq))\n                        .map(task -> taskToBeRescheduled(finalWorkflow, task))\n                        .collect(Collectors.toList());\n\n        dedupAndAddTasks(workflow, retriableTasks);\n        // Note: updateTasks before updateWorkflow might fail when Workflow is archived and doesn't\n        // exist in primary store.\n        executionDAOFacade.updateTasks(workflow.getTasks());\n        scheduleTask(workflow, retriableTasks);\n    }\n\n    private WorkflowModel findLastFailedSubWorkflowIfAny(\n            TaskModel task, WorkflowModel parentWorkflow) {\n        if (TaskType.TASK_TYPE_SUB_WORKFLOW.equals(task.getTaskType())\n                && UNSUCCESSFUL_TERMINAL_TASK.test(task)) {\n            WorkflowModel subWorkflow =\n                    executionDAOFacade.getWorkflowModel(task.getSubWorkflowId(), true);\n            Optional<TaskModel> taskToRetry =\n                    subWorkflow.getTasks().stream().filter(UNSUCCESSFUL_TERMINAL_TASK).findFirst();\n            if (taskToRetry.isPresent()) {\n                return findLastFailedSubWorkflowIfAny(taskToRetry.get(), subWorkflow);\n            }\n        }\n        return parentWorkflow;\n    }\n\n    /**\n     * Reschedule a task\n     *\n     * @param task failed or cancelled task\n     * @return new instance of a task with \"SCHEDULED\" status\n     */\n    private TaskModel taskToBeRescheduled(WorkflowModel workflow, TaskModel task) {\n        TaskModel taskToBeRetried = task.copy();\n        taskToBeRetried.setTaskId(idGenerator.generate());\n        taskToBeRetried.setRetriedTaskId(task.getTaskId());\n        taskToBeRetried.setStatus(SCHEDULED);\n        taskToBeRetried.setRetryCount(task.getRetryCount() + 1);\n        taskToBeRetried.setRetried(false);\n        taskToBeRetried.setPollCount(0);\n        taskToBeRetried.setCallbackAfterSeconds(0);\n        taskToBeRetried.setSubWorkflowId(null);\n        taskToBeRetried.setScheduledTime(0);\n        taskToBeRetried.setStartTime(0);\n        taskToBeRetried.setEndTime(0);\n        taskToBeRetried.setWorkerId(null);\n        taskToBeRetried.setReasonForIncompletion(null);\n        taskToBeRetried.setSeq(0);\n\n        // perform parameter replacement for retried task\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInput(\n                        taskToBeRetried.getWorkflowTask().getInputParameters(),\n                        workflow,\n                        taskToBeRetried.getWorkflowTask().getTaskDefinition(),\n                        taskToBeRetried.getTaskId());\n        taskToBeRetried.getInputData().putAll(taskInput);\n\n        task.setRetried(true);\n        // since this task is being retried and a retry has been computed, task lifecycle is\n        // complete\n        task.setExecuted(true);\n        return taskToBeRetried;\n    }\n\n    private void endExecution(WorkflowModel workflow, TaskModel terminateTask) {\n        if (terminateTask != null) {\n            String terminationStatus =\n                    (String)\n                            terminateTask\n                                    .getInputData()\n                                    .get(Terminate.getTerminationStatusParameter());\n            String reason =\n                    (String)\n                            terminateTask\n                                    .getInputData()\n                                    .get(Terminate.getTerminationReasonParameter());\n            if (StringUtils.isBlank(reason)) {\n                reason =\n                        String.format(\n                                \"Workflow is %s by TERMINATE task: %s\",\n                                terminationStatus, terminateTask.getTaskId());\n            }\n            if (WorkflowModel.Status.FAILED.name().equals(terminationStatus)) {\n                workflow.setStatus(WorkflowModel.Status.FAILED);\n                workflow =\n                        terminate(\n                                workflow,\n                                new TerminateWorkflowException(\n                                        reason, workflow.getStatus(), terminateTask));\n            } else {\n                workflow.setReasonForIncompletion(reason);\n                workflow = completeWorkflow(workflow);\n            }\n        } else {\n            workflow = completeWorkflow(workflow);\n        }\n        cancelNonTerminalTasks(workflow);\n    }\n\n    /**\n     * @param workflow the workflow to be completed\n     * @throws ConflictException if workflow is already in terminal state.\n     */\n    @VisibleForTesting\n    WorkflowModel completeWorkflow(WorkflowModel workflow) {\n        LOGGER.debug(\"Completing workflow execution for {}\", workflow.getWorkflowId());\n\n        if (workflow.getStatus().equals(WorkflowModel.Status.COMPLETED)) {\n            queueDAO.remove(DECIDER_QUEUE, workflow.getWorkflowId()); // remove from the sweep queue\n            executionDAOFacade.removeFromPendingWorkflow(\n                    workflow.getWorkflowName(), workflow.getWorkflowId());\n            LOGGER.debug(\"Workflow: {} has already been completed.\", workflow.getWorkflowId());\n            return workflow;\n        }\n\n        if (workflow.getStatus().isTerminal()) {\n            String msg =\n                    \"Workflow is already in terminal state. Current status: \"\n                            + workflow.getStatus();\n            throw new ConflictException(msg);\n        }\n\n        deciderService.updateWorkflowOutput(workflow, null);\n\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n\n        // update the failed reference task names\n        List<TaskModel> failedTasks =\n                workflow.getTasks().stream()\n                        .filter(\n                                t ->\n                                        FAILED.equals(t.getStatus())\n                                                || FAILED_WITH_TERMINAL_ERROR.equals(t.getStatus()))\n                        .collect(Collectors.toList());\n\n        workflow.getFailedReferenceTaskNames()\n                .addAll(\n                        failedTasks.stream()\n                                .map(TaskModel::getReferenceTaskName)\n                                .collect(Collectors.toSet()));\n\n        workflow.getFailedTaskNames()\n                .addAll(\n                        failedTasks.stream()\n                                .map(TaskModel::getTaskDefName)\n                                .collect(Collectors.toSet()));\n\n        executionDAOFacade.updateWorkflow(workflow);\n        LOGGER.debug(\"Completed workflow execution for {}\", workflow.getWorkflowId());\n        workflowStatusListener.onWorkflowCompletedIfEnabled(workflow);\n        Monitors.recordWorkflowCompletion(\n                workflow.getWorkflowName(),\n                workflow.getEndTime() - workflow.getCreateTime(),\n                workflow.getOwnerApp());\n\n        if (workflow.hasParent()) {\n            updateParentWorkflowTask(workflow);\n            LOGGER.info(\n                    \"{} updated parent {} task {}\",\n                    workflow.toShortString(),\n                    workflow.getParentWorkflowId(),\n                    workflow.getParentWorkflowTaskId());\n            expediteLazyWorkflowEvaluation(workflow.getParentWorkflowId());\n        }\n\n        executionLockService.releaseLock(workflow.getWorkflowId());\n        executionLockService.deleteLock(workflow.getWorkflowId());\n        return workflow;\n    }\n\n    public void terminateWorkflow(String workflowId, String reason) {\n        WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n        if (WorkflowModel.Status.COMPLETED.equals(workflow.getStatus())) {\n            throw new ConflictException(\"Cannot terminate a COMPLETED workflow.\");\n        }\n        workflow.setStatus(WorkflowModel.Status.TERMINATED);\n        terminateWorkflow(workflow, reason, null);\n    }\n\n    /**\n     * @param workflow the workflow to be terminated\n     * @param reason the reason for termination\n     * @param failureWorkflow the failure workflow (if any) to be triggered as a result of this\n     *     termination\n     */\n    public WorkflowModel terminateWorkflow(\n            WorkflowModel workflow, String reason, String failureWorkflow) {\n        try {\n            executionLockService.acquireLock(workflow.getWorkflowId(), 60000);\n\n            if (!workflow.getStatus().isTerminal()) {\n                workflow.setStatus(WorkflowModel.Status.TERMINATED);\n            }\n\n            try {\n                deciderService.updateWorkflowOutput(workflow, null);\n            } catch (Exception e) {\n                // catch any failure in this step and continue the execution of terminating workflow\n                LOGGER.error(\n                        \"Failed to update output data for workflow: {}\",\n                        workflow.getWorkflowId(),\n                        e);\n                Monitors.error(CLASS_NAME, \"terminateWorkflow\");\n            }\n\n            // update the failed reference task names\n            List<TaskModel> failedTasks =\n                    workflow.getTasks().stream()\n                            .filter(\n                                    t ->\n                                            FAILED.equals(t.getStatus())\n                                                    || FAILED_WITH_TERMINAL_ERROR.equals(\n                                                            t.getStatus()))\n                            .collect(Collectors.toList());\n\n            workflow.getFailedReferenceTaskNames()\n                    .addAll(\n                            failedTasks.stream()\n                                    .map(TaskModel::getReferenceTaskName)\n                                    .collect(Collectors.toSet()));\n\n            workflow.getFailedTaskNames()\n                    .addAll(\n                            failedTasks.stream()\n                                    .map(TaskModel::getTaskDefName)\n                                    .collect(Collectors.toSet()));\n\n            String workflowId = workflow.getWorkflowId();\n            workflow.setReasonForIncompletion(reason);\n            executionDAOFacade.updateWorkflow(workflow);\n            workflowStatusListener.onWorkflowTerminatedIfEnabled(workflow);\n            Monitors.recordWorkflowTermination(\n                    workflow.getWorkflowName(), workflow.getStatus(), workflow.getOwnerApp());\n            LOGGER.info(\"Workflow {} is terminated because of {}\", workflowId, reason);\n            List<TaskModel> tasks = workflow.getTasks();\n            try {\n                // Remove from the task queue if they were there\n                tasks.forEach(\n                        task -> queueDAO.remove(QueueUtils.getQueueName(task), task.getTaskId()));\n            } catch (Exception e) {\n                LOGGER.warn(\n                        \"Error removing task(s) from queue during workflow termination : {}\",\n                        workflowId,\n                        e);\n            }\n\n            if (workflow.hasParent()) {\n                updateParentWorkflowTask(workflow);\n                LOGGER.info(\n                        \"{} updated parent {} task {}\",\n                        workflow.toShortString(),\n                        workflow.getParentWorkflowId(),\n                        workflow.getParentWorkflowTaskId());\n                expediteLazyWorkflowEvaluation(workflow.getParentWorkflowId());\n            }\n\n            if (!StringUtils.isBlank(failureWorkflow)) {\n                Map<String, Object> input = new HashMap<>(workflow.getInput());\n                input.put(\"workflowId\", workflowId);\n                input.put(\"reason\", reason);\n                input.put(\"failureStatus\", workflow.getStatus().toString());\n                if (workflow.getFailedTaskId() != null) {\n                    input.put(\"failureTaskId\", workflow.getFailedTaskId());\n                }\n                input.put(\"failedWorkflow\", workflow);\n\n                try {\n                    String failureWFId = idGenerator.generate();\n                    StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n                    startWorkflowInput.setName(failureWorkflow);\n                    startWorkflowInput.setWorkflowInput(input);\n                    startWorkflowInput.setCorrelationId(workflow.getCorrelationId());\n                    startWorkflowInput.setTaskToDomain(workflow.getTaskToDomain());\n                    startWorkflowInput.setWorkflowId(failureWFId);\n                    startWorkflowInput.setTriggeringWorkflowId(workflowId);\n\n                    eventPublisher.publishEvent(new WorkflowCreationEvent(startWorkflowInput));\n\n                    workflow.addOutput(\"conductor.failure_workflow\", failureWFId);\n                } catch (Exception e) {\n                    LOGGER.error(\"Failed to start error workflow\", e);\n                    workflow.getOutput()\n                            .put(\n                                    \"conductor.failure_workflow\",\n                                    \"Error workflow \"\n                                            + failureWorkflow\n                                            + \" failed to start.  reason: \"\n                                            + e.getMessage());\n                    Monitors.recordWorkflowStartError(\n                            failureWorkflow, WorkflowContext.get().getClientApp());\n                }\n                executionDAOFacade.updateWorkflow(workflow);\n            }\n            executionDAOFacade.removeFromPendingWorkflow(\n                    workflow.getWorkflowName(), workflow.getWorkflowId());\n\n            List<String> erroredTasks = cancelNonTerminalTasks(workflow);\n            if (!erroredTasks.isEmpty()) {\n                throw new NonTransientException(\n                        String.format(\n                                \"Error canceling system tasks: %s\",\n                                String.join(\",\", erroredTasks)));\n            }\n            return workflow;\n        } finally {\n            executionLockService.releaseLock(workflow.getWorkflowId());\n            executionLockService.deleteLock(workflow.getWorkflowId());\n        }\n    }\n\n    /**\n     * @param taskResult the task result to be updated.\n     * @throws IllegalArgumentException if the {@link TaskResult} is null.\n     * @throws NotFoundException if the Task is not found.\n     */\n    public void updateTask(TaskResult taskResult) {\n        if (taskResult == null) {\n            throw new IllegalArgumentException(\"Task object is null\");\n        } else if (taskResult.isExtendLease()) {\n            extendLease(taskResult);\n            return;\n        }\n\n        String workflowId = taskResult.getWorkflowInstanceId();\n        WorkflowModel workflowInstance = executionDAOFacade.getWorkflowModel(workflowId, false);\n\n        TaskModel task =\n                Optional.ofNullable(executionDAOFacade.getTaskModel(taskResult.getTaskId()))\n                        .orElseThrow(\n                                () ->\n                                        new NotFoundException(\n                                                \"No such task found by id: %s\",\n                                                taskResult.getTaskId()));\n\n        LOGGER.debug(\"Task: {} belonging to Workflow {} being updated\", task, workflowInstance);\n\n        String taskQueueName = QueueUtils.getQueueName(task);\n\n        if (task.getStatus().isTerminal()) {\n            // Task was already updated....\n            queueDAO.remove(taskQueueName, taskResult.getTaskId());\n            LOGGER.info(\n                    \"Task: {} has already finished execution with status: {} within workflow: {}. Removed task from queue: {}\",\n                    task.getTaskId(),\n                    task.getStatus(),\n                    task.getWorkflowInstanceId(),\n                    taskQueueName);\n            Monitors.recordUpdateConflict(\n                    task.getTaskType(), workflowInstance.getWorkflowName(), task.getStatus());\n            return;\n        }\n\n        if (workflowInstance.getStatus().isTerminal()) {\n            // Workflow is in terminal state\n            queueDAO.remove(taskQueueName, taskResult.getTaskId());\n            LOGGER.info(\n                    \"Workflow: {} has already finished execution. Task update for: {} ignored and removed from Queue: {}.\",\n                    workflowInstance,\n                    taskResult.getTaskId(),\n                    taskQueueName);\n            Monitors.recordUpdateConflict(\n                    task.getTaskType(),\n                    workflowInstance.getWorkflowName(),\n                    workflowInstance.getStatus());\n            return;\n        }\n\n        // for system tasks, setting to SCHEDULED would mean restarting the task which is\n        // undesirable\n        // for worker tasks, set status to SCHEDULED and push to the queue\n        if (!systemTaskRegistry.isSystemTask(task.getTaskType())\n                && taskResult.getStatus() == TaskResult.Status.IN_PROGRESS) {\n            task.setStatus(SCHEDULED);\n        } else {\n            task.setStatus(TaskModel.Status.valueOf(taskResult.getStatus().name()));\n        }\n        task.setOutputMessage(taskResult.getOutputMessage());\n        task.setReasonForIncompletion(taskResult.getReasonForIncompletion());\n        task.setWorkerId(taskResult.getWorkerId());\n        task.setCallbackAfterSeconds(taskResult.getCallbackAfterSeconds());\n        task.setOutputData(taskResult.getOutputData());\n        task.setSubWorkflowId(taskResult.getSubWorkflowId());\n\n        if (StringUtils.isNotBlank(taskResult.getExternalOutputPayloadStoragePath())) {\n            task.setExternalOutputPayloadStoragePath(\n                    taskResult.getExternalOutputPayloadStoragePath());\n        }\n\n        if (task.getStatus().isTerminal()) {\n            task.setEndTime(System.currentTimeMillis());\n        }\n\n        // Update message in Task queue based on Task status\n        switch (task.getStatus()) {\n            case COMPLETED:\n            case CANCELED:\n            case FAILED:\n            case FAILED_WITH_TERMINAL_ERROR:\n            case TIMED_OUT:\n                try {\n                    queueDAO.remove(taskQueueName, taskResult.getTaskId());\n                    LOGGER.debug(\n                            \"Task: {} removed from taskQueue: {} since the task status is {}\",\n                            task,\n                            taskQueueName,\n                            task.getStatus().name());\n                } catch (Exception e) {\n                    // Ignore exceptions on queue remove as it wouldn't impact task and workflow\n                    // execution, and will be cleaned up eventually\n                    String errorMsg =\n                            String.format(\n                                    \"Error removing the message in queue for task: %s for workflow: %s\",\n                                    task.getTaskId(), workflowId);\n                    LOGGER.warn(errorMsg, e);\n                    Monitors.recordTaskQueueOpError(\n                            task.getTaskType(), workflowInstance.getWorkflowName());\n                }\n                break;\n            case IN_PROGRESS:\n            case SCHEDULED:\n                try {\n                    long callBack = taskResult.getCallbackAfterSeconds();\n                    queueDAO.postpone(\n                            taskQueueName, task.getTaskId(), task.getWorkflowPriority(), callBack);\n                    LOGGER.debug(\n                            \"Task: {} postponed in taskQueue: {} since the task status is {} with callbackAfterSeconds: {}\",\n                            task,\n                            taskQueueName,\n                            task.getStatus().name(),\n                            callBack);\n                } catch (Exception e) {\n                    // Throw exceptions on queue postpone, this would impact task execution\n                    String errorMsg =\n                            String.format(\n                                    \"Error postponing the message in queue for task: %s for workflow: %s\",\n                                    task.getTaskId(), workflowId);\n                    LOGGER.error(errorMsg, e);\n                    Monitors.recordTaskQueueOpError(\n                            task.getTaskType(), workflowInstance.getWorkflowName());\n                    throw new TransientException(errorMsg, e);\n                }\n                break;\n            default:\n                break;\n        }\n\n        // Throw a TransientException if below operations fail to avoid workflow inconsistencies.\n        try {\n            executionDAOFacade.updateTask(task);\n        } catch (Exception e) {\n            String errorMsg =\n                    String.format(\n                            \"Error updating task: %s for workflow: %s\",\n                            task.getTaskId(), workflowId);\n            LOGGER.error(errorMsg, e);\n            Monitors.recordTaskUpdateError(task.getTaskType(), workflowInstance.getWorkflowName());\n            throw new TransientException(errorMsg, e);\n        }\n\n        try {\n            notifyTaskStatusListener(task);\n        } catch (Exception e) {\n            String errorMsg =\n                    String.format(\n                            \"Error while notifying TaskStatusListener: %s for workflow: %s\",\n                            task.getTaskId(), workflowId);\n            LOGGER.error(errorMsg, e);\n        }\n\n        taskResult.getLogs().forEach(taskExecLog -> taskExecLog.setTaskId(task.getTaskId()));\n        executionDAOFacade.addTaskExecLog(taskResult.getLogs());\n\n        if (task.getStatus().isTerminal()) {\n            long duration = getTaskDuration(0, task);\n            long lastDuration = task.getEndTime() - task.getStartTime();\n            Monitors.recordTaskExecutionTime(\n                    task.getTaskDefName(), duration, true, task.getStatus());\n            Monitors.recordTaskExecutionTime(\n                    task.getTaskDefName(), lastDuration, false, task.getStatus());\n        }\n\n        if (!isLazyEvaluateWorkflow(workflowInstance.getWorkflowDefinition(), task)) {\n            decide(workflowId);\n        }\n    }\n\n    private void notifyTaskStatusListener(TaskModel task) {\n        switch (task.getStatus()) {\n            case COMPLETED:\n                taskStatusListener.onTaskCompleted(task);\n                break;\n            case CANCELED:\n                taskStatusListener.onTaskCanceled(task);\n                break;\n            case FAILED:\n                taskStatusListener.onTaskFailed(task);\n                break;\n            case FAILED_WITH_TERMINAL_ERROR:\n                taskStatusListener.onTaskFailedWithTerminalError(task);\n                break;\n            case TIMED_OUT:\n                taskStatusListener.onTaskTimedOut(task);\n                break;\n            case IN_PROGRESS:\n                taskStatusListener.onTaskInProgress(task);\n                break;\n            case SCHEDULED:\n                // no-op, already done in addTaskToQueue\n            default:\n                break;\n        }\n    }\n\n    private void extendLease(TaskResult taskResult) {\n        TaskModel task =\n                Optional.ofNullable(executionDAOFacade.getTaskModel(taskResult.getTaskId()))\n                        .orElseThrow(\n                                () ->\n                                        new NotFoundException(\n                                                \"No such task found by id: %s\",\n                                                taskResult.getTaskId()));\n\n        LOGGER.debug(\n                \"Extend lease for Task: {} belonging to Workflow: {}\",\n                task,\n                task.getWorkflowInstanceId());\n        if (!task.getStatus().isTerminal()) {\n            try {\n                executionDAOFacade.extendLease(task);\n            } catch (Exception e) {\n                String errorMsg =\n                        String.format(\n                                \"Error extend lease for Task: %s belonging to Workflow: %s\",\n                                task.getTaskId(), task.getWorkflowInstanceId());\n                LOGGER.error(errorMsg, e);\n                Monitors.recordTaskExtendLeaseError(task.getTaskType(), task.getWorkflowType());\n                throw new TransientException(errorMsg, e);\n            }\n        }\n    }\n\n    /**\n     * Determines if a workflow can be lazily evaluated, if it meets any of these criteria\n     *\n     * <ul>\n     *   <li>The task is NOT a loop task within DO_WHILE\n     *   <li>The task is one of the intermediate tasks in a branch within a FORK_JOIN\n     *   <li>The task is forked from a FORK_JOIN_DYNAMIC\n     * </ul>\n     *\n     * @param workflowDef The workflow definition of the workflow for which evaluation decision is\n     *     to be made\n     * @param task The task which is attempting to trigger the evaluation\n     * @return true if workflow can be lazily evaluated, false otherwise\n     */\n    @VisibleForTesting\n    boolean isLazyEvaluateWorkflow(WorkflowDef workflowDef, TaskModel task) {\n        if (task.isLoopOverTask()) {\n            return false;\n        }\n\n        String taskRefName = task.getReferenceTaskName();\n        List<WorkflowTask> workflowTasks = workflowDef.collectTasks();\n\n        List<WorkflowTask> forkTasks =\n                workflowTasks.stream()\n                        .filter(t -> t.getType().equals(TaskType.FORK_JOIN.name()))\n                        .collect(Collectors.toList());\n\n        List<WorkflowTask> joinTasks =\n                workflowTasks.stream()\n                        .filter(t -> t.getType().equals(TaskType.JOIN.name()))\n                        .collect(Collectors.toList());\n\n        if (forkTasks.stream().anyMatch(fork -> fork.has(taskRefName))) {\n            return joinTasks.stream().anyMatch(join -> join.getJoinOn().contains(taskRefName))\n                    && task.getStatus().isSuccessful();\n        }\n\n        return workflowTasks.stream().noneMatch(t -> t.getTaskReferenceName().equals(taskRefName))\n                && task.getStatus().isSuccessful();\n    }\n\n    public TaskModel getTask(String taskId) {\n        return Optional.ofNullable(executionDAOFacade.getTaskModel(taskId))\n                .map(\n                        task -> {\n                            if (task.getWorkflowTask() != null) {\n                                return metadataMapperService.populateTaskWithDefinition(task);\n                            }\n                            return task;\n                        })\n                .orElse(null);\n    }\n\n    public List<Workflow> getRunningWorkflows(String workflowName, int version) {\n        return executionDAOFacade.getPendingWorkflowsByName(workflowName, version);\n    }\n\n    public List<String> getWorkflows(String name, Integer version, Long startTime, Long endTime) {\n        return executionDAOFacade.getWorkflowsByName(name, startTime, endTime).stream()\n                .filter(workflow -> workflow.getWorkflowVersion() == version)\n                .map(Workflow::getWorkflowId)\n                .collect(Collectors.toList());\n    }\n\n    public List<String> getRunningWorkflowIds(String workflowName, int version) {\n        return executionDAOFacade.getRunningWorkflowIds(workflowName, version);\n    }\n\n    @EventListener(WorkflowEvaluationEvent.class)\n    public void handleWorkflowEvaluationEvent(WorkflowEvaluationEvent wee) {\n        decide(wee.getWorkflowModel());\n    }\n\n    /** Records a metric for the \"decide\" process. */\n    public WorkflowModel decide(String workflowId) {\n        StopWatch watch = new StopWatch();\n        watch.start();\n        if (!executionLockService.acquireLock(workflowId)) {\n            return null;\n        }\n        try {\n\n            WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n            if (workflow == null) {\n                // This can happen if the workflowId is incorrect\n                return null;\n            }\n            return decide(workflow);\n\n        } finally {\n            executionLockService.releaseLock(workflowId);\n            watch.stop();\n            Monitors.recordWorkflowDecisionTime(watch.getTime());\n        }\n    }\n\n    /**\n     * This method overloads the {@link #decide(String)}. It will acquire a lock and evaluate the\n     * state of the workflow.\n     *\n     * @param workflow the workflow to evaluate the state for\n     * @return the workflow\n     */\n    public WorkflowModel decideWithLock(WorkflowModel workflow) {\n        if (workflow == null) {\n            return null;\n        }\n        StopWatch watch = new StopWatch();\n        watch.start();\n        if (!executionLockService.acquireLock(workflow.getWorkflowId())) {\n            return null;\n        }\n        try {\n            return decide(workflow);\n\n        } finally {\n            executionLockService.releaseLock(workflow.getWorkflowId());\n            watch.stop();\n            Monitors.recordWorkflowDecisionTime(watch.getTime());\n        }\n    }\n\n    /**\n     * @param workflow the workflow to evaluate the state for\n     * @return true if the workflow has completed (success or failed), false otherwise. Note: This\n     *     method does not acquire the lock on the workflow and should ony be called / overridden if\n     *     No locking is required or lock is acquired externally\n     */\n    public WorkflowModel decide(WorkflowModel workflow) {\n        if (workflow.getStatus().isTerminal()) {\n            if (!workflow.getStatus().isSuccessful()) {\n                cancelNonTerminalTasks(workflow);\n            }\n            return workflow;\n        }\n\n        // we find any sub workflow tasks that have changed\n        // and change the workflow/task state accordingly\n        adjustStateIfSubWorkflowChanged(workflow);\n\n        try {\n            DeciderService.DeciderOutcome outcome = deciderService.decide(workflow);\n            if (outcome.isComplete) {\n                endExecution(workflow, outcome.terminateTask);\n                return workflow;\n            }\n\n            List<TaskModel> tasksToBeScheduled = outcome.tasksToBeScheduled;\n            setTaskDomains(tasksToBeScheduled, workflow);\n            List<TaskModel> tasksToBeUpdated = outcome.tasksToBeUpdated;\n\n            tasksToBeScheduled = dedupAndAddTasks(workflow, tasksToBeScheduled);\n\n            boolean stateChanged = scheduleTask(workflow, tasksToBeScheduled); // start\n\n            for (TaskModel task : outcome.tasksToBeScheduled) {\n                executionDAOFacade.populateTaskData(task);\n                if (systemTaskRegistry.isSystemTask(task.getTaskType())\n                        && NON_TERMINAL_TASK.test(task)) {\n                    WorkflowSystemTask workflowSystemTask =\n                            systemTaskRegistry.get(task.getTaskType());\n                    if (!workflowSystemTask.isAsync()\n                            && workflowSystemTask.execute(workflow, task, this)) {\n                        tasksToBeUpdated.add(task);\n                        stateChanged = true;\n                    }\n                }\n            }\n\n            if (!outcome.tasksToBeUpdated.isEmpty() || !tasksToBeScheduled.isEmpty()) {\n                executionDAOFacade.updateTasks(tasksToBeUpdated);\n            }\n\n            if (stateChanged) {\n                return decide(workflow);\n            }\n\n            if (!outcome.tasksToBeUpdated.isEmpty() || !tasksToBeScheduled.isEmpty()) {\n                executionDAOFacade.updateWorkflow(workflow);\n            }\n\n            return workflow;\n\n        } catch (TerminateWorkflowException twe) {\n            LOGGER.info(\"Execution terminated of workflow: {}\", workflow, twe);\n            terminate(workflow, twe);\n            return workflow;\n        } catch (RuntimeException e) {\n            LOGGER.error(\"Error deciding workflow: {}\", workflow.getWorkflowId(), e);\n            throw e;\n        }\n    }\n\n    private void adjustStateIfSubWorkflowChanged(WorkflowModel workflow) {\n        Optional<TaskModel> changedSubWorkflowTask = findChangedSubWorkflowTask(workflow);\n        if (changedSubWorkflowTask.isPresent()) {\n            // reset the flag\n            TaskModel subWorkflowTask = changedSubWorkflowTask.get();\n            subWorkflowTask.setSubworkflowChanged(false);\n            executionDAOFacade.updateTask(subWorkflowTask);\n\n            LOGGER.info(\n                    \"{} reset subworkflowChanged flag for {}\",\n                    workflow.toShortString(),\n                    subWorkflowTask.getTaskId());\n\n            // find all terminal and unsuccessful JOIN tasks and set them to IN_PROGRESS\n            if (workflow.getWorkflowDefinition().containsType(TaskType.TASK_TYPE_JOIN)\n                    || workflow.getWorkflowDefinition()\n                            .containsType(TaskType.TASK_TYPE_FORK_JOIN_DYNAMIC)) {\n                // if we are here, then the SUB_WORKFLOW task could be part of a FORK_JOIN or\n                // FORK_JOIN_DYNAMIC\n                // and the JOIN task(s) needs to be evaluated again, set them to IN_PROGRESS\n                workflow.getTasks().stream()\n                        .filter(UNSUCCESSFUL_JOIN_TASK)\n                        .peek(\n                                task -> {\n                                    task.setStatus(TaskModel.Status.IN_PROGRESS);\n                                    addTaskToQueue(task);\n                                })\n                        .forEach(executionDAOFacade::updateTask);\n            }\n        }\n    }\n\n    private Optional<TaskModel> findChangedSubWorkflowTask(WorkflowModel workflow) {\n        WorkflowDef workflowDef =\n                Optional.ofNullable(workflow.getWorkflowDefinition())\n                        .orElseGet(\n                                () ->\n                                        metadataDAO\n                                                .getWorkflowDef(\n                                                        workflow.getWorkflowName(),\n                                                        workflow.getWorkflowVersion())\n                                                .orElseThrow(\n                                                        () ->\n                                                                new TransientException(\n                                                                        \"Workflow Definition is not found\")));\n        if (workflowDef.containsType(TaskType.TASK_TYPE_SUB_WORKFLOW)\n                || workflow.getWorkflowDefinition()\n                        .containsType(TaskType.TASK_TYPE_FORK_JOIN_DYNAMIC)) {\n            return workflow.getTasks().stream()\n                    .filter(\n                            t ->\n                                    t.getTaskType().equals(TaskType.TASK_TYPE_SUB_WORKFLOW)\n                                            && t.isSubworkflowChanged()\n                                            && !t.isRetried())\n                    .findFirst();\n        }\n        return Optional.empty();\n    }\n\n    @VisibleForTesting\n    List<String> cancelNonTerminalTasks(WorkflowModel workflow) {\n        List<String> erroredTasks = new ArrayList<>();\n        // Update non-terminal tasks' status to CANCELED\n        for (TaskModel task : workflow.getTasks()) {\n            if (!task.getStatus().isTerminal()) {\n                // Cancel the ones which are not completed yet....\n                task.setStatus(CANCELED);\n                if (systemTaskRegistry.isSystemTask(task.getTaskType())) {\n                    WorkflowSystemTask workflowSystemTask =\n                            systemTaskRegistry.get(task.getTaskType());\n                    try {\n                        workflowSystemTask.cancel(workflow, task, this);\n                    } catch (Exception e) {\n                        erroredTasks.add(task.getReferenceTaskName());\n                        LOGGER.error(\n                                \"Error canceling system task:{}/{} in workflow: {}\",\n                                workflowSystemTask.getTaskType(),\n                                task.getTaskId(),\n                                workflow.getWorkflowId(),\n                                e);\n                    }\n                }\n                executionDAOFacade.updateTask(task);\n            }\n        }\n        if (erroredTasks.isEmpty()) {\n            try {\n                workflowStatusListener.onWorkflowFinalizedIfEnabled(workflow);\n                queueDAO.remove(DECIDER_QUEUE, workflow.getWorkflowId());\n            } catch (Exception e) {\n                LOGGER.error(\n                        \"Error removing workflow: {} from decider queue\",\n                        workflow.getWorkflowId(),\n                        e);\n            }\n        }\n        return erroredTasks;\n    }\n\n    @VisibleForTesting\n    List<TaskModel> dedupAndAddTasks(WorkflowModel workflow, List<TaskModel> tasks) {\n        Set<String> tasksInWorkflow =\n                workflow.getTasks().stream()\n                        .map(task -> task.getReferenceTaskName() + \"_\" + task.getRetryCount())\n                        .collect(Collectors.toSet());\n\n        List<TaskModel> dedupedTasks =\n                tasks.stream()\n                        .filter(\n                                task ->\n                                        !tasksInWorkflow.contains(\n                                                task.getReferenceTaskName()\n                                                        + \"_\"\n                                                        + task.getRetryCount()))\n                        .collect(Collectors.toList());\n\n        workflow.getTasks().addAll(dedupedTasks);\n        return dedupedTasks;\n    }\n\n    /**\n     * @throws ConflictException if the workflow is in terminal state.\n     */\n    public void pauseWorkflow(String workflowId) {\n        try {\n            executionLockService.acquireLock(workflowId, 60000);\n            WorkflowModel.Status status = WorkflowModel.Status.PAUSED;\n            WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, false);\n            if (workflow.getStatus().isTerminal()) {\n                throw new ConflictException(\n                        \"Workflow %s has ended, status cannot be updated.\",\n                        workflow.toShortString());\n            }\n            if (workflow.getStatus().equals(status)) {\n                return; // Already paused!\n            }\n            workflow.setStatus(status);\n            executionDAOFacade.updateWorkflow(workflow);\n        } finally {\n            executionLockService.releaseLock(workflowId);\n        }\n\n        // remove from the sweep queue\n        // any exceptions can be ignored, as this is not critical to the pause operation\n        try {\n            queueDAO.remove(DECIDER_QUEUE, workflowId);\n        } catch (Exception e) {\n            LOGGER.info(\n                    \"[pauseWorkflow] Error removing workflow: {} from decider queue\",\n                    workflowId,\n                    e);\n        }\n    }\n\n    /**\n     * @param workflowId the workflow to be resumed\n     * @throws IllegalStateException if the workflow is not in PAUSED state\n     */\n    public void resumeWorkflow(String workflowId) {\n        WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, false);\n        if (!workflow.getStatus().equals(WorkflowModel.Status.PAUSED)) {\n            throw new IllegalStateException(\n                    \"The workflow \"\n                            + workflowId\n                            + \" is not PAUSED so cannot resume. \"\n                            + \"Current status is \"\n                            + workflow.getStatus().name());\n        }\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setLastRetriedTime(System.currentTimeMillis());\n        // Add to decider queue\n        queueDAO.push(\n                DECIDER_QUEUE,\n                workflow.getWorkflowId(),\n                workflow.getPriority(),\n                properties.getWorkflowOffsetTimeout().getSeconds());\n        executionDAOFacade.updateWorkflow(workflow);\n        decide(workflowId);\n    }\n\n    /**\n     * @param workflowId the id of the workflow\n     * @param taskReferenceName the referenceName of the task to be skipped\n     * @param skipTaskRequest the {@link SkipTaskRequest} object\n     * @throws IllegalStateException\n     */\n    public void skipTaskFromWorkflow(\n            String workflowId, String taskReferenceName, SkipTaskRequest skipTaskRequest) {\n\n        WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n\n        // If the workflow is not running then cannot skip any task\n        if (!workflow.getStatus().equals(WorkflowModel.Status.RUNNING)) {\n            String errorMsg =\n                    String.format(\n                            \"The workflow %s is not running so the task referenced by %s cannot be skipped\",\n                            workflowId, taskReferenceName);\n            throw new IllegalStateException(errorMsg);\n        }\n\n        // Check if the reference name is as per the workflowdef\n        WorkflowTask workflowTask =\n                workflow.getWorkflowDefinition().getTaskByRefName(taskReferenceName);\n        if (workflowTask == null) {\n            String errorMsg =\n                    String.format(\n                            \"The task referenced by %s does not exist in the WorkflowDefinition %s\",\n                            taskReferenceName, workflow.getWorkflowName());\n            throw new IllegalStateException(errorMsg);\n        }\n\n        // If the task is already started the again it cannot be skipped\n        workflow.getTasks()\n                .forEach(\n                        task -> {\n                            if (task.getReferenceTaskName().equals(taskReferenceName)) {\n                                String errorMsg =\n                                        String.format(\n                                                \"The task referenced %s has already been processed, cannot be skipped\",\n                                                taskReferenceName);\n                                throw new IllegalStateException(errorMsg);\n                            }\n                        });\n\n        // Now create a \"SKIPPED\" task for this workflow\n        TaskModel taskToBeSkipped = new TaskModel();\n        taskToBeSkipped.setTaskId(idGenerator.generate());\n        taskToBeSkipped.setReferenceTaskName(taskReferenceName);\n        taskToBeSkipped.setWorkflowInstanceId(workflowId);\n        taskToBeSkipped.setWorkflowPriority(workflow.getPriority());\n        taskToBeSkipped.setStatus(SKIPPED);\n        taskToBeSkipped.setEndTime(System.currentTimeMillis());\n        taskToBeSkipped.setTaskType(workflowTask.getName());\n        taskToBeSkipped.setCorrelationId(workflow.getCorrelationId());\n        if (skipTaskRequest != null) {\n            taskToBeSkipped.setInputData(skipTaskRequest.getTaskInput());\n            taskToBeSkipped.setOutputData(skipTaskRequest.getTaskOutput());\n            taskToBeSkipped.setInputMessage(skipTaskRequest.getTaskInputMessage());\n            taskToBeSkipped.setOutputMessage(skipTaskRequest.getTaskOutputMessage());\n        }\n        executionDAOFacade.createTasks(Collections.singletonList(taskToBeSkipped));\n        decide(workflow.getWorkflowId());\n    }\n\n    public WorkflowModel getWorkflow(String workflowId, boolean includeTasks) {\n        return executionDAOFacade.getWorkflowModel(workflowId, includeTasks);\n    }\n\n    public void addTaskToQueue(TaskModel task) {\n        // put in queue\n        String taskQueueName = QueueUtils.getQueueName(task);\n        if (task.getCallbackAfterSeconds() > 0) {\n            queueDAO.push(\n                    taskQueueName,\n                    task.getTaskId(),\n                    task.getWorkflowPriority(),\n                    task.getCallbackAfterSeconds());\n        } else {\n            queueDAO.push(taskQueueName, task.getTaskId(), task.getWorkflowPriority(), 0);\n        }\n        LOGGER.debug(\n                \"Added task {} with priority {} to queue {} with call back seconds {}\",\n                task,\n                task.getWorkflowPriority(),\n                taskQueueName,\n                task.getCallbackAfterSeconds());\n    }\n\n    @VisibleForTesting\n    void setTaskDomains(List<TaskModel> tasks, WorkflowModel workflow) {\n        Map<String, String> taskToDomain = workflow.getTaskToDomain();\n        if (taskToDomain != null) {\n            // Step 1: Apply * mapping to all tasks, if present.\n            String domainstr = taskToDomain.get(\"*\");\n            if (StringUtils.isNotBlank(domainstr)) {\n                String[] domains = domainstr.split(\",\");\n                tasks.forEach(\n                        task -> {\n                            // Filter out SystemTask\n                            if (!systemTaskRegistry.isSystemTask(task.getTaskType())) {\n                                // Check which domain worker is polling\n                                // Set the task domain\n                                task.setDomain(getActiveDomain(task.getTaskType(), domains));\n                            }\n                        });\n            }\n            // Step 2: Override additional mappings.\n            tasks.forEach(\n                    task -> {\n                        if (!systemTaskRegistry.isSystemTask(task.getTaskType())) {\n                            String taskDomainstr = taskToDomain.get(task.getTaskType());\n                            if (taskDomainstr != null) {\n                                task.setDomain(\n                                        getActiveDomain(\n                                                task.getTaskType(), taskDomainstr.split(\",\")));\n                            }\n                        }\n                    });\n        }\n    }\n\n    /**\n     * Gets the active domain from the list of domains where the task is to be queued. The domain\n     * list must be ordered. In sequence, check if any worker has polled for last\n     * `activeWorkerLastPollMs`, if so that is the Active domain. When no active domains are found:\n     * <li>If NO_DOMAIN token is provided, return null.\n     * <li>Else, return last domain from list.\n     *\n     * @param taskType the taskType of the task for which active domain is to be found\n     * @param domains the array of domains for the task. (Must contain atleast one element).\n     * @return the active domain where the task will be queued\n     */\n    @VisibleForTesting\n    String getActiveDomain(String taskType, String[] domains) {\n        if (domains == null || domains.length == 0) {\n            return null;\n        }\n\n        return Arrays.stream(domains)\n                .filter(domain -> !domain.equalsIgnoreCase(\"NO_DOMAIN\"))\n                .map(domain -> executionDAOFacade.getTaskPollDataByDomain(taskType, domain.trim()))\n                .filter(Objects::nonNull)\n                .filter(validateLastPolledTime)\n                .findFirst()\n                .map(PollData::getDomain)\n                .orElse(\n                        domains[domains.length - 1].trim().equalsIgnoreCase(\"NO_DOMAIN\")\n                                ? null\n                                : domains[domains.length - 1].trim());\n    }\n\n    private long getTaskDuration(long s, TaskModel task) {\n        long duration = task.getEndTime() - task.getStartTime();\n        s += duration;\n        if (task.getRetriedTaskId() == null) {\n            return s;\n        }\n        return s + getTaskDuration(s, executionDAOFacade.getTaskModel(task.getRetriedTaskId()));\n    }\n\n    @VisibleForTesting\n    boolean scheduleTask(WorkflowModel workflow, List<TaskModel> tasks) {\n        List<TaskModel> tasksToBeQueued;\n        boolean startedSystemTasks = false;\n\n        try {\n            if (tasks == null || tasks.isEmpty()) {\n                return false;\n            }\n\n            // Get the highest seq number\n            int count = workflow.getTasks().stream().mapToInt(TaskModel::getSeq).max().orElse(0);\n\n            for (TaskModel task : tasks) {\n                if (task.getSeq() == 0) { // Set only if the seq was not set\n                    task.setSeq(++count);\n                }\n            }\n\n            // metric to track the distribution of number of tasks within a workflow\n            Monitors.recordNumTasksInWorkflow(\n                    workflow.getTasks().size() + tasks.size(),\n                    workflow.getWorkflowName(),\n                    String.valueOf(workflow.getWorkflowVersion()));\n\n            // Save the tasks in the DAO\n            executionDAOFacade.createTasks(tasks);\n\n            List<TaskModel> systemTasks =\n                    tasks.stream()\n                            .filter(task -> systemTaskRegistry.isSystemTask(task.getTaskType()))\n                            .collect(Collectors.toList());\n\n            tasksToBeQueued =\n                    tasks.stream()\n                            .filter(task -> !systemTaskRegistry.isSystemTask(task.getTaskType()))\n                            .collect(Collectors.toList());\n\n            // Traverse through all the system tasks, start the sync tasks, in case of async queue\n            // the tasks\n            for (TaskModel task : systemTasks) {\n                WorkflowSystemTask workflowSystemTask = systemTaskRegistry.get(task.getTaskType());\n                if (workflowSystemTask == null) {\n                    throw new NotFoundException(\n                            \"No system task found by name %s\", task.getTaskType());\n                }\n                if (task.getStatus() != null\n                        && !task.getStatus().isTerminal()\n                        && task.getStartTime() == 0) {\n                    task.setStartTime(System.currentTimeMillis());\n                }\n                if (!workflowSystemTask.isAsync()) {\n                    try {\n                        // start execution of synchronous system tasks\n                        workflowSystemTask.start(workflow, task, this);\n                    } catch (Exception e) {\n                        String errorMsg =\n                                String.format(\n                                        \"Unable to start system task: %s, {id: %s, name: %s}\",\n                                        task.getTaskType(),\n                                        task.getTaskId(),\n                                        task.getTaskDefName());\n                        throw new NonTransientException(errorMsg, e);\n                    }\n                    startedSystemTasks = true;\n                    executionDAOFacade.updateTask(task);\n                } else {\n                    tasksToBeQueued.add(task);\n                }\n            }\n\n        } catch (Exception e) {\n            List<String> taskIds =\n                    tasks.stream().map(TaskModel::getTaskId).collect(Collectors.toList());\n            String errorMsg =\n                    String.format(\n                            \"Error scheduling tasks: %s, for workflow: %s\",\n                            taskIds, workflow.getWorkflowId());\n            LOGGER.error(errorMsg, e);\n            Monitors.error(CLASS_NAME, \"scheduleTask\");\n            throw new TerminateWorkflowException(errorMsg);\n        }\n\n        // On addTaskToQueue failures, ignore the exceptions and let WorkflowRepairService take care\n        // of republishing the messages to the queue.\n        try {\n            addTaskToQueue(tasksToBeQueued);\n        } catch (Exception e) {\n            List<String> taskIds =\n                    tasksToBeQueued.stream().map(TaskModel::getTaskId).collect(Collectors.toList());\n            String errorMsg =\n                    String.format(\n                            \"Error pushing tasks to the queue: %s, for workflow: %s\",\n                            taskIds, workflow.getWorkflowId());\n            LOGGER.warn(errorMsg, e);\n            Monitors.error(CLASS_NAME, \"scheduleTask\");\n        }\n        return startedSystemTasks;\n    }\n\n    private void addTaskToQueue(final List<TaskModel> tasks) {\n        for (TaskModel task : tasks) {\n            addTaskToQueue(task);\n            // notify TaskStatusListener\n            try {\n                taskStatusListener.onTaskScheduled(task);\n            } catch (Exception e) {\n                String errorMsg =\n                        String.format(\n                                \"Error while notifying TaskStatusListener: %s for workflow: %s\",\n                                task.getTaskId(), task.getWorkflowInstanceId());\n                LOGGER.error(errorMsg, e);\n            }\n        }\n    }\n\n    private WorkflowModel terminate(\n            final WorkflowModel workflow, TerminateWorkflowException terminateWorkflowException) {\n        if (!workflow.getStatus().isTerminal()) {\n            workflow.setStatus(terminateWorkflowException.getWorkflowStatus());\n        }\n\n        if (terminateWorkflowException.getTask() != null && workflow.getFailedTaskId() == null) {\n            workflow.setFailedTaskId(terminateWorkflowException.getTask().getTaskId());\n        }\n\n        String failureWorkflow = workflow.getWorkflowDefinition().getFailureWorkflow();\n        if (failureWorkflow != null) {\n            if (failureWorkflow.startsWith(\"$\")) {\n                String[] paramPathComponents = failureWorkflow.split(\"\\\\.\");\n                String name = paramPathComponents[2]; // name of the input parameter\n                failureWorkflow = (String) workflow.getInput().get(name);\n            }\n        }\n        if (terminateWorkflowException.getTask() != null) {\n            executionDAOFacade.updateTask(terminateWorkflowException.getTask());\n        }\n        return terminateWorkflow(\n                workflow, terminateWorkflowException.getMessage(), failureWorkflow);\n    }\n\n    private boolean rerunWF(\n            String workflowId,\n            String taskId,\n            Map<String, Object> taskInput,\n            Map<String, Object> workflowInput,\n            String correlationId) {\n\n        // Get the workflow\n        WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n        if (!workflow.getStatus().isTerminal()) {\n            String errorMsg =\n                    String.format(\n                            \"Workflow: %s is not in terminal state, unable to rerun.\", workflow);\n            LOGGER.error(errorMsg);\n            throw new ConflictException(errorMsg);\n        }\n        updateAndPushParents(workflow, \"reran\");\n\n        // If the task Id is null it implies that the entire workflow has to be rerun\n        if (taskId == null) {\n            // remove all tasks\n            workflow.getTasks().forEach(task -> executionDAOFacade.removeTask(task.getTaskId()));\n            workflow.setTasks(new ArrayList<>());\n            // Set workflow as RUNNING\n            workflow.setStatus(WorkflowModel.Status.RUNNING);\n            // Reset failure reason from previous run to default\n            workflow.setReasonForIncompletion(null);\n            workflow.setFailedTaskId(null);\n            workflow.setFailedReferenceTaskNames(new HashSet<>());\n            workflow.setFailedTaskNames(new HashSet<>());\n\n            if (correlationId != null) {\n                workflow.setCorrelationId(correlationId);\n            }\n            if (workflowInput != null) {\n                workflow.setInput(workflowInput);\n            }\n\n            queueDAO.push(\n                    DECIDER_QUEUE,\n                    workflow.getWorkflowId(),\n                    workflow.getPriority(),\n                    properties.getWorkflowOffsetTimeout().getSeconds());\n            executionDAOFacade.updateWorkflow(workflow);\n\n            decide(workflowId);\n            return true;\n        }\n\n        // Now iterate through the tasks and find the \"specific\" task\n        TaskModel rerunFromTask = null;\n        for (TaskModel task : workflow.getTasks()) {\n            if (task.getTaskId().equals(taskId)) {\n                rerunFromTask = task;\n                break;\n            }\n        }\n\n        // If not found look into sub workflows\n        if (rerunFromTask == null) {\n            for (TaskModel task : workflow.getTasks()) {\n                if (task.getTaskType().equalsIgnoreCase(TaskType.TASK_TYPE_SUB_WORKFLOW)) {\n                    String subWorkflowId = task.getSubWorkflowId();\n                    if (rerunWF(subWorkflowId, taskId, taskInput, null, null)) {\n                        rerunFromTask = task;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if (rerunFromTask != null) {\n            // set workflow as RUNNING\n            workflow.setStatus(WorkflowModel.Status.RUNNING);\n            // Reset failure reason from previous run to default\n            workflow.setReasonForIncompletion(null);\n            workflow.setFailedTaskId(null);\n            workflow.setFailedReferenceTaskNames(new HashSet<>());\n            workflow.setFailedTaskNames(new HashSet<>());\n\n            if (correlationId != null) {\n                workflow.setCorrelationId(correlationId);\n            }\n            if (workflowInput != null) {\n                workflow.setInput(workflowInput);\n            }\n            // Add to decider queue\n            queueDAO.push(\n                    DECIDER_QUEUE,\n                    workflow.getWorkflowId(),\n                    workflow.getPriority(),\n                    properties.getWorkflowOffsetTimeout().getSeconds());\n            executionDAOFacade.updateWorkflow(workflow);\n            // update tasks in datastore to update workflow-tasks relationship for archived\n            // workflows\n            executionDAOFacade.updateTasks(workflow.getTasks());\n            // Remove all tasks after the \"rerunFromTask\"\n            List<TaskModel> filteredTasks = new ArrayList<>();\n            for (TaskModel task : workflow.getTasks()) {\n                if (task.getSeq() > rerunFromTask.getSeq()) {\n                    executionDAOFacade.removeTask(task.getTaskId());\n                } else {\n                    filteredTasks.add(task);\n                }\n            }\n            workflow.setTasks(filteredTasks);\n            // reset fields before restarting the task\n            rerunFromTask.setScheduledTime(System.currentTimeMillis());\n            rerunFromTask.setStartTime(0);\n            rerunFromTask.setUpdateTime(0);\n            rerunFromTask.setEndTime(0);\n            rerunFromTask.clearOutput();\n            rerunFromTask.setRetried(false);\n            rerunFromTask.setExecuted(false);\n            if (rerunFromTask.getTaskType().equalsIgnoreCase(TaskType.TASK_TYPE_SUB_WORKFLOW)) {\n                // if task is sub workflow set task as IN_PROGRESS and reset start time\n                rerunFromTask.setStatus(IN_PROGRESS);\n                rerunFromTask.setStartTime(System.currentTimeMillis());\n            } else {\n                if (taskInput != null) {\n                    rerunFromTask.setInputData(taskInput);\n                }\n                if (systemTaskRegistry.isSystemTask(rerunFromTask.getTaskType())\n                        && !systemTaskRegistry.get(rerunFromTask.getTaskType()).isAsync()) {\n                    // Start the synchronous system task directly\n                    systemTaskRegistry\n                            .get(rerunFromTask.getTaskType())\n                            .start(workflow, rerunFromTask, this);\n                } else {\n                    // Set the task to rerun as SCHEDULED\n                    rerunFromTask.setStatus(SCHEDULED);\n                    addTaskToQueue(rerunFromTask);\n                }\n            }\n            executionDAOFacade.updateTask(rerunFromTask);\n            decide(workflow.getWorkflowId());\n            return true;\n        }\n        return false;\n    }\n\n    public void scheduleNextIteration(TaskModel loopTask, WorkflowModel workflow) {\n        // Schedule only first loop over task. Rest will be taken care in Decider Service when this\n        // task will get completed.\n        List<TaskModel> scheduledLoopOverTasks =\n                deciderService.getTasksToBeScheduled(\n                        workflow,\n                        loopTask.getWorkflowTask().getLoopOver().get(0),\n                        loopTask.getRetryCount(),\n                        null);\n        setTaskDomains(scheduledLoopOverTasks, workflow);\n        scheduledLoopOverTasks.forEach(\n                t -> {\n                    t.setReferenceTaskName(\n                            TaskUtils.appendIteration(\n                                    t.getReferenceTaskName(), loopTask.getIteration()));\n                    t.setIteration(loopTask.getIteration());\n                });\n        scheduleTask(workflow, scheduledLoopOverTasks);\n        workflow.getTasks().addAll(scheduledLoopOverTasks);\n    }\n\n    public TaskDef getTaskDefinition(TaskModel task) {\n        return task.getTaskDefinition()\n                .orElseGet(\n                        () ->\n                                Optional.ofNullable(\n                                                metadataDAO.getTaskDef(\n                                                        task.getWorkflowTask().getName()))\n                                        .orElseThrow(\n                                                () -> {\n                                                    String reason =\n                                                            String.format(\n                                                                    \"Invalid task specified. Cannot find task by name %s in the task definitions\",\n                                                                    task.getWorkflowTask()\n                                                                            .getName());\n                                                    return new TerminateWorkflowException(reason);\n                                                }));\n    }\n\n    @VisibleForTesting\n    void updateParentWorkflowTask(WorkflowModel subWorkflow) {\n        TaskModel subWorkflowTask =\n                executionDAOFacade.getTaskModel(subWorkflow.getParentWorkflowTaskId());\n        executeSubworkflowTaskAndSyncData(subWorkflow, subWorkflowTask);\n        executionDAOFacade.updateTask(subWorkflowTask);\n    }\n\n    private void executeSubworkflowTaskAndSyncData(\n            WorkflowModel subWorkflow, TaskModel subWorkflowTask) {\n        WorkflowSystemTask subWorkflowSystemTask =\n                systemTaskRegistry.get(TaskType.TASK_TYPE_SUB_WORKFLOW);\n        subWorkflowSystemTask.execute(subWorkflow, subWorkflowTask, this);\n    }\n\n    /**\n     * Pushes workflow id into the decider queue with a higher priority to expedite evaluation.\n     *\n     * @param workflowId The workflow to be evaluated at higher priority\n     */\n    private void expediteLazyWorkflowEvaluation(String workflowId) {\n        if (queueDAO.containsMessage(DECIDER_QUEUE, workflowId)) {\n            queueDAO.postpone(DECIDER_QUEUE, workflowId, EXPEDITED_PRIORITY, 0);\n        } else {\n            queueDAO.push(DECIDER_QUEUE, workflowId, EXPEDITED_PRIORITY, 0);\n        }\n\n        LOGGER.info(\"Pushed workflow {} to {} for expedited evaluation\", workflowId, DECIDER_QUEUE);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/evaluators/Evaluator.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.evaluators;\n\npublic interface Evaluator {\n    /**\n     * Evaluate the expression using the inputs provided, if required. Evaluation of the expression\n     * depends on the type of the evaluator.\n     *\n     * @param expression Expression to be evaluated.\n     * @param input Input object to the evaluator to help evaluate the expression.\n     * @return Return the evaluation result.\n     */\n    Object evaluate(String expression, Object input);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/evaluators/JavascriptEvaluator.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.evaluators;\n\nimport javax.script.ScriptException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.events.ScriptEvaluator;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\n\n@Component(JavascriptEvaluator.NAME)\npublic class JavascriptEvaluator implements Evaluator {\n\n    public static final String NAME = \"javascript\";\n    private static final Logger LOGGER = LoggerFactory.getLogger(JavascriptEvaluator.class);\n\n    @Override\n    public Object evaluate(String expression, Object input) {\n        LOGGER.debug(\"Javascript evaluator -- expression: {}\", expression);\n        try {\n            // Evaluate the expression by using the Javascript evaluation engine.\n            Object result = ScriptEvaluator.eval(expression, input);\n            LOGGER.debug(\"Javascript evaluator -- result: {}\", result);\n            return result;\n        } catch (ScriptException e) {\n            LOGGER.error(\"Error while evaluating script: {}\", expression, e);\n            throw new TerminateWorkflowException(e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/evaluators/ValueParamEvaluator.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.evaluators;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\n\n@Component(ValueParamEvaluator.NAME)\npublic class ValueParamEvaluator implements Evaluator {\n\n    public static final String NAME = \"value-param\";\n    private static final Logger LOGGER = LoggerFactory.getLogger(ValueParamEvaluator.class);\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public Object evaluate(String expression, Object input) {\n        LOGGER.debug(\"ValueParam evaluator -- evaluating: {}\", expression);\n        if (input instanceof Map) {\n            Object result = ((Map<String, Object>) input).get(expression);\n            LOGGER.debug(\"ValueParam evaluator -- result: {}\", result);\n            return result;\n        } else {\n            String errorMsg = String.format(\"Input has to be a JSON object: %s\", input.getClass());\n            LOGGER.error(errorMsg);\n            throw new TerminateWorkflowException(errorMsg);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/DecisionTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.script.ScriptException;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.events.ScriptEvaluator;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#DECISION} to a List {@link TaskModel} starting with Task of type {@link\n * TaskType#DECISION} which is marked as IN_PROGRESS, followed by the list of {@link TaskModel}\n * based on the case expression evaluation in the Decision task.\n *\n * @deprecated {@link com.netflix.conductor.core.execution.tasks.Decision} is also deprecated. Use\n *     {@link com.netflix.conductor.core.execution.tasks.Switch} and so ${@link SwitchTaskMapper}\n *     will be used as a result.\n */\n@Deprecated\n@Component\npublic class DecisionTaskMapper implements TaskMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DecisionTaskMapper.class);\n\n    @Override\n    public String getTaskType() {\n        return TaskType.DECISION.name();\n    }\n\n    /**\n     * This method gets the list of tasks that need to scheduled when the task to scheduled is of\n     * type {@link TaskType#DECISION}.\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return List of tasks in the following order:\n     *     <ul>\n     *       <li>{@link TaskType#DECISION} with {@link TaskModel.Status#IN_PROGRESS}\n     *       <li>List of task based on the evaluation of {@link WorkflowTask#getCaseExpression()}\n     *           are scheduled.\n     *       <li>In case of no matching result after the evaluation of the {@link\n     *           WorkflowTask#getCaseExpression()}, the {@link WorkflowTask#getDefaultCase()} Tasks\n     *           are scheduled.\n     *     </ul>\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n        LOGGER.debug(\"TaskMapperContext {} in DecisionTaskMapper\", taskMapperContext);\n        List<TaskModel> tasksToBeScheduled = new LinkedList<>();\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        Map<String, Object> taskInput = taskMapperContext.getTaskInput();\n        int retryCount = taskMapperContext.getRetryCount();\n\n        // get the expression to be evaluated\n        String caseValue = getEvaluatedCaseValue(workflowTask, taskInput);\n\n        // QQ why is the case value and the caseValue passed and caseOutput passes as the same ??\n        TaskModel decisionTask = taskMapperContext.createTaskModel();\n        decisionTask.setTaskType(TaskType.TASK_TYPE_DECISION);\n        decisionTask.setTaskDefName(TaskType.TASK_TYPE_DECISION);\n        decisionTask.addInput(\"case\", caseValue);\n        decisionTask.addOutput(\"caseOutput\", Collections.singletonList(caseValue));\n        decisionTask.setStartTime(System.currentTimeMillis());\n        decisionTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        tasksToBeScheduled.add(decisionTask);\n\n        // get the list of tasks based on the decision\n        List<WorkflowTask> selectedTasks = workflowTask.getDecisionCases().get(caseValue);\n        // if the tasks returned are empty based on evaluated case value, then get the default case\n        // if there is one\n        if (selectedTasks == null || selectedTasks.isEmpty()) {\n            selectedTasks = workflowTask.getDefaultCase();\n        }\n        // once there are selected tasks that need to proceeded as part of the decision, get the\n        // next task to be scheduled by using the decider service\n        if (selectedTasks != null && !selectedTasks.isEmpty()) {\n            WorkflowTask selectedTask =\n                    selectedTasks.get(0); // Schedule the first task to be executed...\n            // TODO break out this recursive call using function composition of what needs to be\n            // done and then walk back the condition tree\n            List<TaskModel> caseTasks =\n                    taskMapperContext\n                            .getDeciderService()\n                            .getTasksToBeScheduled(\n                                    workflowModel,\n                                    selectedTask,\n                                    retryCount,\n                                    taskMapperContext.getRetryTaskId());\n            tasksToBeScheduled.addAll(caseTasks);\n            decisionTask.addInput(\"hasChildren\", \"true\");\n        }\n        return tasksToBeScheduled;\n    }\n\n    /**\n     * This method evaluates the case expression of a decision task and returns a string\n     * representation of the evaluated result.\n     *\n     * @param workflowTask: The decision task that has the case expression to be evaluated.\n     * @param taskInput: the input which has the values that will be used in evaluating the case\n     *     expression.\n     * @return A String representation of the evaluated result\n     */\n    @VisibleForTesting\n    String getEvaluatedCaseValue(WorkflowTask workflowTask, Map<String, Object> taskInput) {\n        String expression = workflowTask.getCaseExpression();\n        String caseValue;\n        if (StringUtils.isNotBlank(expression)) {\n            LOGGER.debug(\"Case being evaluated using decision expression: {}\", expression);\n            try {\n                // Evaluate the expression by using the Nashhorn based script evaluator\n                Object returnValue = ScriptEvaluator.eval(expression, taskInput);\n                caseValue = (returnValue == null) ? \"null\" : returnValue.toString();\n            } catch (ScriptException e) {\n                String errorMsg = String.format(\"Error while evaluating script: %s\", expression);\n                LOGGER.error(errorMsg, e);\n                throw new TerminateWorkflowException(errorMsg);\n            }\n\n        } else { // In case of no case expression, get the caseValueParam and treat it as a string\n            // representation of caseValue\n            LOGGER.debug(\n                    \"No Expression available on the decision task, case value being assigned as param name\");\n            String paramName = workflowTask.getCaseValueParam();\n            caseValue = \"\" + taskInput.get(paramName);\n        }\n        return caseValue;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/DoWhileTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#DO_WHILE} to a {@link TaskModel} of type {@link TaskType#DO_WHILE}\n */\n@Component\npublic class DoWhileTaskMapper implements TaskMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DoWhileTaskMapper.class);\n\n    private final MetadataDAO metadataDAO;\n    private final ParametersUtils parametersUtils;\n\n    @Autowired\n    public DoWhileTaskMapper(MetadataDAO metadataDAO, ParametersUtils parametersUtils) {\n        this.metadataDAO = metadataDAO;\n        this.parametersUtils = parametersUtils;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.DO_WHILE.name();\n    }\n\n    /**\n     * This method maps {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n     * TaskType#DO_WHILE} to a {@link TaskModel} of type {@link TaskType#DO_WHILE} with a status of\n     * {@link TaskModel.Status#IN_PROGRESS}\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return: A {@link TaskModel} of type {@link TaskType#DO_WHILE} in a List\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n        LOGGER.debug(\"TaskMapperContext {} in DoWhileTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n\n        TaskModel task = workflowModel.getTaskByRefName(workflowTask.getTaskReferenceName());\n        if (task != null && task.getStatus().isTerminal()) {\n            // Since loopTask is already completed no need to schedule task again.\n            return List.of();\n        }\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(taskMapperContext.getTaskDefinition())\n                        .orElseGet(\n                                () ->\n                                        Optional.ofNullable(\n                                                        metadataDAO.getTaskDef(\n                                                                workflowTask.getName()))\n                                                .orElseGet(TaskDef::new));\n\n        TaskModel doWhileTask = taskMapperContext.createTaskModel();\n        doWhileTask.setTaskType(TaskType.TASK_TYPE_DO_WHILE);\n        doWhileTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        doWhileTask.setStartTime(System.currentTimeMillis());\n        doWhileTask.setRateLimitPerFrequency(taskDefinition.getRateLimitPerFrequency());\n        doWhileTask.setRateLimitFrequencyInSeconds(taskDefinition.getRateLimitFrequencyInSeconds());\n        doWhileTask.setRetryCount(taskMapperContext.getRetryCount());\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInputV2(\n                        workflowTask.getInputParameters(),\n                        workflowModel,\n                        doWhileTask.getTaskId(),\n                        taskDefinition);\n        doWhileTask.setInputData(taskInput);\n        return List.of(doWhileTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/DynamicTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#DYNAMIC} to a {@link TaskModel} based on definition derived from the dynamic task name\n * defined in {@link WorkflowTask#getInputParameters()}\n */\n@Component\npublic class DynamicTaskMapper implements TaskMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    @Autowired\n    public DynamicTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.DYNAMIC.name();\n    }\n\n    /**\n     * This method maps a dynamic task to a {@link TaskModel} based on the input params\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return A {@link List} that contains a single {@link TaskModel} with a {@link\n     *     TaskModel.Status#SCHEDULED}\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n        LOGGER.debug(\"TaskMapperContext {} in DynamicTaskMapper\", taskMapperContext);\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        Map<String, Object> taskInput = taskMapperContext.getTaskInput();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        int retryCount = taskMapperContext.getRetryCount();\n        String retriedTaskId = taskMapperContext.getRetryTaskId();\n\n        String taskNameParam = workflowTask.getDynamicTaskNameParam();\n        String taskName = getDynamicTaskName(taskInput, taskNameParam);\n        workflowTask.setName(taskName);\n        TaskDef taskDefinition = getDynamicTaskDefinition(workflowTask);\n        workflowTask.setTaskDefinition(taskDefinition);\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        workflowTask.getInputParameters(),\n                        workflowModel,\n                        taskDefinition,\n                        taskMapperContext.getTaskId());\n\n        // IMPORTANT: The WorkflowTask that is inside TaskMapperContext is changed above\n        // createTaskModel() must be called here so the changes are reflected in the created\n        // TaskModel\n        TaskModel dynamicTask = taskMapperContext.createTaskModel();\n        dynamicTask.setStartDelayInSeconds(workflowTask.getStartDelay());\n        dynamicTask.setInputData(input);\n        dynamicTask.setStatus(TaskModel.Status.SCHEDULED);\n        dynamicTask.setRetryCount(retryCount);\n        dynamicTask.setCallbackAfterSeconds(workflowTask.getStartDelay());\n        dynamicTask.setResponseTimeoutSeconds(taskDefinition.getResponseTimeoutSeconds());\n        dynamicTask.setTaskType(taskName);\n        dynamicTask.setRetriedTaskId(retriedTaskId);\n        dynamicTask.setWorkflowPriority(workflowModel.getPriority());\n        return Collections.singletonList(dynamicTask);\n    }\n\n    /**\n     * Helper method that looks into the input params and returns the dynamic task name\n     *\n     * @param taskInput: a map which contains different input parameters and also contains the\n     *     mapping between the dynamic task name param and the actual name representing the dynamic\n     *     task\n     * @param taskNameParam: the key that is used to look up the dynamic task name.\n     * @return The name of the dynamic task\n     * @throws TerminateWorkflowException : In case is there is no value dynamic task name in the\n     *     input parameters.\n     */\n    @VisibleForTesting\n    String getDynamicTaskName(Map<String, Object> taskInput, String taskNameParam)\n            throws TerminateWorkflowException {\n        return Optional.ofNullable(taskInput.get(taskNameParam))\n                .map(String::valueOf)\n                .orElseThrow(\n                        () -> {\n                            String reason =\n                                    String.format(\n                                            \"Cannot map a dynamic task based on the parameter and input. \"\n                                                    + \"Parameter= %s, input= %s\",\n                                            taskNameParam, taskInput);\n                            return new TerminateWorkflowException(reason);\n                        });\n    }\n\n    /**\n     * This method gets the TaskDefinition for a specific {@link WorkflowTask}\n     *\n     * @param workflowTask: An instance of {@link WorkflowTask} which has the name of the using\n     *     which the {@link TaskDef} can be retrieved.\n     * @return An instance of TaskDefinition\n     * @throws TerminateWorkflowException : in case of no workflow definition available\n     */\n    @VisibleForTesting\n    TaskDef getDynamicTaskDefinition(WorkflowTask workflowTask)\n            throws TerminateWorkflowException { // TODO this is a common pattern in code base can\n        // be moved to DAO\n        return Optional.ofNullable(workflowTask.getTaskDefinition())\n                .orElseGet(\n                        () ->\n                                Optional.ofNullable(metadataDAO.getTaskDef(workflowTask.getName()))\n                                        .orElseThrow(\n                                                () -> {\n                                                    String reason =\n                                                            String.format(\n                                                                    \"Invalid task specified.  Cannot find task by name %s in the task definitions\",\n                                                                    workflowTask.getName());\n                                                    return new TerminateWorkflowException(reason);\n                                                }));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/EventTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_EVENT;\n\n@Component\npublic class EventTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(EventTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n\n    @Autowired\n    public EventTaskMapper(ParametersUtils parametersUtils) {\n        this.parametersUtils = parametersUtils;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.EVENT.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        LOGGER.debug(\"TaskMapperContext {} in EventTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n\n        workflowTask.getInputParameters().put(\"sink\", workflowTask.getSink());\n        workflowTask.getInputParameters().put(\"asyncComplete\", workflowTask.isAsyncComplete());\n        Map<String, Object> eventTaskInput =\n                parametersUtils.getTaskInputV2(\n                        workflowTask.getInputParameters(), workflowModel, taskId, null);\n        String sink = (String) eventTaskInput.get(\"sink\");\n        Boolean asynComplete = (Boolean) eventTaskInput.get(\"asyncComplete\");\n\n        TaskModel eventTask = taskMapperContext.createTaskModel();\n        eventTask.setTaskType(TASK_TYPE_EVENT);\n        eventTask.setStatus(TaskModel.Status.SCHEDULED);\n\n        eventTask.setInputData(eventTaskInput);\n        eventTask.getInputData().put(\"sink\", sink);\n        eventTask.getInputData().put(\"asyncComplete\", asynComplete);\n\n        return List.of(eventTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/ExclusiveJoinTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.model.TaskModel;\n\n@Component\npublic class ExclusiveJoinTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(ExclusiveJoinTaskMapper.class);\n\n    @Override\n    public String getTaskType() {\n        return TaskType.EXCLUSIVE_JOIN.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        LOGGER.debug(\"TaskMapperContext {} in ExclusiveJoinTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n\n        Map<String, Object> joinInput = new HashMap<>();\n        joinInput.put(\"joinOn\", workflowTask.getJoinOn());\n\n        if (workflowTask.getDefaultExclusiveJoinTask() != null) {\n            joinInput.put(\"defaultExclusiveJoinTask\", workflowTask.getDefaultExclusiveJoinTask());\n        }\n\n        TaskModel joinTask = taskMapperContext.createTaskModel();\n        joinTask.setTaskType(TaskType.TASK_TYPE_EXCLUSIVE_JOIN);\n        joinTask.setTaskDefName(TaskType.TASK_TYPE_EXCLUSIVE_JOIN);\n        joinTask.setStartTime(System.currentTimeMillis());\n        joinTask.setInputData(joinInput);\n        joinTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        return List.of(joinTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinDynamicTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.apache.commons.lang3.tuple.Pair;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.DynamicForkJoinTaskList;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#FORK_JOIN_DYNAMIC} to a LinkedList of {@link TaskModel} beginning with a {@link\n * TaskType#TASK_TYPE_FORK}, followed by the user defined dynamic tasks and a {@link TaskType#JOIN}\n * at the end\n */\n@Component\npublic class ForkJoinDynamicTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(ForkJoinDynamicTaskMapper.class);\n\n    private final IDGenerator idGenerator;\n    private final ParametersUtils parametersUtils;\n    private final ObjectMapper objectMapper;\n    private final MetadataDAO metadataDAO;\n    private static final TypeReference<List<WorkflowTask>> ListOfWorkflowTasks =\n            new TypeReference<>() {};\n\n    @Autowired\n    public ForkJoinDynamicTaskMapper(\n            IDGenerator idGenerator,\n            ParametersUtils parametersUtils,\n            ObjectMapper objectMapper,\n            MetadataDAO metadataDAO) {\n        this.idGenerator = idGenerator;\n        this.parametersUtils = parametersUtils;\n        this.objectMapper = objectMapper;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.FORK_JOIN_DYNAMIC.name();\n    }\n\n    /**\n     * This method gets the list of tasks that need to scheduled when the task to scheduled is of\n     * type {@link TaskType#FORK_JOIN_DYNAMIC}. Creates a Fork Task, followed by the Dynamic tasks\n     * and a final JOIN task.\n     *\n     * <p>The definitions of the dynamic forks that need to be scheduled are available in the {@link\n     * WorkflowTask#getInputParameters()} which are accessed using the {@link\n     * TaskMapperContext#getWorkflowTask()}. The dynamic fork task definitions are referred by a key\n     * value either by {@link WorkflowTask#getDynamicForkTasksParam()} or by {@link\n     * WorkflowTask#getDynamicForkJoinTasksParam()} When creating the list of tasks to be scheduled\n     * a set of preconditions are validated:\n     *\n     * <ul>\n     *   <li>If the input parameter representing the Dynamic fork tasks is available as part of\n     *       {@link WorkflowTask#getDynamicForkTasksParam()} then the input for the dynamic task is\n     *       validated to be a map by using {@link WorkflowTask#getDynamicForkTasksInputParamName()}\n     *   <li>If the input parameter representing the Dynamic fork tasks is available as part of\n     *       {@link WorkflowTask#getDynamicForkJoinTasksParam()} then the input for the dynamic\n     *       tasks is available in the payload of the tasks definition.\n     *   <li>A check is performed that the next following task in the {@link WorkflowDef} is a\n     *       {@link TaskType#JOIN}\n     * </ul>\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return List of tasks in the following order:\n     *     <ul>\n     *       <li>{@link TaskType#TASK_TYPE_FORK} with {@link TaskModel.Status#COMPLETED}\n     *       <li>Might be any kind of task, but this is most cases is a UserDefinedTask with {@link\n     *           TaskModel.Status#SCHEDULED}\n     *       <li>{@link TaskType#JOIN} with {@link TaskModel.Status#IN_PROGRESS}\n     *     </ul>\n     *\n     * @throws TerminateWorkflowException In case of:\n     *     <ul>\n     *       <li>When the task after {@link TaskType#FORK_JOIN_DYNAMIC} is not a {@link\n     *           TaskType#JOIN}\n     *       <li>When the input parameters for the dynamic tasks are not of type {@link Map}\n     *     </ul>\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n        LOGGER.debug(\"TaskMapperContext {} in ForkJoinDynamicTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        int retryCount = taskMapperContext.getRetryCount();\n\n        List<TaskModel> mappedTasks = new LinkedList<>();\n        // Get the list of dynamic tasks and the input for the tasks\n        Pair<List<WorkflowTask>, Map<String, Map<String, Object>>> workflowTasksAndInputPair =\n                Optional.ofNullable(workflowTask.getDynamicForkTasksParam())\n                        .map(\n                                dynamicForkTaskParam ->\n                                        getDynamicForkTasksAndInput(\n                                                workflowTask, workflowModel, dynamicForkTaskParam))\n                        .orElseGet(\n                                () -> getDynamicForkJoinTasksAndInput(workflowTask, workflowModel));\n\n        List<WorkflowTask> dynForkTasks = workflowTasksAndInputPair.getLeft();\n        Map<String, Map<String, Object>> tasksInput = workflowTasksAndInputPair.getRight();\n\n        // Create Fork Task which needs to be followed by the dynamic tasks\n        TaskModel forkDynamicTask = createDynamicForkTask(taskMapperContext, dynForkTasks);\n\n        mappedTasks.add(forkDynamicTask);\n\n        List<String> joinOnTaskRefs = new LinkedList<>();\n        // Add each dynamic task to the mapped tasks and also get the last dynamic task in the list,\n        // which indicates that the following task after that needs to be a join task\n        for (WorkflowTask dynForkTask :\n                dynForkTasks) { // TODO this is a cyclic dependency, break it out using function\n            // composition\n            List<TaskModel> forkedTasks =\n                    taskMapperContext\n                            .getDeciderService()\n                            .getTasksToBeScheduled(workflowModel, dynForkTask, retryCount);\n\n            // It's an error state if no forkedTasks can be decided upon. In the cases where we've\n            // seen\n            // this happen is when a dynamic task is attempting to be created here, but a task with\n            // the\n            // same reference name has already been created in the Workflow.\n            if (forkedTasks == null || forkedTasks.isEmpty()) {\n                Optional<String> existingTaskRefName =\n                        workflowModel.getTasks().stream()\n                                .filter(\n                                        runningTask ->\n                                                runningTask\n                                                                .getStatus()\n                                                                .equals(\n                                                                        TaskModel.Status\n                                                                                .IN_PROGRESS)\n                                                        || runningTask.getStatus().isTerminal())\n                                .map(TaskModel::getReferenceTaskName)\n                                .filter(\n                                        refTaskName ->\n                                                refTaskName.equals(\n                                                        dynForkTask.getTaskReferenceName()))\n                                .findAny();\n\n                // Construct an informative error message\n                String terminateMessage =\n                        \"No dynamic tasks could be created for the Workflow: \"\n                                + workflowModel.toShortString()\n                                + \", Dynamic Fork Task: \"\n                                + dynForkTask;\n                if (existingTaskRefName.isPresent()) {\n                    terminateMessage +=\n                            \"Attempted to create a duplicate task reference name: \"\n                                    + existingTaskRefName.get();\n                }\n                throw new TerminateWorkflowException(terminateMessage);\n            }\n\n            for (TaskModel forkedTask : forkedTasks) {\n                try {\n                    Map<String, Object> forkedTaskInput =\n                            tasksInput.get(forkedTask.getReferenceTaskName());\n                    forkedTask.addInput(forkedTaskInput);\n                } catch (Exception e) {\n                    String reason =\n                            String.format(\n                                    \"Tasks could not be dynamically forked due to invalid input: %s\",\n                                    e.getMessage());\n                    throw new TerminateWorkflowException(reason);\n                }\n            }\n            mappedTasks.addAll(forkedTasks);\n            // Get the last of the dynamic tasks so that the join can be performed once this task is\n            // done\n            TaskModel last = forkedTasks.get(forkedTasks.size() - 1);\n            joinOnTaskRefs.add(last.getReferenceTaskName());\n        }\n\n        // From the workflow definition get the next task and make sure that it is a JOIN task.\n        // The dynamic fork tasks need to be followed by a join task\n        WorkflowTask joinWorkflowTask =\n                workflowModel\n                        .getWorkflowDefinition()\n                        .getNextTask(workflowTask.getTaskReferenceName());\n\n        if (joinWorkflowTask == null || !joinWorkflowTask.getType().equals(TaskType.JOIN.name())) {\n            throw new TerminateWorkflowException(\n                    \"Dynamic join definition is not followed by a join task.  Check the workflow definition.\");\n        }\n\n        // Create Join task\n        HashMap<String, Object> joinInput = new HashMap<>();\n        joinInput.put(\"joinOn\", joinOnTaskRefs);\n        TaskModel joinTask = createJoinTask(workflowModel, joinWorkflowTask, joinInput);\n        mappedTasks.add(joinTask);\n\n        return mappedTasks;\n    }\n\n    /**\n     * This method creates a FORK task and adds the list of dynamic fork tasks keyed by\n     * \"forkedTaskDefs\" and their names keyed by \"forkedTasks\" into {@link TaskModel#getInputData()}\n     *\n     * @param taskMapperContext: The {@link TaskMapperContext} which wraps workflowTask, workflowDef\n     *     and workflowModel\n     * @param dynForkTasks: The list of dynamic forked tasks, the reference names of these tasks\n     *     will be added to the forkDynamicTask\n     * @return A new instance of {@link TaskModel} representing a {@link TaskType#TASK_TYPE_FORK}\n     */\n    @VisibleForTesting\n    TaskModel createDynamicForkTask(\n            TaskMapperContext taskMapperContext, List<WorkflowTask> dynForkTasks) {\n        TaskModel forkDynamicTask = taskMapperContext.createTaskModel();\n        forkDynamicTask.setTaskType(TaskType.TASK_TYPE_FORK);\n        forkDynamicTask.setTaskDefName(TaskType.TASK_TYPE_FORK);\n        forkDynamicTask.setStartTime(System.currentTimeMillis());\n        forkDynamicTask.setEndTime(System.currentTimeMillis());\n        List<String> forkedTaskNames =\n                dynForkTasks.stream()\n                        .map(WorkflowTask::getTaskReferenceName)\n                        .collect(Collectors.toList());\n        forkDynamicTask.getInputData().put(\"forkedTasks\", forkedTaskNames);\n        forkDynamicTask\n                .getInputData()\n                .put(\n                        \"forkedTaskDefs\",\n                        dynForkTasks); // TODO: Remove this parameter in the later releases\n        forkDynamicTask.setStatus(TaskModel.Status.COMPLETED);\n        return forkDynamicTask;\n    }\n\n    /**\n     * This method creates a JOIN task that is used in the {@link\n     * this#getMappedTasks(TaskMapperContext)} at the end to add a join task to be scheduled after\n     * all the fork tasks\n     *\n     * @param workflowModel: A instance of the {@link WorkflowModel} which represents the workflow\n     *     being executed.\n     * @param joinWorkflowTask: A instance of {@link WorkflowTask} which is of type {@link\n     *     TaskType#JOIN}\n     * @param joinInput: The input which is set in the {@link TaskModel#setInputData(Map)}\n     * @return a new instance of {@link TaskModel} representing a {@link TaskType#JOIN}\n     */\n    @VisibleForTesting\n    TaskModel createJoinTask(\n            WorkflowModel workflowModel,\n            WorkflowTask joinWorkflowTask,\n            HashMap<String, Object> joinInput) {\n        TaskModel joinTask = new TaskModel();\n        joinTask.setTaskType(TaskType.TASK_TYPE_JOIN);\n        joinTask.setTaskDefName(TaskType.TASK_TYPE_JOIN);\n        joinTask.setReferenceTaskName(joinWorkflowTask.getTaskReferenceName());\n        joinTask.setWorkflowInstanceId(workflowModel.getWorkflowId());\n        joinTask.setWorkflowType(workflowModel.getWorkflowName());\n        joinTask.setCorrelationId(workflowModel.getCorrelationId());\n        joinTask.setScheduledTime(System.currentTimeMillis());\n        joinTask.setStartTime(System.currentTimeMillis());\n        joinTask.setInputData(joinInput);\n        joinTask.setTaskId(idGenerator.generate());\n        joinTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        joinTask.setWorkflowTask(joinWorkflowTask);\n        joinTask.setWorkflowPriority(workflowModel.getPriority());\n        return joinTask;\n    }\n\n    /**\n     * This method is used to get the List of dynamic workflow tasks and their input based on the\n     * {@link WorkflowTask#getDynamicForkTasksParam()}\n     *\n     * @param workflowTask: The Task of type FORK_JOIN_DYNAMIC that needs to scheduled, which has\n     *     the input parameters\n     * @param workflowModel: The instance of the {@link WorkflowModel} which represents the workflow\n     *     being executed.\n     * @param dynamicForkTaskParam: The key representing the dynamic fork join json payload which is\n     *     available in {@link WorkflowTask#getInputParameters()}\n     * @return a {@link Pair} representing the list of dynamic fork tasks in {@link Pair#getLeft()}\n     *     and the input for the dynamic fork tasks in {@link Pair#getRight()}\n     * @throws TerminateWorkflowException : In case of input parameters of the dynamic fork tasks\n     *     not represented as {@link Map}\n     */\n    @SuppressWarnings(\"unchecked\")\n    @VisibleForTesting\n    Pair<List<WorkflowTask>, Map<String, Map<String, Object>>> getDynamicForkTasksAndInput(\n            WorkflowTask workflowTask, WorkflowModel workflowModel, String dynamicForkTaskParam)\n            throws TerminateWorkflowException {\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        workflowTask.getInputParameters(), workflowModel, null, null);\n        Object dynamicForkTasksJson = input.get(dynamicForkTaskParam);\n        List<WorkflowTask> dynamicForkWorkflowTasks =\n                objectMapper.convertValue(dynamicForkTasksJson, ListOfWorkflowTasks);\n        if (dynamicForkWorkflowTasks == null) {\n            dynamicForkWorkflowTasks = new ArrayList<>();\n        }\n        for (WorkflowTask dynamicForkWorkflowTask : dynamicForkWorkflowTasks) {\n            if ((dynamicForkWorkflowTask.getTaskDefinition() == null)\n                    && StringUtils.isNotBlank(dynamicForkWorkflowTask.getName())) {\n                dynamicForkWorkflowTask.setTaskDefinition(\n                        metadataDAO.getTaskDef(dynamicForkWorkflowTask.getName()));\n            }\n        }\n        Object dynamicForkTasksInput = input.get(workflowTask.getDynamicForkTasksInputParamName());\n        if (!(dynamicForkTasksInput instanceof Map)) {\n            throw new TerminateWorkflowException(\n                    \"Input to the dynamically forked tasks is not a map -> expecting a map of K,V  but found \"\n                            + dynamicForkTasksInput);\n        }\n        return new ImmutablePair<>(\n                dynamicForkWorkflowTasks, (Map<String, Map<String, Object>>) dynamicForkTasksInput);\n    }\n\n    /**\n     * This method is used to get the List of dynamic workflow tasks and their input based on the\n     * {@link WorkflowTask#getDynamicForkJoinTasksParam()}\n     *\n     * <p><b>NOTE:</b> This method is kept for legacy reasons, new workflows should use the {@link\n     * #getDynamicForkTasksAndInput}\n     *\n     * @param workflowTask: The Task of type FORK_JOIN_DYNAMIC that needs to scheduled, which has\n     *     the input parameters\n     * @param workflowModel: The instance of the {@link WorkflowModel} which represents the workflow\n     *     being executed.\n     * @return {@link Pair} representing the list of dynamic fork tasks in {@link Pair#getLeft()}\n     *     and the input for the dynamic fork tasks in {@link Pair#getRight()}\n     * @throws TerminateWorkflowException : In case of the {@link WorkflowTask#getInputParameters()}\n     *     does not have a payload that contains the list of the dynamic tasks\n     */\n    @VisibleForTesting\n    Pair<List<WorkflowTask>, Map<String, Map<String, Object>>> getDynamicForkJoinTasksAndInput(\n            WorkflowTask workflowTask, WorkflowModel workflowModel)\n            throws TerminateWorkflowException {\n        String dynamicForkJoinTaskParam = workflowTask.getDynamicForkJoinTasksParam();\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        workflowTask.getInputParameters(), workflowModel, null, null);\n        Object paramValue = input.get(dynamicForkJoinTaskParam);\n        DynamicForkJoinTaskList dynamicForkJoinTaskList =\n                objectMapper.convertValue(paramValue, DynamicForkJoinTaskList.class);\n\n        if (dynamicForkJoinTaskList == null) {\n            String reason =\n                    String.format(\n                            \"Dynamic tasks could not be created. The value of %s from task's input %s has no dynamic tasks to be scheduled\",\n                            dynamicForkJoinTaskParam, input);\n            LOGGER.error(reason);\n            throw new TerminateWorkflowException(reason);\n        }\n\n        Map<String, Map<String, Object>> dynamicForkJoinTasksInput = new HashMap<>();\n\n        List<WorkflowTask> dynamicForkJoinWorkflowTasks =\n                dynamicForkJoinTaskList.getDynamicTasks().stream()\n                        .peek(\n                                dynamicForkJoinTask ->\n                                        dynamicForkJoinTasksInput.put(\n                                                dynamicForkJoinTask.getReferenceName(),\n                                                dynamicForkJoinTask\n                                                        .getInput())) // TODO create a custom pair\n                        // collector\n                        .map(\n                                dynamicForkJoinTask -> {\n                                    WorkflowTask dynamicForkJoinWorkflowTask = new WorkflowTask();\n                                    dynamicForkJoinWorkflowTask.setTaskReferenceName(\n                                            dynamicForkJoinTask.getReferenceName());\n                                    dynamicForkJoinWorkflowTask.setName(\n                                            dynamicForkJoinTask.getTaskName());\n                                    dynamicForkJoinWorkflowTask.setType(\n                                            dynamicForkJoinTask.getType());\n                                    if (dynamicForkJoinWorkflowTask.getTaskDefinition() == null\n                                            && StringUtils.isNotBlank(\n                                                    dynamicForkJoinWorkflowTask.getName())) {\n                                        dynamicForkJoinWorkflowTask.setTaskDefinition(\n                                                metadataDAO.getTaskDef(\n                                                        dynamicForkJoinTask.getTaskName()));\n                                    }\n                                    return dynamicForkJoinWorkflowTask;\n                                })\n                        .collect(Collectors.toCollection(LinkedList::new));\n\n        return new ImmutablePair<>(dynamicForkJoinWorkflowTasks, dynamicForkJoinTasksInput);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#FORK_JOIN} to a LinkedList of {@link TaskModel} beginning with a completed {@link\n * TaskType#TASK_TYPE_FORK}, followed by the user defined fork tasks\n */\n@Component\npublic class ForkJoinTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(ForkJoinTaskMapper.class);\n\n    @Override\n    public String getTaskType() {\n        return TaskType.FORK_JOIN.name();\n    }\n\n    /**\n     * This method gets the list of tasks that need to scheduled when the task to scheduled is of\n     * type {@link TaskType#FORK_JOIN}.\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return List of tasks in the following order: *\n     *     <ul>\n     *       <li>{@link TaskType#TASK_TYPE_FORK} with {@link TaskModel.Status#COMPLETED}\n     *       <li>Might be any kind of task, but in most cases is a UserDefinedTask with {@link\n     *           TaskModel.Status#SCHEDULED}\n     *     </ul>\n     *\n     * @throws TerminateWorkflowException When the task after {@link TaskType#FORK_JOIN} is not a\n     *     {@link TaskType#JOIN}\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n\n        LOGGER.debug(\"TaskMapperContext {} in ForkJoinTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        Map<String, Object> taskInput = taskMapperContext.getTaskInput();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        int retryCount = taskMapperContext.getRetryCount();\n\n        List<TaskModel> tasksToBeScheduled = new LinkedList<>();\n        TaskModel forkTask = taskMapperContext.createTaskModel();\n        forkTask.setTaskType(TaskType.TASK_TYPE_FORK);\n        forkTask.setTaskDefName(TaskType.TASK_TYPE_FORK);\n        long epochMillis = System.currentTimeMillis();\n        forkTask.setStartTime(epochMillis);\n        forkTask.setEndTime(epochMillis);\n        forkTask.setInputData(taskInput);\n        forkTask.setStatus(TaskModel.Status.COMPLETED);\n\n        tasksToBeScheduled.add(forkTask);\n        List<List<WorkflowTask>> forkTasks = workflowTask.getForkTasks();\n        for (List<WorkflowTask> wfts : forkTasks) {\n            WorkflowTask wft = wfts.get(0);\n            List<TaskModel> tasks2 =\n                    taskMapperContext\n                            .getDeciderService()\n                            .getTasksToBeScheduled(workflowModel, wft, retryCount);\n            tasksToBeScheduled.addAll(tasks2);\n        }\n\n        WorkflowTask joinWorkflowTask =\n                workflowModel\n                        .getWorkflowDefinition()\n                        .getNextTask(workflowTask.getTaskReferenceName());\n\n        if (joinWorkflowTask == null || !joinWorkflowTask.getType().equals(TaskType.JOIN.name())) {\n            throw new TerminateWorkflowException(\n                    \"Fork task definition is not followed by a join task.  Check the blueprint\");\n        }\n        List<TaskModel> joinTask =\n                taskMapperContext\n                        .getDeciderService()\n                        .getTasksToBeScheduled(workflowModel, joinWorkflowTask, retryCount);\n\n        tasksToBeScheduled.addAll(joinTask);\n        return tasksToBeScheduled;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/HTTPTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.*;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#HTTP} to a {@link TaskModel} of type {@link TaskType#HTTP} with {@link\n * TaskModel.Status#SCHEDULED}\n */\n@Component\npublic class HTTPTaskMapper implements TaskMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(HTTPTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    @Autowired\n    public HTTPTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.HTTP.name();\n    }\n\n    /**\n     * This method maps a {@link WorkflowTask} of type {@link TaskType#HTTP} to a {@link TaskModel}\n     * in a {@link TaskModel.Status#SCHEDULED} state\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return a List with just one HTTP task\n     * @throws TerminateWorkflowException In case if the task definition does not exist\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n\n        LOGGER.debug(\"TaskMapperContext {} in HTTPTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        workflowTask.getInputParameters().put(\"asyncComplete\", workflowTask.isAsyncComplete());\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n        int retryCount = taskMapperContext.getRetryCount();\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(taskMapperContext.getTaskDefinition())\n                        .orElseGet(() -> metadataDAO.getTaskDef(workflowTask.getName()));\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInputV2(\n                        workflowTask.getInputParameters(), workflowModel, taskId, taskDefinition);\n        Boolean asynComplete = (Boolean) input.get(\"asyncComplete\");\n\n        TaskModel httpTask = taskMapperContext.createTaskModel();\n        httpTask.setInputData(input);\n        httpTask.getInputData().put(\"asyncComplete\", asynComplete);\n        httpTask.setStatus(TaskModel.Status.SCHEDULED);\n        httpTask.setRetryCount(retryCount);\n        httpTask.setCallbackAfterSeconds(workflowTask.getStartDelay());\n        if (Objects.nonNull(taskDefinition)) {\n            httpTask.setRateLimitPerFrequency(taskDefinition.getRateLimitPerFrequency());\n            httpTask.setRateLimitFrequencyInSeconds(\n                    taskDefinition.getRateLimitFrequencyInSeconds());\n            httpTask.setIsolationGroupId(taskDefinition.getIsolationGroupId());\n            httpTask.setExecutionNameSpace(taskDefinition.getExecutionNameSpace());\n        }\n        return List.of(httpTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/HumanTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.tasks.Human;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_HUMAN;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#HUMAN} to a {@link TaskModel} of type {@link Human} with {@link\n * TaskModel.Status#IN_PROGRESS}\n */\n@Component\npublic class HumanTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(HumanTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n\n    public HumanTaskMapper(ParametersUtils parametersUtils) {\n        this.parametersUtils = parametersUtils;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.HUMAN.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n\n        Map<String, Object> humanTaskInput =\n                parametersUtils.getTaskInputV2(\n                        taskMapperContext.getWorkflowTask().getInputParameters(),\n                        workflowModel,\n                        taskId,\n                        null);\n\n        TaskModel humanTask = taskMapperContext.createTaskModel();\n        humanTask.setTaskType(TASK_TYPE_HUMAN);\n        humanTask.setInputData(humanTaskInput);\n        humanTask.setStartTime(System.currentTimeMillis());\n        humanTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        return List.of(humanTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/InlineTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#INLINE} to a List {@link TaskModel} starting with Task of type {@link TaskType#INLINE}\n * which is marked as IN_PROGRESS, followed by the list of {@link TaskModel} based on the case\n * expression evaluation in the Inline task.\n */\n@Component\npublic class InlineTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(InlineTaskMapper.class);\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    public InlineTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.INLINE.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        LOGGER.debug(\"TaskMapperContext {} in InlineTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(taskMapperContext.getTaskDefinition())\n                        .orElseGet(() -> metadataDAO.getTaskDef(workflowTask.getName()));\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInputV2(\n                        taskMapperContext.getWorkflowTask().getInputParameters(),\n                        workflowModel,\n                        taskId,\n                        taskDefinition);\n\n        TaskModel inlineTask = taskMapperContext.createTaskModel();\n        inlineTask.setTaskType(TaskType.TASK_TYPE_INLINE);\n        inlineTask.setStartTime(System.currentTimeMillis());\n        inlineTask.setInputData(taskInput);\n        inlineTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        return List.of(inlineTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/JoinTaskMapper.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#JOIN} to a {@link TaskModel} of type {@link TaskType#JOIN}\n */\n@Component\npublic class JoinTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(JoinTaskMapper.class);\n\n    @Override\n    public String getTaskType() {\n        return TaskType.JOIN.name();\n    }\n\n    /**\n     * This method maps {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n     * TaskType#JOIN} to a {@link TaskModel} of type {@link TaskType#JOIN} with a status of {@link\n     * TaskModel.Status#IN_PROGRESS}\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return A {@link TaskModel} of type {@link TaskType#JOIN} in a List\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        LOGGER.debug(\"TaskMapperContext {} in JoinTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n\n        Map<String, Object> joinInput = new HashMap<>();\n        joinInput.put(\"joinOn\", workflowTask.getJoinOn());\n\n        TaskModel joinTask = taskMapperContext.createTaskModel();\n        joinTask.setTaskType(TaskType.TASK_TYPE_JOIN);\n        joinTask.setTaskDefName(TaskType.TASK_TYPE_JOIN);\n        joinTask.setStartTime(System.currentTimeMillis());\n        joinTask.setInputData(joinInput);\n        joinTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        return List.of(joinTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/JsonJQTransformTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n@Component\npublic class JsonJQTransformTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(JsonJQTransformTaskMapper.class);\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    public JsonJQTransformTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.JSON_JQ_TRANSFORM.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        LOGGER.debug(\"TaskMapperContext {} in JsonJQTransformTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(taskMapperContext.getTaskDefinition())\n                        .orElseGet(() -> metadataDAO.getTaskDef(workflowTask.getName()));\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInputV2(\n                        workflowTask.getInputParameters(), workflowModel, taskId, taskDefinition);\n\n        TaskModel jsonJQTransformTask = taskMapperContext.createTaskModel();\n        jsonJQTransformTask.setStartTime(System.currentTimeMillis());\n        jsonJQTransformTask.setInputData(taskInput);\n        jsonJQTransformTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        return List.of(jsonJQTransformTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/KafkaPublishTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n@Component\npublic class KafkaPublishTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(KafkaPublishTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    @Autowired\n    public KafkaPublishTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.KAFKA_PUBLISH.name();\n    }\n\n    /**\n     * This method maps a {@link WorkflowTask} of type {@link TaskType#KAFKA_PUBLISH} to a {@link\n     * TaskModel} in a {@link TaskModel.Status#SCHEDULED} state\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return a List with just one Kafka task\n     * @throws TerminateWorkflowException In case if the task definition does not exist\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n\n        LOGGER.debug(\"TaskMapperContext {} in KafkaPublishTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n        int retryCount = taskMapperContext.getRetryCount();\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(taskMapperContext.getTaskDefinition())\n                        .orElseGet(() -> metadataDAO.getTaskDef(workflowTask.getName()));\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInputV2(\n                        workflowTask.getInputParameters(), workflowModel, taskId, taskDefinition);\n\n        TaskModel kafkaPublishTask = taskMapperContext.createTaskModel();\n        kafkaPublishTask.setInputData(input);\n        kafkaPublishTask.setStatus(TaskModel.Status.SCHEDULED);\n        kafkaPublishTask.setRetryCount(retryCount);\n        kafkaPublishTask.setCallbackAfterSeconds(workflowTask.getStartDelay());\n        if (Objects.nonNull(taskDefinition)) {\n            kafkaPublishTask.setExecutionNameSpace(taskDefinition.getExecutionNameSpace());\n            kafkaPublishTask.setIsolationGroupId(taskDefinition.getIsolationGroupId());\n            kafkaPublishTask.setRateLimitPerFrequency(taskDefinition.getRateLimitPerFrequency());\n            kafkaPublishTask.setRateLimitFrequencyInSeconds(\n                    taskDefinition.getRateLimitFrequencyInSeconds());\n        }\n        return Collections.singletonList(kafkaPublishTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/LambdaTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * @author x-ultra\n * @deprecated {@link com.netflix.conductor.core.execution.tasks.Lambda} is also deprecated. Use\n *     {@link com.netflix.conductor.core.execution.tasks.Inline} and so ${@link InlineTaskMapper}\n *     will be used as a result.\n */\n@Deprecated\n@Component\npublic class LambdaTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(LambdaTaskMapper.class);\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    public LambdaTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.LAMBDA.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        LOGGER.debug(\"TaskMapperContext {} in LambdaTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(taskMapperContext.getTaskDefinition())\n                        .orElseGet(() -> metadataDAO.getTaskDef(workflowTask.getName()));\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInputV2(\n                        taskMapperContext.getWorkflowTask().getInputParameters(),\n                        workflowModel,\n                        taskId,\n                        taskDefinition);\n\n        TaskModel lambdaTask = taskMapperContext.createTaskModel();\n        lambdaTask.setTaskType(TaskType.TASK_TYPE_LAMBDA);\n        lambdaTask.setStartTime(System.currentTimeMillis());\n        lambdaTask.setInputData(taskInput);\n        lambdaTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        return List.of(lambdaTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/NoopTaskMapper.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.model.TaskModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.*;\n\n@Component\npublic class NoopTaskMapper implements TaskMapper {\n\n    public static final Logger logger = LoggerFactory.getLogger(NoopTaskMapper.class);\n\n    @Override\n    public String getTaskType() {\n        return TaskType.NOOP.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n        logger.debug(\"TaskMapperContext {} in NoopTaskMapper\", taskMapperContext);\n\n        TaskModel task = taskMapperContext.createTaskModel();\n        task.setTaskType(TASK_TYPE_NOOP);\n        task.setStartTime(System.currentTimeMillis());\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        return List.of(task);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/SetVariableTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.model.TaskModel;\n\n@Component\npublic class SetVariableTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(SetVariableTaskMapper.class);\n\n    @Override\n    public String getTaskType() {\n        return TaskType.SET_VARIABLE.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n        LOGGER.debug(\"TaskMapperContext {} in SetVariableMapper\", taskMapperContext);\n\n        TaskModel varTask = taskMapperContext.createTaskModel();\n        varTask.setStartTime(System.currentTimeMillis());\n        varTask.setInputData(taskMapperContext.getTaskInput());\n        varTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        return List.of(varTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/SimpleTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#SIMPLE} to a {@link TaskModel} with status {@link TaskModel.Status#SCHEDULED}.\n * <b>NOTE:</b> There is not type defined for simples task.\n */\n@Component\npublic class SimpleTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(SimpleTaskMapper.class);\n    private final ParametersUtils parametersUtils;\n\n    public SimpleTaskMapper(ParametersUtils parametersUtils) {\n        this.parametersUtils = parametersUtils;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.SIMPLE.name();\n    }\n\n    /**\n     * This method maps a {@link WorkflowTask} of type {@link TaskType#SIMPLE} to a {@link\n     * TaskModel}\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @throws TerminateWorkflowException In case if the task definition does not exist\n     * @return a List with just one simple task\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n\n        LOGGER.debug(\"TaskMapperContext {} in SimpleTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        int retryCount = taskMapperContext.getRetryCount();\n        String retriedTaskId = taskMapperContext.getRetryTaskId();\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(workflowTask.getTaskDefinition())\n                        .orElseThrow(\n                                () -> {\n                                    String reason =\n                                            String.format(\n                                                    \"Invalid task. Task %s does not have a definition\",\n                                                    workflowTask.getName());\n                                    return new TerminateWorkflowException(reason);\n                                });\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        workflowTask.getInputParameters(),\n                        workflowModel,\n                        taskDefinition,\n                        taskMapperContext.getTaskId());\n        TaskModel simpleTask = taskMapperContext.createTaskModel();\n        simpleTask.setTaskType(workflowTask.getName());\n        simpleTask.setStartDelayInSeconds(workflowTask.getStartDelay());\n        simpleTask.setInputData(input);\n        simpleTask.setStatus(TaskModel.Status.SCHEDULED);\n        simpleTask.setRetryCount(retryCount);\n        simpleTask.setCallbackAfterSeconds(workflowTask.getStartDelay());\n        simpleTask.setResponseTimeoutSeconds(taskDefinition.getResponseTimeoutSeconds());\n        simpleTask.setRetriedTaskId(retriedTaskId);\n        simpleTask.setRateLimitPerFrequency(taskDefinition.getRateLimitPerFrequency());\n        simpleTask.setRateLimitFrequencyInSeconds(taskDefinition.getRateLimitFrequencyInSeconds());\n        return List.of(simpleTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/StartWorkflowTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.model.TaskModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.START_WORKFLOW;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_START_WORKFLOW;\n\n@Component\npublic class StartWorkflowTaskMapper implements TaskMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(StartWorkflowTaskMapper.class);\n\n    @Override\n    public String getTaskType() {\n        return START_WORKFLOW.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n\n        TaskModel startWorkflowTask = taskMapperContext.createTaskModel();\n        startWorkflowTask.setTaskType(TASK_TYPE_START_WORKFLOW);\n        startWorkflowTask.addInput(taskMapperContext.getTaskInput());\n        startWorkflowTask.setStatus(TaskModel.Status.SCHEDULED);\n        startWorkflowTask.setCallbackAfterSeconds(workflowTask.getStartDelay());\n        LOGGER.debug(\"{} created\", startWorkflowTask);\n        return List.of(startWorkflowTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/SubWorkflowTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.*;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW;\n\n@Component\npublic class SubWorkflowTaskMapper implements TaskMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SubWorkflowTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    public SubWorkflowTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.SUB_WORKFLOW.name();\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n        LOGGER.debug(\"TaskMapperContext {} in SubWorkflowTaskMapper\", taskMapperContext);\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n        // Check if there are sub workflow parameters, if not throw an exception, cannot initiate a\n        // sub-workflow without workflow params\n        SubWorkflowParams subWorkflowParams = getSubWorkflowParams(workflowTask);\n\n        Map<String, Object> resolvedParams =\n                getSubWorkflowInputParameters(workflowModel, subWorkflowParams);\n\n        String subWorkflowName = resolvedParams.get(\"name\").toString();\n        Integer subWorkflowVersion = getSubWorkflowVersion(resolvedParams, subWorkflowName);\n\n        Object subWorkflowDefinition = resolvedParams.get(\"workflowDefinition\");\n\n        Map subWorkflowTaskToDomain = null;\n        Object uncheckedTaskToDomain = resolvedParams.get(\"taskToDomain\");\n        if (uncheckedTaskToDomain instanceof Map) {\n            subWorkflowTaskToDomain = (Map) uncheckedTaskToDomain;\n        }\n\n        TaskModel subWorkflowTask = taskMapperContext.createTaskModel();\n        subWorkflowTask.setTaskType(TASK_TYPE_SUB_WORKFLOW);\n        subWorkflowTask.addInput(\"subWorkflowName\", subWorkflowName);\n        subWorkflowTask.addInput(\"subWorkflowVersion\", subWorkflowVersion);\n        subWorkflowTask.addInput(\"subWorkflowTaskToDomain\", subWorkflowTaskToDomain);\n        subWorkflowTask.addInput(\"subWorkflowDefinition\", subWorkflowDefinition);\n        subWorkflowTask.addInput(\"workflowInput\", taskMapperContext.getTaskInput());\n        subWorkflowTask.setStatus(TaskModel.Status.SCHEDULED);\n        subWorkflowTask.setCallbackAfterSeconds(workflowTask.getStartDelay());\n        LOGGER.debug(\"SubWorkflowTask {} created to be Scheduled\", subWorkflowTask);\n        return List.of(subWorkflowTask);\n    }\n\n    @VisibleForTesting\n    SubWorkflowParams getSubWorkflowParams(WorkflowTask workflowTask) {\n        return Optional.ofNullable(workflowTask.getSubWorkflowParam())\n                .orElseThrow(\n                        () -> {\n                            String reason =\n                                    String.format(\n                                            \"Task %s is defined as sub-workflow and is missing subWorkflowParams. \"\n                                                    + \"Please check the workflow definition\",\n                                            workflowTask.getName());\n                            LOGGER.error(reason);\n                            return new TerminateWorkflowException(reason);\n                        });\n    }\n\n    private Map<String, Object> getSubWorkflowInputParameters(\n            WorkflowModel workflowModel, SubWorkflowParams subWorkflowParams) {\n        Map<String, Object> params = new HashMap<>();\n        params.put(\"name\", subWorkflowParams.getName());\n\n        Integer version = subWorkflowParams.getVersion();\n        if (version != null) {\n            params.put(\"version\", version);\n        }\n        Map<String, String> taskToDomain = subWorkflowParams.getTaskToDomain();\n        if (taskToDomain != null) {\n            params.put(\"taskToDomain\", taskToDomain);\n        }\n\n        params = parametersUtils.getTaskInputV2(params, workflowModel, null, null);\n\n        // do not resolve params inside subworkflow definition\n        Object subWorkflowDefinition = subWorkflowParams.getWorkflowDefinition();\n        if (subWorkflowDefinition != null) {\n            params.put(\"workflowDefinition\", subWorkflowDefinition);\n        }\n\n        return params;\n    }\n\n    private Integer getSubWorkflowVersion(\n            Map<String, Object> resolvedParams, String subWorkflowName) {\n        return Optional.ofNullable(resolvedParams.get(\"version\"))\n                .map(Object::toString)\n                .map(Integer::parseInt)\n                .orElseGet(\n                        () ->\n                                metadataDAO\n                                        .getLatestWorkflowDef(subWorkflowName)\n                                        .map(WorkflowDef::getVersion)\n                                        .orElseThrow(\n                                                () -> {\n                                                    String reason =\n                                                            String.format(\n                                                                    \"The Task %s defined as a sub-workflow has no workflow definition available \",\n                                                                    subWorkflowName);\n                                                    LOGGER.error(reason);\n                                                    return new TerminateWorkflowException(reason);\n                                                }));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/SwitchTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#SWITCH} to a List {@link TaskModel} starting with Task of type {@link TaskType#SWITCH}\n * which is marked as IN_PROGRESS, followed by the list of {@link TaskModel} based on the case\n * expression evaluation in the Switch task.\n */\n@Component\npublic class SwitchTaskMapper implements TaskMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SwitchTaskMapper.class);\n\n    private final Map<String, Evaluator> evaluators;\n\n    @Autowired\n    public SwitchTaskMapper(Map<String, Evaluator> evaluators) {\n        this.evaluators = evaluators;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.SWITCH.name();\n    }\n\n    /**\n     * This method gets the list of tasks that need to scheduled when the task to scheduled is of\n     * type {@link TaskType#SWITCH}.\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return List of tasks in the following order:\n     *     <ul>\n     *       <li>{@link TaskType#SWITCH} with {@link TaskModel.Status#IN_PROGRESS}\n     *       <li>List of tasks based on the evaluation of {@link WorkflowTask#getEvaluatorType()}\n     *           and {@link WorkflowTask#getExpression()} are scheduled.\n     *       <li>In the case of no matching {@link WorkflowTask#getEvaluatorType()}, workflow will\n     *           be terminated with error message. In case of no matching result after the\n     *           evaluation of the {@link WorkflowTask#getExpression()}, the {@link\n     *           WorkflowTask#getDefaultCase()} Tasks are scheduled.\n     *     </ul>\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n        LOGGER.debug(\"TaskMapperContext {} in SwitchTaskMapper\", taskMapperContext);\n        List<TaskModel> tasksToBeScheduled = new LinkedList<>();\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        Map<String, Object> taskInput = taskMapperContext.getTaskInput();\n        int retryCount = taskMapperContext.getRetryCount();\n\n        // get the expression to be evaluated\n        String evaluatorType = workflowTask.getEvaluatorType();\n        Evaluator evaluator = evaluators.get(evaluatorType);\n        if (evaluator == null) {\n            String errorMsg = String.format(\"No evaluator registered for type: %s\", evaluatorType);\n            LOGGER.error(errorMsg);\n            throw new TerminateWorkflowException(errorMsg);\n        }\n\n        String evalResult = \"\";\n        try {\n            evalResult = \"\" + evaluator.evaluate(workflowTask.getExpression(), taskInput);\n        } catch (Exception exception) {\n            TaskModel switchTask = taskMapperContext.createTaskModel();\n            switchTask.setTaskType(TaskType.TASK_TYPE_SWITCH);\n            switchTask.setTaskDefName(TaskType.TASK_TYPE_SWITCH);\n            switchTask.getInputData().putAll(taskInput);\n            switchTask.setStartTime(System.currentTimeMillis());\n            switchTask.setStatus(TaskModel.Status.FAILED);\n            switchTask.setReasonForIncompletion(exception.getMessage());\n            tasksToBeScheduled.add(switchTask);\n\n            return tasksToBeScheduled;\n        }\n\n        // QQ why is the case value and the caseValue passed and caseOutput passes as the same ??\n        TaskModel switchTask = taskMapperContext.createTaskModel();\n        switchTask.setTaskType(TaskType.TASK_TYPE_SWITCH);\n        switchTask.setTaskDefName(TaskType.TASK_TYPE_SWITCH);\n        switchTask.getInputData().putAll(taskInput);\n        switchTask.getInputData().put(\"case\", evalResult);\n        switchTask.addOutput(\"evaluationResult\", List.of(evalResult));\n        switchTask.addOutput(\"selectedCase\", evalResult);\n        switchTask.setStartTime(System.currentTimeMillis());\n        switchTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        tasksToBeScheduled.add(switchTask);\n\n        // get the list of tasks based on the evaluated expression\n        List<WorkflowTask> selectedTasks = workflowTask.getDecisionCases().get(evalResult);\n        // if the tasks returned are empty based on evaluated result, then get the default case if\n        // there is one\n        if (selectedTasks == null || selectedTasks.isEmpty()) {\n            selectedTasks = workflowTask.getDefaultCase();\n        }\n        // once there are selected tasks that need to proceeded as part of the switch, get the next\n        // task to be scheduled by using the decider service\n        if (selectedTasks != null && !selectedTasks.isEmpty()) {\n            WorkflowTask selectedTask =\n                    selectedTasks.get(0); // Schedule the first task to be executed...\n            // TODO break out this recursive call using function composition of what needs to be\n            // done and then walk back the condition tree\n            List<TaskModel> caseTasks =\n                    taskMapperContext\n                            .getDeciderService()\n                            .getTasksToBeScheduled(\n                                    workflowModel,\n                                    selectedTask,\n                                    retryCount,\n                                    taskMapperContext.getRetryTaskId());\n            tasksToBeScheduled.addAll(caseTasks);\n            switchTask.getInputData().put(\"hasChildren\", \"true\");\n        }\n        return tasksToBeScheduled;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/TaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.model.TaskModel;\n\npublic interface TaskMapper {\n\n    String getTaskType();\n\n    List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException;\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/TaskMapperContext.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Map;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/** Business Object class used for interaction between the DeciderService and Different Mappers */\npublic class TaskMapperContext {\n\n    private final WorkflowModel workflowModel;\n    private final TaskDef taskDefinition;\n    private final WorkflowTask workflowTask;\n    private final Map<String, Object> taskInput;\n    private final int retryCount;\n    private final String retryTaskId;\n    private final String taskId;\n    private final DeciderService deciderService;\n\n    private TaskMapperContext(Builder builder) {\n        workflowModel = builder.workflowModel;\n        taskDefinition = builder.taskDefinition;\n        workflowTask = builder.workflowTask;\n        taskInput = builder.taskInput;\n        retryCount = builder.retryCount;\n        retryTaskId = builder.retryTaskId;\n        taskId = builder.taskId;\n        deciderService = builder.deciderService;\n    }\n\n    public static Builder newBuilder() {\n        return new Builder();\n    }\n\n    public static Builder newBuilder(TaskMapperContext copy) {\n        Builder builder = new Builder();\n        builder.workflowModel = copy.getWorkflowModel();\n        builder.taskDefinition = copy.getTaskDefinition();\n        builder.workflowTask = copy.getWorkflowTask();\n        builder.taskInput = copy.getTaskInput();\n        builder.retryCount = copy.getRetryCount();\n        builder.retryTaskId = copy.getRetryTaskId();\n        builder.taskId = copy.getTaskId();\n        builder.deciderService = copy.getDeciderService();\n        return builder;\n    }\n\n    public WorkflowDef getWorkflowDefinition() {\n        return workflowModel.getWorkflowDefinition();\n    }\n\n    public WorkflowModel getWorkflowModel() {\n        return workflowModel;\n    }\n\n    public TaskDef getTaskDefinition() {\n        return taskDefinition;\n    }\n\n    public WorkflowTask getWorkflowTask() {\n        return workflowTask;\n    }\n\n    public int getRetryCount() {\n        return retryCount;\n    }\n\n    public String getRetryTaskId() {\n        return retryTaskId;\n    }\n\n    public String getTaskId() {\n        return taskId;\n    }\n\n    public Map<String, Object> getTaskInput() {\n        return taskInput;\n    }\n\n    public DeciderService getDeciderService() {\n        return deciderService;\n    }\n\n    public TaskModel createTaskModel() {\n        TaskModel taskModel = new TaskModel();\n        taskModel.setReferenceTaskName(workflowTask.getTaskReferenceName());\n        taskModel.setWorkflowInstanceId(workflowModel.getWorkflowId());\n        taskModel.setWorkflowType(workflowModel.getWorkflowName());\n        taskModel.setCorrelationId(workflowModel.getCorrelationId());\n        taskModel.setScheduledTime(System.currentTimeMillis());\n\n        taskModel.setTaskId(taskId);\n        taskModel.setWorkflowTask(workflowTask);\n        taskModel.setWorkflowPriority(workflowModel.getPriority());\n\n        // the following properties are overridden by some TaskMapper implementations\n        taskModel.setTaskType(workflowTask.getType());\n        taskModel.setTaskDefName(workflowTask.getName());\n        return taskModel;\n    }\n\n    @Override\n    public String toString() {\n        return \"TaskMapperContext{\"\n                + \"workflowDefinition=\"\n                + getWorkflowDefinition()\n                + \", workflowModel=\"\n                + workflowModel\n                + \", workflowTask=\"\n                + workflowTask\n                + \", taskInput=\"\n                + taskInput\n                + \", retryCount=\"\n                + retryCount\n                + \", retryTaskId='\"\n                + retryTaskId\n                + '\\''\n                + \", taskId='\"\n                + taskId\n                + '\\''\n                + '}';\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof TaskMapperContext)) {\n            return false;\n        }\n\n        TaskMapperContext that = (TaskMapperContext) o;\n\n        if (getRetryCount() != that.getRetryCount()) {\n            return false;\n        }\n        if (!getWorkflowDefinition().equals(that.getWorkflowDefinition())) {\n            return false;\n        }\n        if (!getWorkflowModel().equals(that.getWorkflowModel())) {\n            return false;\n        }\n        if (!getWorkflowTask().equals(that.getWorkflowTask())) {\n            return false;\n        }\n        if (!getTaskInput().equals(that.getTaskInput())) {\n            return false;\n        }\n        if (getRetryTaskId() != null\n                ? !getRetryTaskId().equals(that.getRetryTaskId())\n                : that.getRetryTaskId() != null) {\n            return false;\n        }\n        return getTaskId().equals(that.getTaskId());\n    }\n\n    @Override\n    public int hashCode() {\n        int result = getWorkflowDefinition().hashCode();\n        result = 31 * result + getWorkflowModel().hashCode();\n        result = 31 * result + getWorkflowTask().hashCode();\n        result = 31 * result + getTaskInput().hashCode();\n        result = 31 * result + getRetryCount();\n        result = 31 * result + (getRetryTaskId() != null ? getRetryTaskId().hashCode() : 0);\n        result = 31 * result + getTaskId().hashCode();\n        return result;\n    }\n\n    /** {@code TaskMapperContext} builder static inner class. */\n    public static final class Builder {\n\n        private WorkflowModel workflowModel;\n        private TaskDef taskDefinition;\n        private WorkflowTask workflowTask;\n        private Map<String, Object> taskInput;\n        private int retryCount;\n        private String retryTaskId;\n        private String taskId;\n        private DeciderService deciderService;\n\n        private Builder() {}\n\n        /**\n         * Sets the {@code workflowModel} and returns a reference to this Builder so that the\n         * methods can be chained together.\n         *\n         * @param val the {@code workflowModel} to set\n         * @return a reference to this Builder\n         */\n        public Builder withWorkflowModel(WorkflowModel val) {\n            workflowModel = val;\n            return this;\n        }\n\n        /**\n         * Sets the {@code taskDefinition} and returns a reference to this Builder so that the\n         * methods can be chained together.\n         *\n         * @param val the {@code taskDefinition} to set\n         * @return a reference to this Builder\n         */\n        public Builder withTaskDefinition(TaskDef val) {\n            taskDefinition = val;\n            return this;\n        }\n\n        /**\n         * Sets the {@code workflowTask} and returns a reference to this Builder so that the methods\n         * can be chained together.\n         *\n         * @param val the {@code workflowTask} to set\n         * @return a reference to this Builder\n         */\n        public Builder withWorkflowTask(WorkflowTask val) {\n            workflowTask = val;\n            return this;\n        }\n\n        /**\n         * Sets the {@code taskInput} and returns a reference to this Builder so that the methods\n         * can be chained together.\n         *\n         * @param val the {@code taskInput} to set\n         * @return a reference to this Builder\n         */\n        public Builder withTaskInput(Map<String, Object> val) {\n            taskInput = val;\n            return this;\n        }\n\n        /**\n         * Sets the {@code retryCount} and returns a reference to this Builder so that the methods\n         * can be chained together.\n         *\n         * @param val the {@code retryCount} to set\n         * @return a reference to this Builder\n         */\n        public Builder withRetryCount(int val) {\n            retryCount = val;\n            return this;\n        }\n\n        /**\n         * Sets the {@code retryTaskId} and returns a reference to this Builder so that the methods\n         * can be chained together.\n         *\n         * @param val the {@code retryTaskId} to set\n         * @return a reference to this Builder\n         */\n        public Builder withRetryTaskId(String val) {\n            retryTaskId = val;\n            return this;\n        }\n\n        /**\n         * Sets the {@code taskId} and returns a reference to this Builder so that the methods can\n         * be chained together.\n         *\n         * @param val the {@code taskId} to set\n         * @return a reference to this Builder\n         */\n        public Builder withTaskId(String val) {\n            taskId = val;\n            return this;\n        }\n\n        /**\n         * Sets the {@code deciderService} and returns a reference to this Builder so that the\n         * methods can be chained together.\n         *\n         * @param val the {@code deciderService} to set\n         * @return a reference to this Builder\n         */\n        public Builder withDeciderService(DeciderService val) {\n            deciderService = val;\n            return this;\n        }\n\n        /**\n         * Returns a {@code TaskMapperContext} built from the parameters previously set.\n         *\n         * @return a {@code TaskMapperContext} built with parameters of this {@code\n         *     TaskMapperContext.Builder}\n         */\n        public TaskMapperContext build() {\n            return new TaskMapperContext(this);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/TerminateTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_TERMINATE;\n\n@Component\npublic class TerminateTaskMapper implements TaskMapper {\n\n    public static final Logger logger = LoggerFactory.getLogger(TerminateTaskMapper.class);\n    private final ParametersUtils parametersUtils;\n\n    public TerminateTaskMapper(ParametersUtils parametersUtils) {\n        this.parametersUtils = parametersUtils;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.TERMINATE.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        logger.debug(\"TaskMapperContext {} in TerminateTaskMapper\", taskMapperContext);\n\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInputV2(\n                        taskMapperContext.getWorkflowTask().getInputParameters(),\n                        workflowModel,\n                        taskId,\n                        null);\n\n        TaskModel task = taskMapperContext.createTaskModel();\n        task.setTaskType(TASK_TYPE_TERMINATE);\n        task.setStartTime(System.currentTimeMillis());\n        task.setInputData(taskInput);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        return List.of(task);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/UserDefinedTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#USER_DEFINED} to a {@link TaskModel} of type {@link TaskType#USER_DEFINED} with {@link\n * TaskModel.Status#SCHEDULED}\n */\n@Component\npublic class UserDefinedTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(UserDefinedTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n    private final MetadataDAO metadataDAO;\n\n    public UserDefinedTaskMapper(ParametersUtils parametersUtils, MetadataDAO metadataDAO) {\n        this.parametersUtils = parametersUtils;\n        this.metadataDAO = metadataDAO;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.USER_DEFINED.name();\n    }\n\n    /**\n     * This method maps a {@link WorkflowTask} of type {@link TaskType#USER_DEFINED} to a {@link\n     * TaskModel} in a {@link TaskModel.Status#SCHEDULED} state\n     *\n     * @param taskMapperContext: A wrapper class containing the {@link WorkflowTask}, {@link\n     *     WorkflowDef}, {@link WorkflowModel} and a string representation of the TaskId\n     * @return a List with just one User defined task\n     * @throws TerminateWorkflowException In case if the task definition does not exist\n     */\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext)\n            throws TerminateWorkflowException {\n\n        LOGGER.debug(\"TaskMapperContext {} in UserDefinedTaskMapper\", taskMapperContext);\n\n        WorkflowTask workflowTask = taskMapperContext.getWorkflowTask();\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n        int retryCount = taskMapperContext.getRetryCount();\n\n        TaskDef taskDefinition =\n                Optional.ofNullable(taskMapperContext.getTaskDefinition())\n                        .orElseGet(\n                                () ->\n                                        Optional.ofNullable(\n                                                        metadataDAO.getTaskDef(\n                                                                workflowTask.getName()))\n                                                .orElseThrow(\n                                                        () -> {\n                                                            String reason =\n                                                                    String.format(\n                                                                            \"Invalid task specified. Cannot find task by name %s in the task definitions\",\n                                                                            workflowTask.getName());\n                                                            return new TerminateWorkflowException(\n                                                                    reason);\n                                                        }));\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInputV2(\n                        workflowTask.getInputParameters(), workflowModel, taskId, taskDefinition);\n\n        TaskModel userDefinedTask = taskMapperContext.createTaskModel();\n        userDefinedTask.setInputData(input);\n        userDefinedTask.setStatus(TaskModel.Status.SCHEDULED);\n        userDefinedTask.setRetryCount(retryCount);\n        userDefinedTask.setCallbackAfterSeconds(workflowTask.getStartDelay());\n        userDefinedTask.setRateLimitPerFrequency(taskDefinition.getRateLimitPerFrequency());\n        userDefinedTask.setRateLimitFrequencyInSeconds(\n                taskDefinition.getRateLimitFrequencyInSeconds());\n\n        return List.of(userDefinedTask);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/mapper/WaitTaskMapper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.text.ParseException;\nimport java.time.Duration;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.tasks.Wait;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_WAIT;\nimport static com.netflix.conductor.core.execution.tasks.Wait.DURATION_INPUT;\nimport static com.netflix.conductor.core.execution.tasks.Wait.UNTIL_INPUT;\nimport static com.netflix.conductor.core.utils.DateTimeUtils.parseDate;\nimport static com.netflix.conductor.core.utils.DateTimeUtils.parseDuration;\nimport static com.netflix.conductor.model.TaskModel.Status.FAILED_WITH_TERMINAL_ERROR;\n\n/**\n * An implementation of {@link TaskMapper} to map a {@link WorkflowTask} of type {@link\n * TaskType#WAIT} to a {@link TaskModel} of type {@link Wait} with {@link\n * TaskModel.Status#IN_PROGRESS}\n */\n@Component\npublic class WaitTaskMapper implements TaskMapper {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(WaitTaskMapper.class);\n\n    private final ParametersUtils parametersUtils;\n\n    public WaitTaskMapper(ParametersUtils parametersUtils) {\n        this.parametersUtils = parametersUtils;\n    }\n\n    @Override\n    public String getTaskType() {\n        return TaskType.WAIT.name();\n    }\n\n    @Override\n    public List<TaskModel> getMappedTasks(TaskMapperContext taskMapperContext) {\n\n        LOGGER.debug(\"TaskMapperContext {} in WaitTaskMapper\", taskMapperContext);\n\n        WorkflowModel workflowModel = taskMapperContext.getWorkflowModel();\n        String taskId = taskMapperContext.getTaskId();\n\n        Map<String, Object> waitTaskInput =\n                parametersUtils.getTaskInputV2(\n                        taskMapperContext.getWorkflowTask().getInputParameters(),\n                        workflowModel,\n                        taskId,\n                        null);\n\n        TaskModel waitTask = taskMapperContext.createTaskModel();\n        waitTask.setTaskType(TASK_TYPE_WAIT);\n        waitTask.setInputData(waitTaskInput);\n        waitTask.setStartTime(System.currentTimeMillis());\n        waitTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        setCallbackAfter(waitTask);\n        return List.of(waitTask);\n    }\n\n    void setCallbackAfter(TaskModel task) {\n        String duration =\n                Optional.ofNullable(task.getInputData().get(DURATION_INPUT)).orElse(\"\").toString();\n        String until =\n                Optional.ofNullable(task.getInputData().get(UNTIL_INPUT)).orElse(\"\").toString();\n\n        if (StringUtils.isNotBlank(duration) && StringUtils.isNotBlank(until)) {\n            task.setReasonForIncompletion(\n                    \"Both 'duration' and 'until' specified. Please provide only one input\");\n            task.setStatus(FAILED_WITH_TERMINAL_ERROR);\n            return;\n        }\n\n        if (StringUtils.isNotBlank(duration)) {\n\n            Duration timeDuration = parseDuration(duration);\n            long waitTimeout = System.currentTimeMillis() + (timeDuration.getSeconds() * 1000);\n            task.setWaitTimeout(waitTimeout);\n            long seconds = timeDuration.getSeconds();\n            task.setCallbackAfterSeconds(seconds);\n\n        } else if (StringUtils.isNotBlank(until)) {\n            try {\n\n                Date expiryDate = parseDate(until);\n                long timeInMS = expiryDate.getTime();\n                long now = System.currentTimeMillis();\n                long seconds = ((timeInMS - now) / 1000);\n                if (seconds < 0) {\n                    seconds = 0;\n                }\n                task.setCallbackAfterSeconds(seconds);\n                task.setWaitTimeout(timeInMS);\n\n            } catch (ParseException parseException) {\n                task.setReasonForIncompletion(\n                        \"Invalid/Unsupported Wait Until format.  Provided: \" + until);\n                task.setStatus(FAILED_WITH_TERMINAL_ERROR);\n            }\n        } else {\n            // If there is no time duration specified then the WAIT task should wait forever\n            task.setCallbackAfterSeconds(Integer.MAX_VALUE);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Decision.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_DECISION;\n\n/**\n * @deprecated {@link Decision} is deprecated. Use {@link Switch} task for condition evaluation\n *     using the extensible evaluation framework. Also see ${@link\n *     com.netflix.conductor.common.metadata.workflow.WorkflowTask}).\n */\n@Deprecated\n@Component(TASK_TYPE_DECISION)\npublic class Decision extends WorkflowSystemTask {\n\n    public Decision() {\n        super(TASK_TYPE_DECISION);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        task.setStatus(TaskModel.Status.COMPLETED);\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/DoWhile.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport javax.script.ScriptException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.events.ScriptEvaluator;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_DO_WHILE;\n\n@Component(TASK_TYPE_DO_WHILE)\npublic class DoWhile extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DoWhile.class);\n\n    private final ParametersUtils parametersUtils;\n\n    public DoWhile(ParametersUtils parametersUtils) {\n        super(TASK_TYPE_DO_WHILE);\n        this.parametersUtils = parametersUtils;\n    }\n\n    @Override\n    public void cancel(WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n        task.setStatus(TaskModel.Status.CANCELED);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel doWhileTaskModel, WorkflowExecutor workflowExecutor) {\n\n        boolean hasFailures = false;\n        StringBuilder failureReason = new StringBuilder();\n        Map<String, Object> output = new HashMap<>();\n\n        /*\n         * Get the latest set of tasks (the ones that have the highest retry count). We don't want to evaluate any tasks\n         * that have already failed if there is a more current one (a later retry count).\n         */\n        Map<String, TaskModel> relevantTasks = new LinkedHashMap<>();\n        TaskModel relevantTask;\n        for (TaskModel t : workflow.getTasks()) {\n            if (doWhileTaskModel\n                            .getWorkflowTask()\n                            .has(TaskUtils.removeIterationFromTaskRefName(t.getReferenceTaskName()))\n                    && !doWhileTaskModel.getReferenceTaskName().equals(t.getReferenceTaskName())\n                    && doWhileTaskModel.getIteration() == t.getIteration()) {\n                relevantTask = relevantTasks.get(t.getReferenceTaskName());\n                if (relevantTask == null || t.getRetryCount() > relevantTask.getRetryCount()) {\n                    relevantTasks.put(t.getReferenceTaskName(), t);\n                }\n            }\n        }\n        Collection<TaskModel> loopOverTasks = relevantTasks.values();\n\n        if (LOGGER.isDebugEnabled()) {\n            LOGGER.debug(\n                    \"Workflow {} waiting for tasks {} to complete iteration {}\",\n                    workflow.getWorkflowId(),\n                    loopOverTasks.stream()\n                            .map(TaskModel::getReferenceTaskName)\n                            .collect(Collectors.toList()),\n                    doWhileTaskModel.getIteration());\n        }\n\n        // if the loopOverTasks collection is empty, no tasks inside the loop have been scheduled.\n        // so schedule it and exit the method.\n        if (loopOverTasks.isEmpty()) {\n            doWhileTaskModel.setIteration(1);\n            doWhileTaskModel.addOutput(\"iteration\", doWhileTaskModel.getIteration());\n            return scheduleNextIteration(doWhileTaskModel, workflow, workflowExecutor);\n        }\n\n        for (TaskModel loopOverTask : loopOverTasks) {\n            TaskModel.Status taskStatus = loopOverTask.getStatus();\n            hasFailures = !taskStatus.isSuccessful();\n            if (hasFailures) {\n                failureReason.append(loopOverTask.getReasonForIncompletion()).append(\" \");\n            }\n            output.put(\n                    TaskUtils.removeIterationFromTaskRefName(loopOverTask.getReferenceTaskName()),\n                    loopOverTask.getOutputData());\n            if (hasFailures) {\n                break;\n            }\n        }\n        doWhileTaskModel.addOutput(String.valueOf(doWhileTaskModel.getIteration()), output);\n\n        if (hasFailures) {\n            LOGGER.debug(\n                    \"Task {} failed in {} iteration\",\n                    doWhileTaskModel.getTaskId(),\n                    doWhileTaskModel.getIteration() + 1);\n            return markTaskFailure(\n                    doWhileTaskModel, TaskModel.Status.FAILED, failureReason.toString());\n        }\n\n        if (!isIterationComplete(doWhileTaskModel, relevantTasks)) {\n            // current iteration is not complete (all tasks inside the loop are not terminal)\n            return false;\n        }\n\n        // if we are here, the iteration is complete, and we need to check if there is a next\n        // iteration by evaluating the loopCondition\n        boolean shouldContinue;\n        try {\n            shouldContinue = evaluateCondition(workflow, doWhileTaskModel);\n            LOGGER.debug(\n                    \"Task {} condition evaluated to {}\",\n                    doWhileTaskModel.getTaskId(),\n                    shouldContinue);\n            if (shouldContinue) {\n                doWhileTaskModel.setIteration(doWhileTaskModel.getIteration() + 1);\n                doWhileTaskModel.addOutput(\"iteration\", doWhileTaskModel.getIteration());\n                return scheduleNextIteration(doWhileTaskModel, workflow, workflowExecutor);\n            } else {\n                LOGGER.debug(\n                        \"Task {} took {} iterations to complete\",\n                        doWhileTaskModel.getTaskId(),\n                        doWhileTaskModel.getIteration() + 1);\n                return markTaskSuccess(doWhileTaskModel);\n            }\n        } catch (ScriptException e) {\n            String message =\n                    String.format(\n                            \"Unable to evaluate condition %s, exception %s\",\n                            doWhileTaskModel.getWorkflowTask().getLoopCondition(), e.getMessage());\n            LOGGER.error(message);\n            return markTaskFailure(\n                    doWhileTaskModel, TaskModel.Status.FAILED_WITH_TERMINAL_ERROR, message);\n        }\n    }\n\n    /**\n     * Check if all tasks in the current iteration have reached terminal state.\n     *\n     * @param doWhileTaskModel The {@link TaskModel} of DO_WHILE.\n     * @param referenceNameToModel Map of taskReferenceName to {@link TaskModel}.\n     * @return true if all tasks in DO_WHILE.loopOver are in <code>referenceNameToModel</code> and\n     *     reached terminal state.\n     */\n    private boolean isIterationComplete(\n            TaskModel doWhileTaskModel, Map<String, TaskModel> referenceNameToModel) {\n        List<WorkflowTask> workflowTasksInsideDoWhile =\n                doWhileTaskModel.getWorkflowTask().getLoopOver();\n        int iteration = doWhileTaskModel.getIteration();\n        boolean allTasksTerminal = true;\n        for (WorkflowTask workflowTaskInsideDoWhile : workflowTasksInsideDoWhile) {\n            String taskReferenceName =\n                    TaskUtils.appendIteration(\n                            workflowTaskInsideDoWhile.getTaskReferenceName(), iteration);\n            if (referenceNameToModel.containsKey(taskReferenceName)) {\n                TaskModel taskModel = referenceNameToModel.get(taskReferenceName);\n                if (!taskModel.getStatus().isTerminal()) {\n                    allTasksTerminal = false;\n                    break;\n                }\n            } else {\n                allTasksTerminal = false;\n                break;\n            }\n        }\n\n        if (!allTasksTerminal) {\n            // Cases where tasks directly inside loop over are not completed.\n            // loopOver -> [task1 -> COMPLETED, task2 -> IN_PROGRESS]\n            return false;\n        }\n\n        // Check all the tasks in referenceNameToModel are completed or not. These are set of tasks\n        // which are not directly inside loopOver tasks, but they are under hierarchy\n        // loopOver -> [decisionTask -> COMPLETED [ task1 -> COMPLETED, task2 -> IN_PROGRESS]]\n        return referenceNameToModel.values().stream()\n                .noneMatch(taskModel -> !taskModel.getStatus().isTerminal());\n    }\n\n    boolean scheduleNextIteration(\n            TaskModel doWhileTaskModel, WorkflowModel workflow, WorkflowExecutor workflowExecutor) {\n        LOGGER.debug(\n                \"Scheduling loop tasks for task {} as condition {} evaluated to true\",\n                doWhileTaskModel.getTaskId(),\n                doWhileTaskModel.getWorkflowTask().getLoopCondition());\n        workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflow);\n        return true; // Return true even though status not changed. Iteration has to be updated in\n        // execution DAO.\n    }\n\n    boolean markTaskFailure(TaskModel taskModel, TaskModel.Status status, String failureReason) {\n        LOGGER.error(\"Marking task {} failed with error.\", taskModel.getTaskId());\n        taskModel.setReasonForIncompletion(failureReason);\n        taskModel.setStatus(status);\n        return true;\n    }\n\n    boolean markTaskSuccess(TaskModel taskModel) {\n        LOGGER.debug(\n                \"Task {} took {} iterations to complete\",\n                taskModel.getTaskId(),\n                taskModel.getIteration() + 1);\n        taskModel.setStatus(TaskModel.Status.COMPLETED);\n        return true;\n    }\n\n    @VisibleForTesting\n    boolean evaluateCondition(WorkflowModel workflow, TaskModel task) throws ScriptException {\n        TaskDef taskDefinition = task.getTaskDefinition().orElse(null);\n        // Use paramUtils to compute the task input\n        Map<String, Object> conditionInput =\n                parametersUtils.getTaskInputV2(\n                        task.getWorkflowTask().getInputParameters(),\n                        workflow,\n                        task.getTaskId(),\n                        taskDefinition);\n        conditionInput.put(task.getReferenceTaskName(), task.getOutputData());\n        List<TaskModel> loopOver =\n                workflow.getTasks().stream()\n                        .filter(\n                                t ->\n                                        (task.getWorkflowTask()\n                                                        .has(\n                                                                TaskUtils\n                                                                        .removeIterationFromTaskRefName(\n                                                                                t\n                                                                                        .getReferenceTaskName()))\n                                                && !task.getReferenceTaskName()\n                                                        .equals(t.getReferenceTaskName())))\n                        .collect(Collectors.toList());\n\n        for (TaskModel loopOverTask : loopOver) {\n            conditionInput.put(\n                    TaskUtils.removeIterationFromTaskRefName(loopOverTask.getReferenceTaskName()),\n                    loopOverTask.getOutputData());\n        }\n\n        String condition = task.getWorkflowTask().getLoopCondition();\n        boolean result = false;\n        if (condition != null) {\n            LOGGER.debug(\"Condition: {} is being evaluated\", condition);\n            // Evaluate the expression by using the Nashorn based script evaluator\n            result = ScriptEvaluator.evalBool(condition, conditionInput);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Event.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.core.events.EventQueues;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_EVENT;\n\n@Component(TASK_TYPE_EVENT)\npublic class Event extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(Event.class);\n    public static final String NAME = \"EVENT\";\n\n    private static final String EVENT_PRODUCED = \"event_produced\";\n\n    private final ObjectMapper objectMapper;\n    private final ParametersUtils parametersUtils;\n    private final EventQueues eventQueues;\n\n    public Event(\n            EventQueues eventQueues, ParametersUtils parametersUtils, ObjectMapper objectMapper) {\n        super(TASK_TYPE_EVENT);\n        this.parametersUtils = parametersUtils;\n        this.eventQueues = eventQueues;\n        this.objectMapper = objectMapper;\n    }\n\n    @Override\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        Map<String, Object> payload = new HashMap<>(task.getInputData());\n        payload.put(\"workflowInstanceId\", workflow.getWorkflowId());\n        payload.put(\"workflowType\", workflow.getWorkflowName());\n        payload.put(\"workflowVersion\", workflow.getWorkflowVersion());\n        payload.put(\"correlationId\", workflow.getCorrelationId());\n\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.addOutput(payload);\n\n        try {\n            task.addOutput(EVENT_PRODUCED, computeQueueName(workflow, task));\n        } catch (Exception e) {\n            task.setStatus(TaskModel.Status.FAILED);\n            task.setReasonForIncompletion(e.getMessage());\n            LOGGER.error(\n                    \"Error executing task: {}, workflow: {}\",\n                    task.getTaskId(),\n                    workflow.getWorkflowId(),\n                    e);\n        }\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        try {\n            String queueName = (String) task.getOutputData().get(EVENT_PRODUCED);\n            ObservableQueue queue = getQueue(queueName, task.getTaskId());\n            Message message = getPopulatedMessage(task);\n            queue.publish(List.of(message));\n            LOGGER.debug(\"Published message:{} to queue:{}\", message.getId(), queue.getName());\n            if (!isAsyncComplete(task)) {\n                task.setStatus(TaskModel.Status.COMPLETED);\n                return true;\n            }\n        } catch (JsonProcessingException jpe) {\n            task.setStatus(TaskModel.Status.FAILED);\n            task.setReasonForIncompletion(\"Error serializing JSON payload: \" + jpe.getMessage());\n            LOGGER.error(\n                    \"Error serializing JSON payload for task: {}, workflow: {}\",\n                    task.getTaskId(),\n                    workflow.getWorkflowId());\n        } catch (Exception e) {\n            task.setStatus(TaskModel.Status.FAILED);\n            task.setReasonForIncompletion(e.getMessage());\n            LOGGER.error(\n                    \"Error executing task: {}, workflow: {}\",\n                    task.getTaskId(),\n                    workflow.getWorkflowId(),\n                    e);\n        }\n        return false;\n    }\n\n    @Override\n    public void cancel(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        Message message = new Message(task.getTaskId(), null, task.getTaskId());\n        String queueName = computeQueueName(workflow, task);\n        ObservableQueue queue = getQueue(queueName, task.getTaskId());\n        queue.ack(List.of(message));\n    }\n\n    @VisibleForTesting\n    String computeQueueName(WorkflowModel workflow, TaskModel task) {\n        String sinkValueRaw = (String) task.getInputData().get(\"sink\");\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"sink\", sinkValueRaw);\n        Map<String, Object> replaced =\n                parametersUtils.getTaskInputV2(input, workflow, task.getTaskId(), null);\n        String sinkValue = (String) replaced.get(\"sink\");\n        String queueName = sinkValue;\n\n        if (sinkValue.startsWith(\"conductor\")) {\n            if (\"conductor\".equals(sinkValue)) {\n                queueName =\n                        sinkValue\n                                + \":\"\n                                + workflow.getWorkflowName()\n                                + \":\"\n                                + task.getReferenceTaskName();\n            } else if (sinkValue.startsWith(\"conductor:\")) {\n                queueName =\n                        \"conductor:\"\n                                + workflow.getWorkflowName()\n                                + \":\"\n                                + sinkValue.replaceAll(\"conductor:\", \"\");\n            } else {\n                throw new IllegalStateException(\n                        \"Invalid / Unsupported sink specified: \" + sinkValue);\n            }\n        }\n        return queueName;\n    }\n\n    @VisibleForTesting\n    ObservableQueue getQueue(String queueName, String taskId) {\n        try {\n            return eventQueues.getQueue(queueName);\n        } catch (IllegalArgumentException e) {\n            throw new IllegalStateException(\n                    \"Error loading queue:\"\n                            + queueName\n                            + \", for task:\"\n                            + taskId\n                            + \", error: \"\n                            + e.getMessage());\n        } catch (Exception e) {\n            throw new NonTransientException(\"Unable to find queue name for task \" + taskId);\n        }\n    }\n\n    Message getPopulatedMessage(TaskModel task) throws JsonProcessingException {\n        String payloadJson = objectMapper.writeValueAsString(task.getOutputData());\n        return new Message(task.getTaskId(), payloadJson, task.getTaskId());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/ExclusiveJoin.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_EXCLUSIVE_JOIN;\n\n@Component(TASK_TYPE_EXCLUSIVE_JOIN)\npublic class ExclusiveJoin extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ExclusiveJoin.class);\n\n    private static final String DEFAULT_EXCLUSIVE_JOIN_TASKS = \"defaultExclusiveJoinTask\";\n\n    public ExclusiveJoin() {\n        super(TASK_TYPE_EXCLUSIVE_JOIN);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n\n        boolean foundExlusiveJoinOnTask = false;\n        boolean hasFailures = false;\n        StringBuilder failureReason = new StringBuilder();\n        TaskModel.Status taskStatus;\n        List<String> joinOn = (List<String>) task.getInputData().get(\"joinOn\");\n        if (task.isLoopOverTask()) {\n            // If exclusive join is part of loop over task, wait for specific iteration to get\n            // complete\n            joinOn =\n                    joinOn.stream()\n                            .map(name -> TaskUtils.appendIteration(name, task.getIteration()))\n                            .collect(Collectors.toList());\n        }\n        TaskModel exclusiveTask = null;\n        for (String joinOnRef : joinOn) {\n            LOGGER.debug(\"Exclusive Join On Task {} \", joinOnRef);\n            exclusiveTask = workflow.getTaskByRefName(joinOnRef);\n            if (exclusiveTask == null || exclusiveTask.getStatus() == TaskModel.Status.SKIPPED) {\n                LOGGER.debug(\"The task {} is either not scheduled or skipped.\", joinOnRef);\n                continue;\n            }\n            taskStatus = exclusiveTask.getStatus();\n            foundExlusiveJoinOnTask = taskStatus.isTerminal();\n            hasFailures = !taskStatus.isSuccessful();\n            if (hasFailures) {\n                failureReason.append(exclusiveTask.getReasonForIncompletion()).append(\" \");\n            }\n\n            break;\n        }\n\n        if (!foundExlusiveJoinOnTask) {\n            List<String> defaultExclusiveJoinTasks =\n                    (List<String>) task.getInputData().get(DEFAULT_EXCLUSIVE_JOIN_TASKS);\n            LOGGER.info(\n                    \"Could not perform exclusive on Join Task(s). Performing now on default exclusive join task(s) {}, workflow: {}\",\n                    defaultExclusiveJoinTasks,\n                    workflow.getWorkflowId());\n            if (defaultExclusiveJoinTasks != null && !defaultExclusiveJoinTasks.isEmpty()) {\n                for (String defaultExclusiveJoinTask : defaultExclusiveJoinTasks) {\n                    // Pick the first task that we should join on and break.\n                    exclusiveTask = workflow.getTaskByRefName(defaultExclusiveJoinTask);\n                    if (exclusiveTask == null\n                            || exclusiveTask.getStatus() == TaskModel.Status.SKIPPED) {\n                        LOGGER.debug(\n                                \"The task {} is either not scheduled or skipped.\",\n                                defaultExclusiveJoinTask);\n                        continue;\n                    }\n\n                    taskStatus = exclusiveTask.getStatus();\n                    foundExlusiveJoinOnTask = taskStatus.isTerminal();\n                    hasFailures = !taskStatus.isSuccessful();\n                    if (hasFailures) {\n                        failureReason.append(exclusiveTask.getReasonForIncompletion()).append(\" \");\n                    }\n                    break;\n                }\n            } else {\n                LOGGER.debug(\n                        \"Could not evaluate last tasks output. Verify the task configuration in the workflow definition.\");\n            }\n        }\n\n        LOGGER.debug(\n                \"Status of flags: foundExlusiveJoinOnTask: {}, hasFailures {}\",\n                foundExlusiveJoinOnTask,\n                hasFailures);\n        if (foundExlusiveJoinOnTask || hasFailures) {\n            if (hasFailures) {\n                task.setReasonForIncompletion(failureReason.toString());\n                task.setStatus(TaskModel.Status.FAILED);\n            } else {\n                task.setOutputData(exclusiveTask.getOutputData());\n                task.setStatus(TaskModel.Status.COMPLETED);\n            }\n            LOGGER.debug(\"Task: {} status is: {}\", task.getTaskId(), task.getStatus());\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/ExecutionConfig.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport org.apache.commons.lang3.concurrent.BasicThreadFactory;\n\nimport com.netflix.conductor.core.utils.SemaphoreUtil;\n\nclass ExecutionConfig {\n\n    private final ExecutorService executorService;\n    private final SemaphoreUtil semaphoreUtil;\n\n    ExecutionConfig(int threadCount, String threadNameFormat) {\n\n        this.executorService =\n                Executors.newFixedThreadPool(\n                        threadCount,\n                        new BasicThreadFactory.Builder().namingPattern(threadNameFormat).build());\n\n        this.semaphoreUtil = new SemaphoreUtil(threadCount);\n    }\n\n    public ExecutorService getExecutorService() {\n        return executorService;\n    }\n\n    public SemaphoreUtil getSemaphoreUtil() {\n        return semaphoreUtil;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Fork.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport org.springframework.stereotype.Component;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK;\n\n@Component(TASK_TYPE_FORK)\npublic class Fork extends WorkflowSystemTask {\n\n    public Fork() {\n        super(TASK_TYPE_FORK);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Human.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_HUMAN;\nimport static com.netflix.conductor.model.TaskModel.Status.IN_PROGRESS;\n\n@Component(TASK_TYPE_HUMAN)\npublic class Human extends WorkflowSystemTask {\n\n    public Human() {\n        super(TASK_TYPE_HUMAN);\n    }\n\n    @Override\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        task.setStatus(IN_PROGRESS);\n    }\n\n    @Override\n    public void cancel(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        task.setStatus(TaskModel.Status.CANCELED);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Inline.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.Map;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_INLINE;\n\n/**\n * @author X-Ultra\n *     <p>Task that enables execute inline script at workflow execution. For example,\n *     <pre>\n * ...\n * {\n *  \"tasks\": [\n *      {\n *          \"name\": \"INLINE\",\n *          \"taskReferenceName\": \"inline_test\",\n *          \"type\": \"INLINE\",\n *          \"inputParameters\": {\n *              \"input\": \"${workflow.input}\",\n *              \"evaluatorType\": \"javascript\"\n *              \"expression\": \"if ($.input.a==1){return {testvalue: true}} else{return {testvalue: false} }\"\n *          }\n *      }\n *  ]\n * }\n * ...\n * </pre>\n *     then to use task output, e.g. <code>script_test.output.testvalue</code> {@link Inline} is a\n *     replacement for deprecated {@link Lambda}\n */\n@Component(TASK_TYPE_INLINE)\npublic class Inline extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(Inline.class);\n    private static final String QUERY_EVALUATOR_TYPE = \"evaluatorType\";\n    private static final String QUERY_EXPRESSION_PARAMETER = \"expression\";\n    public static final String NAME = \"INLINE\";\n\n    private final Map<String, Evaluator> evaluators;\n\n    public Inline(Map<String, Evaluator> evaluators) {\n        super(TASK_TYPE_INLINE);\n        this.evaluators = evaluators;\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        Map<String, Object> taskInput = task.getInputData();\n        String evaluatorType = (String) taskInput.get(QUERY_EVALUATOR_TYPE);\n        String expression = (String) taskInput.get(QUERY_EXPRESSION_PARAMETER);\n\n        try {\n            checkEvaluatorType(evaluatorType);\n            checkExpression(expression);\n            Evaluator evaluator = evaluators.get(evaluatorType);\n            Object evalResult = evaluator.evaluate(expression, taskInput);\n            task.addOutput(\"result\", evalResult);\n            task.setStatus(TaskModel.Status.COMPLETED);\n        } catch (Exception e) {\n            String errorMessage = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();\n            LOGGER.error(\n                    \"Failed to execute Inline Task: {} in workflow: {}\",\n                    task.getTaskId(),\n                    workflow.getWorkflowId(),\n                    e);\n            // TerminateWorkflowException is thrown when the script evaluation fails\n            // Retry will result in the same error, so FAILED_WITH_TERMINAL_ERROR status is used.\n            task.setStatus(\n                    e instanceof TerminateWorkflowException\n                            ? TaskModel.Status.FAILED_WITH_TERMINAL_ERROR\n                            : TaskModel.Status.FAILED);\n            task.setReasonForIncompletion(errorMessage);\n            task.addOutput(\"error\", errorMessage);\n        }\n\n        return true;\n    }\n\n    private void checkEvaluatorType(String evaluatorType) {\n        if (StringUtils.isBlank(evaluatorType)) {\n            LOGGER.error(\"Empty {} in INLINE task. \", QUERY_EVALUATOR_TYPE);\n            throw new TerminateWorkflowException(\n                    \"Empty '\"\n                            + QUERY_EVALUATOR_TYPE\n                            + \"' in INLINE task's input parameters. A non-empty String value must be provided.\");\n        }\n        if (evaluators.get(evaluatorType) == null) {\n            LOGGER.error(\"Evaluator {} for INLINE task not registered\", evaluatorType);\n            throw new TerminateWorkflowException(\n                    \"Unknown evaluator '\" + evaluatorType + \"' in INLINE task.\");\n        }\n    }\n\n    private void checkExpression(String expression) {\n        if (StringUtils.isBlank(expression)) {\n            LOGGER.error(\"Empty {} in INLINE task. \", QUERY_EXPRESSION_PARAMETER);\n            throw new TerminateWorkflowException(\n                    \"Empty '\"\n                            + QUERY_EXPRESSION_PARAMETER\n                            + \"' in Inline task's input parameters. A non-empty String value must be provided.\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/IsolatedTaskQueueProducer.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.service.MetadataService;\n\nimport static com.netflix.conductor.core.execution.tasks.SystemTaskRegistry.ASYNC_SYSTEM_TASKS_QUALIFIER;\n\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.system-task-workers.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class IsolatedTaskQueueProducer {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(IsolatedTaskQueueProducer.class);\n    private final MetadataService metadataService;\n    private final Set<WorkflowSystemTask> asyncSystemTasks;\n    private final SystemTaskWorker systemTaskWorker;\n\n    private final Set<String> listeningQueues = new HashSet<>();\n\n    public IsolatedTaskQueueProducer(\n            MetadataService metadataService,\n            @Qualifier(ASYNC_SYSTEM_TASKS_QUALIFIER) Set<WorkflowSystemTask> asyncSystemTasks,\n            SystemTaskWorker systemTaskWorker,\n            @Value(\"${conductor.app.isolatedSystemTaskEnabled:false}\")\n                    boolean isolatedSystemTaskEnabled,\n            @Value(\"${conductor.app.isolatedSystemTaskQueuePollInterval:10s}\")\n                    Duration isolatedSystemTaskQueuePollInterval) {\n\n        this.metadataService = metadataService;\n        this.asyncSystemTasks = asyncSystemTasks;\n        this.systemTaskWorker = systemTaskWorker;\n\n        if (isolatedSystemTaskEnabled) {\n            LOGGER.info(\"Listening for isolation groups\");\n\n            Executors.newSingleThreadScheduledExecutor()\n                    .scheduleWithFixedDelay(\n                            this::addTaskQueues,\n                            1000,\n                            isolatedSystemTaskQueuePollInterval.toMillis(),\n                            TimeUnit.MILLISECONDS);\n        } else {\n            LOGGER.info(\"Isolated System Task Worker DISABLED\");\n        }\n    }\n\n    private Set<TaskDef> getIsolationExecutionNameSpaces() {\n        Set<TaskDef> isolationExecutionNameSpaces = Collections.emptySet();\n        try {\n            List<TaskDef> taskDefs = metadataService.getTaskDefs();\n            isolationExecutionNameSpaces =\n                    taskDefs.stream()\n                            .filter(\n                                    taskDef ->\n                                            StringUtils.isNotBlank(taskDef.getIsolationGroupId())\n                                                    || StringUtils.isNotBlank(\n                                                            taskDef.getExecutionNameSpace()))\n                            .collect(Collectors.toSet());\n        } catch (RuntimeException e) {\n            LOGGER.error(\n                    \"Unknown exception received in getting isolation groups, sleeping and retrying\",\n                    e);\n        }\n        return isolationExecutionNameSpaces;\n    }\n\n    @VisibleForTesting\n    void addTaskQueues() {\n        Set<TaskDef> isolationTaskDefs = getIsolationExecutionNameSpaces();\n        LOGGER.debug(\"Retrieved queues {}\", isolationTaskDefs);\n\n        for (TaskDef isolatedTaskDef : isolationTaskDefs) {\n            for (WorkflowSystemTask systemTask : this.asyncSystemTasks) {\n                String taskQueue =\n                        QueueUtils.getQueueName(\n                                systemTask.getTaskType(),\n                                null,\n                                isolatedTaskDef.getIsolationGroupId(),\n                                isolatedTaskDef.getExecutionNameSpace());\n                LOGGER.debug(\"Adding taskQueue:'{}' to system task worker coordinator\", taskQueue);\n                if (!listeningQueues.contains(taskQueue)) {\n                    systemTaskWorker.startPolling(systemTask, taskQueue);\n                    listeningQueues.add(taskQueue);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Join.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN;\n\n@Component(TASK_TYPE_JOIN)\npublic class Join extends WorkflowSystemTask {\n\n    public Join() {\n        super(TASK_TYPE_JOIN);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n\n        boolean allDone = true;\n        boolean hasFailures = false;\n        StringBuilder failureReason = new StringBuilder();\n        StringBuilder optionalTaskFailures = new StringBuilder();\n        List<String> joinOn = (List<String>) task.getInputData().get(\"joinOn\");\n        if (task.isLoopOverTask()) {\n            // If join is part of loop over task, wait for specific iteration to get complete\n            joinOn =\n                    joinOn.stream()\n                            .map(name -> TaskUtils.appendIteration(name, task.getIteration()))\n                            .collect(Collectors.toList());\n        }\n        for (String joinOnRef : joinOn) {\n            TaskModel forkedTask = workflow.getTaskByRefName(joinOnRef);\n            if (forkedTask == null) {\n                // Task is not even scheduled yet\n                allDone = false;\n                break;\n            }\n            TaskModel.Status taskStatus = forkedTask.getStatus();\n            hasFailures = !taskStatus.isSuccessful() && !forkedTask.getWorkflowTask().isOptional();\n            if (hasFailures) {\n                failureReason.append(forkedTask.getReasonForIncompletion()).append(\" \");\n            }\n            // Only add to task output if it's not empty\n            if (!forkedTask.getOutputData().isEmpty()) {\n                task.addOutput(joinOnRef, forkedTask.getOutputData());\n            }\n            if (!taskStatus.isTerminal()) {\n                allDone = false;\n            }\n            if (hasFailures) {\n                break;\n            }\n\n            // check for optional task failures\n            if (forkedTask.getWorkflowTask().isOptional()\n                    && taskStatus == TaskModel.Status.COMPLETED_WITH_ERRORS) {\n                optionalTaskFailures\n                        .append(\n                                String.format(\n                                        \"%s/%s\",\n                                        forkedTask.getTaskDefName(), forkedTask.getTaskId()))\n                        .append(\" \");\n            }\n        }\n        if (allDone || hasFailures || optionalTaskFailures.length() > 0) {\n            if (hasFailures) {\n                task.setReasonForIncompletion(failureReason.toString());\n                task.setStatus(TaskModel.Status.FAILED);\n            } else if (optionalTaskFailures.length() > 0) {\n                task.setStatus(TaskModel.Status.COMPLETED_WITH_ERRORS);\n                optionalTaskFailures.append(\"completed with errors\");\n                task.setReasonForIncompletion(optionalTaskFailures.toString());\n            } else {\n                task.setStatus(TaskModel.Status.COMPLETED);\n            }\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public Optional<Long> getEvaluationOffset(TaskModel taskModel, long defaultOffset) {\n        int index = taskModel.getPollCount() > 0 ? taskModel.getPollCount() - 1 : 0;\n        if (index == 0) {\n            return Optional.of(0L);\n        }\n        return Optional.of(Math.min((long) Math.pow(2, index), defaultOffset));\n    }\n\n    public boolean isAsync() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Lambda.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.Map;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.events.ScriptEvaluator;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_LAMBDA;\n\n/**\n * @author X-Ultra\n *     <p>Task that enables execute Lambda script at workflow execution, For example,\n *     <pre>\n * ...\n * {\n *  \"tasks\": [\n *      {\n *          \"name\": \"LAMBDA\",\n *          \"taskReferenceName\": \"lambda_test\",\n *          \"type\": \"LAMBDA\",\n *          \"inputParameters\": {\n *              \"input\": \"${workflow.input}\",\n *              \"scriptExpression\": \"if ($.input.a==1){return {testvalue: true}} else{return {testvalue: false} }\"\n *          }\n *      }\n *  ]\n * }\n * ...\n * </pre>\n *     then to use task output, e.g. <code>script_test.output.testvalue</code>\n * @deprecated {@link Lambda} is deprecated. Use {@link Inline} task for inline expression\n *     evaluation. Also see ${@link com.netflix.conductor.common.metadata.workflow.WorkflowTask})\n */\n@Deprecated\n@Component(TASK_TYPE_LAMBDA)\npublic class Lambda extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(Lambda.class);\n    private static final String QUERY_EXPRESSION_PARAMETER = \"scriptExpression\";\n    public static final String NAME = \"LAMBDA\";\n\n    public Lambda() {\n        super(TASK_TYPE_LAMBDA);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        Map<String, Object> taskInput = task.getInputData();\n        String scriptExpression;\n        try {\n            scriptExpression = (String) taskInput.get(QUERY_EXPRESSION_PARAMETER);\n            if (StringUtils.isNotBlank(scriptExpression)) {\n                String scriptExpressionBuilder =\n                        \"function scriptFun(){\" + scriptExpression + \"} scriptFun();\";\n\n                LOGGER.debug(\n                        \"scriptExpressionBuilder: {}, task: {}\",\n                        scriptExpressionBuilder,\n                        task.getTaskId());\n                Object returnValue = ScriptEvaluator.eval(scriptExpressionBuilder, taskInput);\n                task.addOutput(\"result\", returnValue);\n                task.setStatus(TaskModel.Status.COMPLETED);\n            } else {\n                LOGGER.error(\"Empty {} in Lambda task. \", QUERY_EXPRESSION_PARAMETER);\n                task.setReasonForIncompletion(\n                        \"Empty '\"\n                                + QUERY_EXPRESSION_PARAMETER\n                                + \"' in Lambda task's input parameters. A non-empty String value must be provided.\");\n                task.setStatus(TaskModel.Status.FAILED);\n            }\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Failed to execute Lambda Task: {} in workflow: {}\",\n                    task.getTaskId(),\n                    workflow.getWorkflowId(),\n                    e);\n            task.setStatus(TaskModel.Status.FAILED);\n            task.setReasonForIncompletion(e.getMessage());\n            task.addOutput(\n                    \"error\", e.getCause() != null ? e.getCause().getMessage() : e.getMessage());\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Noop.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_NOOP;\n\n@Component(TASK_TYPE_NOOP)\npublic class Noop extends WorkflowSystemTask {\n\n    public Noop() {\n        super(TASK_TYPE_NOOP);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        task.setStatus(TaskModel.Status.COMPLETED);\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/SetVariable.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SET_VARIABLE;\n\n@Component(TASK_TYPE_SET_VARIABLE)\npublic class SetVariable extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SetVariable.class);\n\n    private final ConductorProperties properties;\n    private final ObjectMapper objectMapper;\n\n    private final ExecutionDAOFacade executionDAOFacade;\n\n    public SetVariable(\n            ConductorProperties properties,\n            ObjectMapper objectMapper,\n            ExecutionDAOFacade executionDAOFacade) {\n        super(TASK_TYPE_SET_VARIABLE);\n        this.properties = properties;\n        this.objectMapper = objectMapper;\n        this.executionDAOFacade = executionDAOFacade;\n    }\n\n    private boolean validateVariablesSize(\n            WorkflowModel workflow, TaskModel task, Map<String, Object> variables) {\n        String workflowId = workflow.getWorkflowId();\n        long maxThreshold = properties.getMaxWorkflowVariablesPayloadSizeThreshold().toKilobytes();\n\n        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {\n            this.objectMapper.writeValue(byteArrayOutputStream, variables);\n            byte[] payloadBytes = byteArrayOutputStream.toByteArray();\n            long payloadSize = payloadBytes.length;\n\n            if (payloadSize > maxThreshold * 1024) {\n                String errorMsg =\n                        String.format(\n                                \"The variables payload size: %d of workflow: %s is greater than the permissible limit: %d bytes\",\n                                payloadSize, workflowId, maxThreshold);\n                LOGGER.error(errorMsg);\n                task.setReasonForIncompletion(errorMsg);\n                return false;\n            }\n            return true;\n        } catch (IOException e) {\n            LOGGER.error(\n                    \"Unable to validate variables payload size of workflow: {}\", workflowId, e);\n            throw new NonTransientException(\n                    \"Unable to validate variables payload size of workflow: \" + workflowId, e);\n        }\n    }\n\n    @Override\n    public boolean execute(WorkflowModel workflow, TaskModel task, WorkflowExecutor provider) {\n        Map<String, Object> variables = workflow.getVariables();\n        Map<String, Object> input = task.getInputData();\n        String taskId = task.getTaskId();\n        ArrayList<String> newKeys;\n        Map<String, Object> previousValues;\n\n        if (input != null && input.size() > 0) {\n            newKeys = new ArrayList<>();\n            previousValues = new HashMap<>();\n            input.keySet()\n                    .forEach(\n                            key -> {\n                                if (variables.containsKey(key)) {\n                                    previousValues.put(key, variables.get(key));\n                                } else {\n                                    newKeys.add(key);\n                                }\n                                variables.put(key, input.get(key));\n                                LOGGER.debug(\n                                        \"Task: {} setting value for variable: {}\", taskId, key);\n                            });\n            if (!validateVariablesSize(workflow, task, variables)) {\n                // restore previous variables\n                previousValues\n                        .keySet()\n                        .forEach(\n                                key -> {\n                                    variables.put(key, previousValues.get(key));\n                                });\n                newKeys.forEach(variables::remove);\n                task.setStatus(TaskModel.Status.FAILED_WITH_TERMINAL_ERROR);\n                return true;\n            }\n        }\n\n        task.setStatus(TaskModel.Status.COMPLETED);\n        executionDAOFacade.updateWorkflow(workflow);\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/StartWorkflow.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.validation.Validator;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_START_WORKFLOW;\nimport static com.netflix.conductor.model.TaskModel.Status.COMPLETED;\nimport static com.netflix.conductor.model.TaskModel.Status.FAILED;\n\n@Component(TASK_TYPE_START_WORKFLOW)\npublic class StartWorkflow extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(StartWorkflow.class);\n\n    private static final String WORKFLOW_ID = \"workflowId\";\n    private static final String START_WORKFLOW_PARAMETER = \"startWorkflow\";\n\n    private final ObjectMapper objectMapper;\n    private final Validator validator;\n    private final StartWorkflowOperation startWorkflowOperation;\n\n    public StartWorkflow(\n            ObjectMapper objectMapper,\n            Validator validator,\n            StartWorkflowOperation startWorkflowOperation) {\n        super(TASK_TYPE_START_WORKFLOW);\n        this.objectMapper = objectMapper;\n        this.validator = validator;\n        this.startWorkflowOperation = startWorkflowOperation;\n    }\n\n    @Override\n    public void start(\n            WorkflowModel workflow, TaskModel taskModel, WorkflowExecutor workflowExecutor) {\n        StartWorkflowRequest request = getRequest(taskModel);\n        if (request == null) {\n            return;\n        }\n\n        if (request.getTaskToDomain() == null || request.getTaskToDomain().isEmpty()) {\n            Map<String, String> workflowTaskToDomainMap = workflow.getTaskToDomain();\n            if (workflowTaskToDomainMap != null) {\n                request.setTaskToDomain(new HashMap<>(workflowTaskToDomainMap));\n            }\n        }\n\n        // set the correlation id of starter workflow, if its empty in the StartWorkflowRequest\n        request.setCorrelationId(\n                StringUtils.defaultIfBlank(\n                        request.getCorrelationId(), workflow.getCorrelationId()));\n\n        try {\n            String workflowId = startWorkflow(request, workflow.getWorkflowId());\n            taskModel.addOutput(WORKFLOW_ID, workflowId);\n            taskModel.setStatus(COMPLETED);\n        } catch (TransientException te) {\n            LOGGER.info(\n                    \"A transient backend error happened when task {} in {} tried to start workflow {}.\",\n                    taskModel.getTaskId(),\n                    workflow.toShortString(),\n                    request.getName());\n        } catch (Exception ae) {\n\n            taskModel.setStatus(FAILED);\n            taskModel.setReasonForIncompletion(ae.getMessage());\n            LOGGER.error(\n                    \"Error starting workflow: {} from workflow: {}\",\n                    request.getName(),\n                    workflow.toShortString(),\n                    ae);\n        }\n    }\n\n    private StartWorkflowRequest getRequest(TaskModel taskModel) {\n        Map<String, Object> taskInput = taskModel.getInputData();\n\n        StartWorkflowRequest startWorkflowRequest = null;\n\n        if (taskInput.get(START_WORKFLOW_PARAMETER) == null) {\n            taskModel.setStatus(FAILED);\n            taskModel.setReasonForIncompletion(\n                    \"Missing '\" + START_WORKFLOW_PARAMETER + \"' in input data.\");\n        } else {\n            try {\n                startWorkflowRequest =\n                        objectMapper.convertValue(\n                                taskInput.get(START_WORKFLOW_PARAMETER),\n                                StartWorkflowRequest.class);\n\n                var violations = validator.validate(startWorkflowRequest);\n                if (!violations.isEmpty()) {\n                    StringBuilder reasonForIncompletion =\n                            new StringBuilder(START_WORKFLOW_PARAMETER)\n                                    .append(\" validation failed. \");\n                    for (var violation : violations) {\n                        reasonForIncompletion\n                                .append(\"'\")\n                                .append(violation.getPropertyPath().toString())\n                                .append(\"' -> \")\n                                .append(violation.getMessage())\n                                .append(\". \");\n                    }\n                    taskModel.setStatus(FAILED);\n                    taskModel.setReasonForIncompletion(reasonForIncompletion.toString());\n                    startWorkflowRequest = null;\n                }\n            } catch (IllegalArgumentException e) {\n                LOGGER.error(\"Error reading StartWorkflowRequest for {}\", taskModel, e);\n                taskModel.setStatus(FAILED);\n                taskModel.setReasonForIncompletion(\n                        \"Error reading StartWorkflowRequest. \" + e.getMessage());\n            }\n        }\n\n        return startWorkflowRequest;\n    }\n\n    private String startWorkflow(StartWorkflowRequest request, String workflowId) {\n        StartWorkflowInput input = new StartWorkflowInput(request);\n        input.setTriggeringWorkflowId(workflowId);\n        return startWorkflowOperation.execute(input);\n    }\n\n    @Override\n    public boolean isAsync() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/SubWorkflow.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.Map;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW;\n\n@Component(TASK_TYPE_SUB_WORKFLOW)\npublic class SubWorkflow extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SubWorkflow.class);\n    private static final String SUB_WORKFLOW_ID = \"subWorkflowId\";\n\n    private final ObjectMapper objectMapper;\n    private final StartWorkflowOperation startWorkflowOperation;\n\n    public SubWorkflow(ObjectMapper objectMapper, StartWorkflowOperation startWorkflowOperation) {\n        super(TASK_TYPE_SUB_WORKFLOW);\n        this.objectMapper = objectMapper;\n        this.startWorkflowOperation = startWorkflowOperation;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        Map<String, Object> input = task.getInputData();\n        String name = input.get(\"subWorkflowName\").toString();\n        int version = (int) input.get(\"subWorkflowVersion\");\n\n        WorkflowDef workflowDefinition = null;\n        if (input.get(\"subWorkflowDefinition\") != null) {\n            // convert the value back to workflow definition object\n            workflowDefinition =\n                    objectMapper.convertValue(\n                            input.get(\"subWorkflowDefinition\"), WorkflowDef.class);\n            name = workflowDefinition.getName();\n        }\n\n        Map<String, String> taskToDomain = workflow.getTaskToDomain();\n        if (input.get(\"subWorkflowTaskToDomain\") instanceof Map) {\n            taskToDomain = (Map<String, String>) input.get(\"subWorkflowTaskToDomain\");\n        }\n\n        var wfInput = (Map<String, Object>) input.get(\"workflowInput\");\n        if (wfInput == null || wfInput.isEmpty()) {\n            wfInput = input;\n        }\n        String correlationId = workflow.getCorrelationId();\n\n        try {\n            StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n            startWorkflowInput.setWorkflowDefinition(workflowDefinition);\n            startWorkflowInput.setName(name);\n            startWorkflowInput.setVersion(version);\n            startWorkflowInput.setWorkflowInput(wfInput);\n            startWorkflowInput.setCorrelationId(correlationId);\n            startWorkflowInput.setParentWorkflowId(workflow.getWorkflowId());\n            startWorkflowInput.setParentWorkflowTaskId(task.getTaskId());\n            startWorkflowInput.setTaskToDomain(taskToDomain);\n\n            String subWorkflowId = startWorkflowOperation.execute(startWorkflowInput);\n\n            task.setSubWorkflowId(subWorkflowId);\n            // For backwards compatibility\n            task.addOutput(SUB_WORKFLOW_ID, subWorkflowId);\n\n            // Set task status based on current sub-workflow status, as the status can change in\n            // recursion by the time we update here.\n            WorkflowModel subWorkflow = workflowExecutor.getWorkflow(subWorkflowId, false);\n            updateTaskStatus(subWorkflow, task);\n        } catch (TransientException te) {\n            LOGGER.info(\n                    \"A transient backend error happened when task {} in {} tried to start sub workflow {}.\",\n                    task.getTaskId(),\n                    workflow.toShortString(),\n                    name);\n        } catch (Exception ae) {\n\n            task.setStatus(TaskModel.Status.FAILED);\n            task.setReasonForIncompletion(ae.getMessage());\n            LOGGER.error(\n                    \"Error starting sub workflow: {} from workflow: {}\",\n                    name,\n                    workflow.toShortString(),\n                    ae);\n        }\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        String workflowId = task.getSubWorkflowId();\n        if (StringUtils.isEmpty(workflowId)) {\n            return false;\n        }\n\n        WorkflowModel subWorkflow = workflowExecutor.getWorkflow(workflowId, false);\n        WorkflowModel.Status subWorkflowStatus = subWorkflow.getStatus();\n        if (!subWorkflowStatus.isTerminal()) {\n            return false;\n        }\n\n        updateTaskStatus(subWorkflow, task);\n        return true;\n    }\n\n    @Override\n    public void cancel(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        String workflowId = task.getSubWorkflowId();\n        if (StringUtils.isEmpty(workflowId)) {\n            return;\n        }\n        WorkflowModel subWorkflow = workflowExecutor.getWorkflow(workflowId, true);\n        subWorkflow.setStatus(WorkflowModel.Status.TERMINATED);\n        String reason =\n                StringUtils.isEmpty(workflow.getReasonForIncompletion())\n                        ? \"Parent workflow has been terminated with status \" + workflow.getStatus()\n                        : \"Parent workflow has been terminated with reason: \"\n                                + workflow.getReasonForIncompletion();\n        workflowExecutor.terminateWorkflow(subWorkflow, reason, null);\n    }\n\n    @Override\n    public boolean isAsync() {\n        return true;\n    }\n\n    /**\n     * Keep Subworkflow task asyncComplete. The Subworkflow task will be executed once\n     * asynchronously to move to IN_PROGRESS state, and will move to termination by Subworkflow's\n     * completeWorkflow logic, there by avoiding periodic polling.\n     *\n     * @param task\n     * @return\n     */\n    @Override\n    public boolean isAsyncComplete(TaskModel task) {\n        return true;\n    }\n\n    private void updateTaskStatus(WorkflowModel subworkflow, TaskModel task) {\n        WorkflowModel.Status status = subworkflow.getStatus();\n        switch (status) {\n            case RUNNING:\n            case PAUSED:\n                task.setStatus(TaskModel.Status.IN_PROGRESS);\n                break;\n            case COMPLETED:\n                task.setStatus(TaskModel.Status.COMPLETED);\n                break;\n            case FAILED:\n                task.setStatus(TaskModel.Status.FAILED);\n                break;\n            case TERMINATED:\n                task.setStatus(TaskModel.Status.CANCELED);\n                break;\n            case TIMED_OUT:\n                task.setStatus(TaskModel.Status.TIMED_OUT);\n                break;\n            default:\n                throw new NonTransientException(\n                        \"Subworkflow status does not conform to relevant task status.\");\n        }\n\n        if (status.isTerminal()) {\n            if (subworkflow.getExternalOutputPayloadStoragePath() != null) {\n                task.setExternalOutputPayloadStoragePath(\n                        subworkflow.getExternalOutputPayloadStoragePath());\n            } else {\n                task.addOutput(subworkflow.getOutput());\n            }\n            if (!status.isSuccessful()) {\n                task.setReasonForIncompletion(\n                        String.format(\n                                \"Sub workflow %s failure reason: %s\",\n                                subworkflow.toShortString(),\n                                subworkflow.getReasonForIncompletion()));\n            }\n        }\n    }\n\n    /**\n     * We don't need the tasks when retrieving the workflow data.\n     *\n     * @return false\n     */\n    @Override\n    public boolean isTaskRetrievalRequired() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Switch.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SWITCH;\n\n/** {@link Switch} task is a replacement for now deprecated {@link Decision} task. */\n@Component(TASK_TYPE_SWITCH)\npublic class Switch extends WorkflowSystemTask {\n\n    public Switch() {\n        super(TASK_TYPE_SWITCH);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        task.setStatus(TaskModel.Status.COMPLETED);\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/SystemTaskRegistry.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport org.springframework.stereotype.Component;\n\n/**\n * A container class that holds a mapping of system task types {@link\n * com.netflix.conductor.common.metadata.tasks.TaskType} to {@link WorkflowSystemTask} instances.\n */\n@Component\npublic class SystemTaskRegistry {\n\n    public static final String ASYNC_SYSTEM_TASKS_QUALIFIER = \"asyncSystemTasks\";\n\n    private final Map<String, WorkflowSystemTask> registry;\n\n    public SystemTaskRegistry(Set<WorkflowSystemTask> tasks) {\n        this.registry =\n                tasks.stream()\n                        .collect(\n                                Collectors.toMap(\n                                        WorkflowSystemTask::getTaskType, Function.identity()));\n    }\n\n    public WorkflowSystemTask get(String taskType) {\n        return Optional.ofNullable(registry.get(taskType))\n                .orElseThrow(\n                        () ->\n                                new IllegalStateException(\n                                        taskType + \"not found in \" + getClass().getSimpleName()));\n    }\n\n    public boolean isSystemTask(String taskType) {\n        return registry.containsKey(taskType);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/SystemTaskWorker.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.core.LifecycleAwareComponent;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.execution.AsyncSystemTaskExecutor;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.core.utils.SemaphoreUtil;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.service.ExecutionService;\n\n/** The worker that polls and executes an async system task. */\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.system-task-workers.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class SystemTaskWorker extends LifecycleAwareComponent {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SystemTaskWorker.class);\n\n    private final long pollInterval;\n    private final QueueDAO queueDAO;\n\n    ExecutionConfig defaultExecutionConfig;\n    private final AsyncSystemTaskExecutor asyncSystemTaskExecutor;\n    private final ConductorProperties properties;\n    private final ExecutionService executionService;\n\n    ConcurrentHashMap<String, ExecutionConfig> queueExecutionConfigMap = new ConcurrentHashMap<>();\n\n    public SystemTaskWorker(\n            QueueDAO queueDAO,\n            AsyncSystemTaskExecutor asyncSystemTaskExecutor,\n            ConductorProperties properties,\n            ExecutionService executionService) {\n        this.properties = properties;\n        int threadCount = properties.getSystemTaskWorkerThreadCount();\n        this.defaultExecutionConfig = new ExecutionConfig(threadCount, \"system-task-worker-%d\");\n        this.asyncSystemTaskExecutor = asyncSystemTaskExecutor;\n        this.queueDAO = queueDAO;\n        this.pollInterval = properties.getSystemTaskWorkerPollInterval().toMillis();\n        this.executionService = executionService;\n\n        LOGGER.info(\"SystemTaskWorker initialized with {} threads\", threadCount);\n    }\n\n    public void startPolling(WorkflowSystemTask systemTask) {\n        startPolling(systemTask, systemTask.getTaskType());\n    }\n\n    public void startPolling(WorkflowSystemTask systemTask, String queueName) {\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleWithFixedDelay(\n                        () -> this.pollAndExecute(systemTask, queueName),\n                        1000,\n                        pollInterval,\n                        TimeUnit.MILLISECONDS);\n        LOGGER.info(\"Started listening for task: {} in queue: {}\", systemTask, queueName);\n    }\n\n    void pollAndExecute(WorkflowSystemTask systemTask, String queueName) {\n        if (!isRunning()) {\n            LOGGER.debug(\n                    \"{} stopped. Not polling for task: {}\", getClass().getSimpleName(), systemTask);\n            return;\n        }\n\n        ExecutionConfig executionConfig = getExecutionConfig(queueName);\n        SemaphoreUtil semaphoreUtil = executionConfig.getSemaphoreUtil();\n        ExecutorService executorService = executionConfig.getExecutorService();\n        String taskName = QueueUtils.getTaskType(queueName);\n\n        int messagesToAcquire = semaphoreUtil.availableSlots();\n\n        try {\n            if (messagesToAcquire <= 0 || !semaphoreUtil.acquireSlots(messagesToAcquire)) {\n                // no available slots, do not poll\n                Monitors.recordSystemTaskWorkerPollingLimited(queueName);\n                return;\n            }\n\n            LOGGER.debug(\"Polling queue: {} with {} slots acquired\", queueName, messagesToAcquire);\n\n            List<String> polledTaskIds = queueDAO.pop(queueName, messagesToAcquire, 200);\n\n            Monitors.recordTaskPoll(queueName);\n            LOGGER.debug(\"Polling queue:{}, got {} tasks\", queueName, polledTaskIds.size());\n\n            if (polledTaskIds.size() > 0) {\n                // Immediately release unused slots when number of messages acquired is less than\n                // acquired slots\n                if (polledTaskIds.size() < messagesToAcquire) {\n                    semaphoreUtil.completeProcessing(messagesToAcquire - polledTaskIds.size());\n                }\n\n                for (String taskId : polledTaskIds) {\n                    if (StringUtils.isNotBlank(taskId)) {\n                        LOGGER.debug(\n                                \"Task: {} from queue: {} being sent to the workflow executor\",\n                                taskId,\n                                queueName);\n                        Monitors.recordTaskPollCount(queueName, 1);\n\n                        executionService.ackTaskReceived(taskId);\n\n                        CompletableFuture<Void> taskCompletableFuture =\n                                CompletableFuture.runAsync(\n                                        () -> asyncSystemTaskExecutor.execute(systemTask, taskId),\n                                        executorService);\n\n                        // release permit after processing is complete\n                        taskCompletableFuture.whenComplete(\n                                (r, e) -> semaphoreUtil.completeProcessing(1));\n                    } else {\n                        semaphoreUtil.completeProcessing(1);\n                    }\n                }\n            } else {\n                // no task polled, release permit\n                semaphoreUtil.completeProcessing(messagesToAcquire);\n            }\n        } catch (Exception e) {\n            // release the permit if exception is thrown during polling, because the thread would\n            // not be busy\n            semaphoreUtil.completeProcessing(messagesToAcquire);\n            Monitors.recordTaskPollError(taskName, e.getClass().getSimpleName());\n            LOGGER.error(\"Error polling system task in queue:{}\", queueName, e);\n        }\n    }\n\n    @VisibleForTesting\n    ExecutionConfig getExecutionConfig(String taskQueue) {\n        if (!QueueUtils.isIsolatedQueue(taskQueue)) {\n            return this.defaultExecutionConfig;\n        }\n        return queueExecutionConfigMap.computeIfAbsent(\n                taskQueue, __ -> this.createExecutionConfig());\n    }\n\n    private ExecutionConfig createExecutionConfig() {\n        int threadCount = properties.getIsolatedSystemTaskWorkerThreadCount();\n        String threadNameFormat = \"isolated-system-task-worker-%d\";\n        return new ExecutionConfig(threadCount, threadNameFormat);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/SystemTaskWorkerCoordinator.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.Set;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.utils.QueueUtils;\n\nimport static com.netflix.conductor.core.execution.tasks.SystemTaskRegistry.ASYNC_SYSTEM_TASKS_QUALIFIER;\n\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.system-task-workers.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class SystemTaskWorkerCoordinator {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SystemTaskWorkerCoordinator.class);\n\n    private final SystemTaskWorker systemTaskWorker;\n    private final String executionNameSpace;\n    private final Set<WorkflowSystemTask> asyncSystemTasks;\n\n    public SystemTaskWorkerCoordinator(\n            SystemTaskWorker systemTaskWorker,\n            ConductorProperties properties,\n            @Qualifier(ASYNC_SYSTEM_TASKS_QUALIFIER) Set<WorkflowSystemTask> asyncSystemTasks) {\n        this.systemTaskWorker = systemTaskWorker;\n        this.asyncSystemTasks = asyncSystemTasks;\n        this.executionNameSpace = properties.getSystemTaskWorkerExecutionNamespace();\n    }\n\n    @EventListener(ApplicationReadyEvent.class)\n    public void initSystemTaskExecutor() {\n        this.asyncSystemTasks.stream()\n                .filter(this::isFromCoordinatorExecutionNameSpace)\n                .forEach(this.systemTaskWorker::startPolling);\n        LOGGER.info(\n                \"{} initialized with {} async tasks\",\n                SystemTaskWorkerCoordinator.class.getSimpleName(),\n                this.asyncSystemTasks.size());\n    }\n\n    @VisibleForTesting\n    boolean isFromCoordinatorExecutionNameSpace(WorkflowSystemTask systemTask) {\n        String queueExecutionNameSpace = QueueUtils.getExecutionNameSpace(systemTask.getTaskType());\n        return StringUtils.equals(queueExecutionNameSpace, executionNameSpace);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Terminate.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_TERMINATE;\nimport static com.netflix.conductor.common.run.Workflow.WorkflowStatus.*;\n\n/**\n * Task that can terminate a workflow with a given status and modify the workflow's output with a\n * given parameter, it can act as a \"return\" statement for conditions where you simply want to\n * terminate your workflow. For example, if you have a decision where the first condition is met,\n * you want to execute some tasks, otherwise you want to finish your workflow.\n *\n * <pre>\n * ...\n * {\n *  \"tasks\": [\n *      {\n *          \"name\": \"terminate\",\n *          \"taskReferenceName\": \"terminate0\",\n *          \"inputParameters\": {\n *              \"terminationStatus\": \"COMPLETED\",\n *              \"workflowOutput\": \"${task0.output}\"\n *          },\n *          \"type\": \"TERMINATE\",\n *          \"startDelay\": 0,\n *          \"optional\": false\n *      }\n *   ]\n * }\n * ...\n * </pre>\n *\n * This task has some validations on creation and execution, they are: - the \"terminationStatus\"\n * parameter is mandatory and it can only receive the values \"COMPLETED\" or \"FAILED\" - the terminate\n * task cannot be optional\n */\n@Component(TASK_TYPE_TERMINATE)\npublic class Terminate extends WorkflowSystemTask {\n\n    private static final String TERMINATION_STATUS_PARAMETER = \"terminationStatus\";\n    private static final String TERMINATION_REASON_PARAMETER = \"terminationReason\";\n    private static final String TERMINATION_WORKFLOW_OUTPUT = \"workflowOutput\";\n\n    public Terminate() {\n        super(TASK_TYPE_TERMINATE);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        String returnStatus = (String) task.getInputData().get(TERMINATION_STATUS_PARAMETER);\n\n        if (validateInputStatus(returnStatus)) {\n            task.setOutputData(getInputFromParam(task.getInputData()));\n            task.setStatus(TaskModel.Status.COMPLETED);\n            return true;\n        }\n        task.setReasonForIncompletion(\"given termination status is not valid\");\n        task.setStatus(TaskModel.Status.FAILED);\n        return false;\n    }\n\n    public static String getTerminationStatusParameter() {\n        return TERMINATION_STATUS_PARAMETER;\n    }\n\n    public static String getTerminationReasonParameter() {\n        return TERMINATION_REASON_PARAMETER;\n    }\n\n    public static String getTerminationWorkflowOutputParameter() {\n        return TERMINATION_WORKFLOW_OUTPUT;\n    }\n\n    public static Boolean validateInputStatus(String status) {\n        return COMPLETED.name().equals(status)\n                || FAILED.name().equals(status)\n                || TERMINATED.name().equals(status);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private Map<String, Object> getInputFromParam(Map<String, Object> taskInput) {\n        HashMap<String, Object> output = new HashMap<>();\n        if (taskInput.get(TERMINATION_WORKFLOW_OUTPUT) == null) {\n            return output;\n        }\n        if (taskInput.get(TERMINATION_WORKFLOW_OUTPUT) instanceof HashMap) {\n            output.putAll((HashMap<String, Object>) taskInput.get(TERMINATION_WORKFLOW_OUTPUT));\n            return output;\n        }\n        output.put(\"output\", taskInput.get(TERMINATION_WORKFLOW_OUTPUT));\n        return output;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/Wait.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_WAIT;\nimport static com.netflix.conductor.model.TaskModel.Status.*;\n\n@Component(TASK_TYPE_WAIT)\npublic class Wait extends WorkflowSystemTask {\n\n    public static final String DURATION_INPUT = \"duration\";\n    public static final String UNTIL_INPUT = \"until\";\n\n    public Wait() {\n        super(TASK_TYPE_WAIT);\n    }\n\n    @Override\n    public void cancel(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        task.setStatus(TaskModel.Status.CANCELED);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        long timeOut = task.getWaitTimeout();\n        if (timeOut == 0) {\n            return false;\n        }\n        if (System.currentTimeMillis() > timeOut) {\n            task.setStatus(COMPLETED);\n            return true;\n        }\n\n        return false;\n    }\n\n    public boolean isAsync() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/execution/tasks/WorkflowSystemTask.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.Optional;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\npublic abstract class WorkflowSystemTask {\n\n    private final String taskType;\n\n    public WorkflowSystemTask(String taskType) {\n        this.taskType = taskType;\n    }\n\n    /**\n     * Start the task execution.\n     *\n     * <p>Called only once, and first, when the task status is SCHEDULED.\n     *\n     * @param workflow Workflow for which the task is being started\n     * @param task Instance of the Task\n     * @param workflowExecutor Workflow Executor\n     */\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        // Do nothing unless overridden by the task implementation\n    }\n\n    /**\n     * \"Execute\" the task.\n     *\n     * <p>Called after {@link #start(WorkflowModel, TaskModel, WorkflowExecutor)}, if the task\n     * status is not terminal. Can be called more than once.\n     *\n     * @param workflow Workflow for which the task is being started\n     * @param task Instance of the Task\n     * @param workflowExecutor Workflow Executor\n     * @return true, if the execution has changed the task status. return false otherwise.\n     */\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        return false;\n    }\n\n    /**\n     * Cancel task execution\n     *\n     * @param workflow Workflow for which the task is being started\n     * @param task Instance of the Task\n     * @param workflowExecutor Workflow Executor\n     */\n    public void cancel(WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {}\n\n    public Optional<Long> getEvaluationOffset(TaskModel taskModel, long defaultOffset) {\n        return Optional.empty();\n    }\n\n    /**\n     * @return True if the task is supposed to be started asynchronously using internal queues.\n     */\n    public boolean isAsync() {\n        return false;\n    }\n\n    /**\n     * @return True to keep task in 'IN_PROGRESS' state, and 'COMPLETE' later by an external\n     *     message.\n     */\n    public boolean isAsyncComplete(TaskModel task) {\n        if (task.getInputData().containsKey(\"asyncComplete\")) {\n            return Optional.ofNullable(task.getInputData().get(\"asyncComplete\"))\n                    .map(result -> (Boolean) result)\n                    .orElse(false);\n        } else {\n            return Optional.ofNullable(task.getWorkflowTask())\n                    .map(WorkflowTask::isAsyncComplete)\n                    .orElse(false);\n        }\n    }\n\n    /**\n     * @return name of the system task\n     */\n    public String getTaskType() {\n        return taskType;\n    }\n\n    /**\n     * Default to true for retrieving tasks when retrieving workflow data. Some cases (e.g.\n     * subworkflows) might not need the tasks at all, and by setting this to false in that case, you\n     * can get a solid performance gain.\n     *\n     * @return true for retrieving tasks when getting workflow\n     */\n    public boolean isTaskRetrievalRequired() {\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        return taskType;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/index/NoopIndexDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.index;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.dao.IndexDAO;\n\n/**\n * Dummy implementation of {@link IndexDAO} which does nothing. Nothing is ever indexed, and no\n * results are ever returned.\n */\npublic class NoopIndexDAO implements IndexDAO {\n\n    @Override\n    public void setup() {}\n\n    @Override\n    public void indexWorkflow(WorkflowSummary workflowSummary) {}\n\n    @Override\n    public CompletableFuture<Void> asyncIndexWorkflow(WorkflowSummary workflowSummary) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public void indexTask(TaskSummary taskSummary) {}\n\n    @Override\n    public CompletableFuture<Void> asyncIndexTask(TaskSummary taskSummary) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public SearchResult<String> searchWorkflows(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return new SearchResult<>(0, Collections.emptyList());\n    }\n\n    @Override\n    public SearchResult<WorkflowSummary> searchWorkflowSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return new SearchResult<>(0, Collections.emptyList());\n    }\n\n    @Override\n    public SearchResult<String> searchTasks(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return new SearchResult<>(0, Collections.emptyList());\n    }\n\n    @Override\n    public SearchResult<TaskSummary> searchTaskSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return new SearchResult<>(0, Collections.emptyList());\n    }\n\n    @Override\n    public void removeWorkflow(String workflowId) {}\n\n    @Override\n    public CompletableFuture<Void> asyncRemoveWorkflow(String workflowId) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public void updateWorkflow(String workflowInstanceId, String[] keys, Object[] values) {}\n\n    @Override\n    public CompletableFuture<Void> asyncUpdateWorkflow(\n            String workflowInstanceId, String[] keys, Object[] values) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public void removeTask(String workflowId, String taskId) {}\n\n    @Override\n    public CompletableFuture<Void> asyncRemoveTask(String workflowId, String taskId) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public void updateTask(String workflowId, String taskId, String[] keys, Object[] values) {}\n\n    @Override\n    public CompletableFuture<Void> asyncUpdateTask(\n            String workflowId, String taskId, String[] keys, Object[] values) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public String get(String workflowInstanceId, String key) {\n        return null;\n    }\n\n    @Override\n    public void addTaskExecutionLogs(List<TaskExecLog> logs) {}\n\n    @Override\n    public CompletableFuture<Void> asyncAddTaskExecutionLogs(List<TaskExecLog> logs) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public List<TaskExecLog> getTaskExecutionLogs(String taskId) {\n        return Collections.emptyList();\n    }\n\n    @Override\n    public void addEventExecution(EventExecution eventExecution) {}\n\n    @Override\n    public List<EventExecution> getEventExecutions(String event) {\n        return Collections.emptyList();\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncAddEventExecution(EventExecution eventExecution) {\n        return null;\n    }\n\n    @Override\n    public void addMessage(String queue, Message msg) {}\n\n    @Override\n    public CompletableFuture<Void> asyncAddMessage(String queue, Message message) {\n        return CompletableFuture.completedFuture(null);\n    }\n\n    @Override\n    public List<Message> getMessages(String queue) {\n        return Collections.emptyList();\n    }\n\n    @Override\n    public List<String> searchArchivableWorkflows(String indexName, long archiveTtlDays) {\n        return Collections.emptyList();\n    }\n\n    @Override\n    public long getWorkflowCount(String query, String freeText) {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/index/NoopIndexDAOConfiguration.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.index;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.dao.IndexDAO;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnProperty(name = \"conductor.indexing.enabled\", havingValue = \"false\")\npublic class NoopIndexDAOConfiguration {\n\n    @Bean\n    public IndexDAO noopIndexDAO() {\n        return new NoopIndexDAO();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/listener/TaskStatusListener.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.listener;\n\nimport com.netflix.conductor.model.TaskModel;\n\n/**\n * Listener for the Task status change. All methods have default implementation so that\n * Implementation can choose to override a subset of interested Task statuses.\n */\npublic interface TaskStatusListener {\n\n    default void onTaskScheduled(TaskModel task) {}\n\n    default void onTaskInProgress(TaskModel task) {}\n\n    default void onTaskCanceled(TaskModel task) {}\n\n    default void onTaskFailed(TaskModel task) {}\n\n    default void onTaskFailedWithTerminalError(TaskModel task) {}\n\n    default void onTaskCompleted(TaskModel task) {}\n\n    default void onTaskCompletedWithErrors(TaskModel task) {}\n\n    default void onTaskTimedOut(TaskModel task) {}\n\n    default void onTaskSkipped(TaskModel task) {}\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/listener/TaskStatusListenerStub.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.listener;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.model.TaskModel;\n\n/** Stub listener default implementation */\npublic class TaskStatusListenerStub implements TaskStatusListener {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskStatusListenerStub.class);\n\n    @Override\n    public void onTaskScheduled(TaskModel task) {\n        LOGGER.debug(\"Task {} is scheduled\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskCanceled(TaskModel task) {\n        LOGGER.debug(\"Task {} is canceled\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskCompleted(TaskModel task) {\n        LOGGER.debug(\"Task {} is completed\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskCompletedWithErrors(TaskModel task) {\n        LOGGER.debug(\"Task {} is completed with errors\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskFailed(TaskModel task) {\n        LOGGER.debug(\"Task {} is failed\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskFailedWithTerminalError(TaskModel task) {\n        LOGGER.debug(\"Task {} is failed with terminal error\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskInProgress(TaskModel task) {\n        LOGGER.debug(\"Task {} is in-progress\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskSkipped(TaskModel task) {\n        LOGGER.debug(\"Task {} is skipped\", task.getTaskId());\n    }\n\n    @Override\n    public void onTaskTimedOut(TaskModel task) {\n        LOGGER.debug(\"Task {} is timed out\", task.getTaskId());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/listener/WorkflowStatusListener.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.listener;\n\nimport com.netflix.conductor.model.WorkflowModel;\n\n/** Listener for the completed and terminated workflows */\npublic interface WorkflowStatusListener {\n\n    default void onWorkflowCompletedIfEnabled(WorkflowModel workflow) {\n        if (workflow.getWorkflowDefinition().isWorkflowStatusListenerEnabled()) {\n            onWorkflowCompleted(workflow);\n        }\n    }\n\n    default void onWorkflowTerminatedIfEnabled(WorkflowModel workflow) {\n        if (workflow.getWorkflowDefinition().isWorkflowStatusListenerEnabled()) {\n            onWorkflowTerminated(workflow);\n        }\n    }\n\n    default void onWorkflowFinalizedIfEnabled(WorkflowModel workflow) {\n        if (workflow.getWorkflowDefinition().isWorkflowStatusListenerEnabled()) {\n            onWorkflowFinalized(workflow);\n        }\n    }\n\n    void onWorkflowCompleted(WorkflowModel workflow);\n\n    void onWorkflowTerminated(WorkflowModel workflow);\n\n    default void onWorkflowFinalized(WorkflowModel workflow) {}\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/listener/WorkflowStatusListenerStub.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.listener;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.model.WorkflowModel;\n\n/** Stub listener default implementation */\npublic class WorkflowStatusListenerStub implements WorkflowStatusListener {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowStatusListenerStub.class);\n\n    @Override\n    public void onWorkflowCompleted(WorkflowModel workflow) {\n        LOGGER.debug(\"Workflow {} is completed\", workflow.getWorkflowId());\n    }\n\n    @Override\n    public void onWorkflowTerminated(WorkflowModel workflow) {\n        LOGGER.debug(\"Workflow {} is terminated\", workflow.getWorkflowId());\n    }\n\n    @Override\n    public void onWorkflowFinalized(WorkflowModel workflow) {\n        LOGGER.debug(\"Workflow {} is finalized\", workflow.getWorkflowId());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/metadata/MetadataMapperService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.metadata;\n\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.WorkflowContext;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * Populates metadata definitions within workflow objects. Benefits of loading and populating\n * metadata definitions upfront could be:\n *\n * <ul>\n *   <li>Immutable definitions within a workflow execution with the added benefit of guaranteeing\n *       consistency at runtime.\n *   <li>Stress is reduced on the storage layer\n * </ul>\n */\n@Component\npublic class MetadataMapperService {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(MetadataMapperService.class);\n    private final MetadataDAO metadataDAO;\n\n    public MetadataMapperService(MetadataDAO metadataDAO) {\n        this.metadataDAO = metadataDAO;\n    }\n\n    public WorkflowDef lookupForWorkflowDefinition(String name, Integer version) {\n        Optional<WorkflowDef> potentialDef =\n                version == null\n                        ? lookupLatestWorkflowDefinition(name)\n                        : lookupWorkflowDefinition(name, version);\n\n        // Check if the workflow definition is valid\n        return potentialDef.orElseThrow(\n                () -> {\n                    LOGGER.error(\n                            \"There is no workflow defined with name {} and version {}\",\n                            name,\n                            version);\n                    return new NotFoundException(\n                            \"No such workflow defined. name=%s, version=%s\", name, version);\n                });\n    }\n\n    @VisibleForTesting\n    Optional<WorkflowDef> lookupWorkflowDefinition(String workflowName, int workflowVersion) {\n        Utils.checkArgument(\n                StringUtils.isNotBlank(workflowName),\n                \"Workflow name must be specified when searching for a definition\");\n        return metadataDAO.getWorkflowDef(workflowName, workflowVersion);\n    }\n\n    @VisibleForTesting\n    Optional<WorkflowDef> lookupLatestWorkflowDefinition(String workflowName) {\n        Utils.checkArgument(\n                StringUtils.isNotBlank(workflowName),\n                \"Workflow name must be specified when searching for a definition\");\n        return metadataDAO.getLatestWorkflowDef(workflowName);\n    }\n\n    public WorkflowModel populateWorkflowWithDefinitions(WorkflowModel workflow) {\n        Utils.checkNotNull(workflow, \"workflow cannot be null\");\n        WorkflowDef workflowDefinition =\n                Optional.ofNullable(workflow.getWorkflowDefinition())\n                        .orElseGet(\n                                () -> {\n                                    WorkflowDef wd =\n                                            lookupForWorkflowDefinition(\n                                                    workflow.getWorkflowName(),\n                                                    workflow.getWorkflowVersion());\n                                    workflow.setWorkflowDefinition(wd);\n                                    return wd;\n                                });\n\n        workflowDefinition.collectTasks().forEach(this::populateWorkflowTaskWithDefinition);\n        checkNotEmptyDefinitions(workflowDefinition);\n\n        return workflow;\n    }\n\n    public WorkflowDef populateTaskDefinitions(WorkflowDef workflowDefinition) {\n        Utils.checkNotNull(workflowDefinition, \"workflowDefinition cannot be null\");\n        workflowDefinition.collectTasks().forEach(this::populateWorkflowTaskWithDefinition);\n        checkNotEmptyDefinitions(workflowDefinition);\n        return workflowDefinition;\n    }\n\n    private void populateWorkflowTaskWithDefinition(WorkflowTask workflowTask) {\n        Utils.checkNotNull(workflowTask, \"WorkflowTask cannot be null\");\n        if (shouldPopulateTaskDefinition(workflowTask)) {\n            workflowTask.setTaskDefinition(metadataDAO.getTaskDef(workflowTask.getName()));\n            if (workflowTask.getTaskDefinition() == null\n                    && workflowTask.getType().equals(TaskType.SIMPLE.name())) {\n                // ad-hoc task def\n                workflowTask.setTaskDefinition(new TaskDef(workflowTask.getName()));\n            }\n        }\n        if (workflowTask.getType().equals(TaskType.SUB_WORKFLOW.name())) {\n            populateVersionForSubWorkflow(workflowTask);\n        }\n    }\n\n    private void populateVersionForSubWorkflow(WorkflowTask workflowTask) {\n        Utils.checkNotNull(workflowTask, \"WorkflowTask cannot be null\");\n        SubWorkflowParams subworkflowParams = workflowTask.getSubWorkflowParam();\n        if (subworkflowParams.getVersion() == null) {\n            String subWorkflowName = subworkflowParams.getName();\n            Integer subWorkflowVersion =\n                    metadataDAO\n                            .getLatestWorkflowDef(subWorkflowName)\n                            .map(WorkflowDef::getVersion)\n                            .orElseThrow(\n                                    () -> {\n                                        String reason =\n                                                String.format(\n                                                        \"The Task %s defined as a sub-workflow has no workflow definition available \",\n                                                        subWorkflowName);\n                                        LOGGER.error(reason);\n                                        return new TerminateWorkflowException(reason);\n                                    });\n            subworkflowParams.setVersion(subWorkflowVersion);\n        }\n    }\n\n    private void checkNotEmptyDefinitions(WorkflowDef workflowDefinition) {\n        Utils.checkNotNull(workflowDefinition, \"WorkflowDefinition cannot be null\");\n\n        // Obtain the names of the tasks with missing definitions\n        Set<String> missingTaskDefinitionNames =\n                workflowDefinition.collectTasks().stream()\n                        .filter(\n                                workflowTask ->\n                                        workflowTask.getType().equals(TaskType.SIMPLE.name()))\n                        .filter(this::shouldPopulateTaskDefinition)\n                        .map(WorkflowTask::getName)\n                        .collect(Collectors.toSet());\n\n        if (!missingTaskDefinitionNames.isEmpty()) {\n            LOGGER.error(\n                    \"Cannot find the task definitions for the following tasks used in workflow: {}\",\n                    missingTaskDefinitionNames);\n            Monitors.recordWorkflowStartError(\n                    workflowDefinition.getName(), WorkflowContext.get().getClientApp());\n            throw new IllegalArgumentException(\n                    \"Cannot find the task definitions for the following tasks used in workflow: \"\n                            + missingTaskDefinitionNames);\n        }\n    }\n\n    public TaskModel populateTaskWithDefinition(TaskModel task) {\n        Utils.checkNotNull(task, \"Task cannot be null\");\n        populateWorkflowTaskWithDefinition(task.getWorkflowTask());\n        return task;\n    }\n\n    @VisibleForTesting\n    boolean shouldPopulateTaskDefinition(WorkflowTask workflowTask) {\n        Utils.checkNotNull(workflowTask, \"WorkflowTask cannot be null\");\n        Utils.checkNotNull(workflowTask.getType(), \"WorkflowTask type cannot be null\");\n        return workflowTask.getTaskDefinition() == null\n                && StringUtils.isNotBlank(workflowTask.getName());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/operation/StartWorkflowOperation.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.operation;\n\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.WorkflowContext;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.event.WorkflowCreationEvent;\nimport com.netflix.conductor.core.event.WorkflowEvaluationEvent;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.metadata.MetadataMapperService;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.conductor.service.ExecutionLockService;\n\n@Component\npublic class StartWorkflowOperation implements WorkflowOperation<StartWorkflowInput, String> {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(StartWorkflowOperation.class);\n\n    private final MetadataMapperService metadataMapperService;\n    private final IDGenerator idGenerator;\n    private final ParametersUtils parametersUtils;\n    private final ExecutionDAOFacade executionDAOFacade;\n    private final ExecutionLockService executionLockService;\n    private final ApplicationEventPublisher eventPublisher;\n\n    public StartWorkflowOperation(\n            MetadataMapperService metadataMapperService,\n            IDGenerator idGenerator,\n            ParametersUtils parametersUtils,\n            ExecutionDAOFacade executionDAOFacade,\n            ExecutionLockService executionLockService,\n            ApplicationEventPublisher eventPublisher) {\n        this.metadataMapperService = metadataMapperService;\n        this.idGenerator = idGenerator;\n        this.parametersUtils = parametersUtils;\n        this.executionDAOFacade = executionDAOFacade;\n        this.executionLockService = executionLockService;\n        this.eventPublisher = eventPublisher;\n    }\n\n    @Override\n    public String execute(StartWorkflowInput input) {\n        return startWorkflow(input);\n    }\n\n    @EventListener(WorkflowCreationEvent.class)\n    public void handleWorkflowCreationEvent(WorkflowCreationEvent workflowCreationEvent) {\n        startWorkflow(workflowCreationEvent.getStartWorkflowInput());\n    }\n\n    private String startWorkflow(StartWorkflowInput input) {\n        WorkflowDef workflowDefinition;\n\n        if (input.getWorkflowDefinition() == null) {\n            workflowDefinition =\n                    metadataMapperService.lookupForWorkflowDefinition(\n                            input.getName(), input.getVersion());\n        } else {\n            workflowDefinition = input.getWorkflowDefinition();\n        }\n\n        workflowDefinition = metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        // perform validations\n        Map<String, Object> workflowInput = input.getWorkflowInput();\n        String externalInputPayloadStoragePath = input.getExternalInputPayloadStoragePath();\n        validateWorkflow(workflowDefinition, workflowInput, externalInputPayloadStoragePath);\n\n        // Generate ID if it's not present\n        String workflowId =\n                Optional.ofNullable(input.getWorkflowId()).orElseGet(idGenerator::generate);\n\n        // Persist the Workflow\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n        workflow.setCorrelationId(input.getCorrelationId());\n        workflow.setPriority(input.getPriority() == null ? 0 : input.getPriority());\n        workflow.setWorkflowDefinition(workflowDefinition);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setParentWorkflowId(input.getParentWorkflowId());\n        workflow.setParentWorkflowTaskId(input.getParentWorkflowTaskId());\n        workflow.setOwnerApp(WorkflowContext.get().getClientApp());\n        workflow.setCreateTime(System.currentTimeMillis());\n        workflow.setUpdatedBy(null);\n        workflow.setUpdatedTime(null);\n        workflow.setEvent(input.getEvent());\n        workflow.setTaskToDomain(input.getTaskToDomain());\n        workflow.setVariables(workflowDefinition.getVariables());\n\n        if (workflowInput != null && !workflowInput.isEmpty()) {\n            Map<String, Object> parsedInput =\n                    parametersUtils.getWorkflowInput(workflowDefinition, workflowInput);\n            workflow.setInput(parsedInput);\n        } else {\n            workflow.setExternalInputPayloadStoragePath(externalInputPayloadStoragePath);\n        }\n\n        try {\n            createAndEvaluate(workflow);\n            Monitors.recordWorkflowStartSuccess(\n                    workflow.getWorkflowName(),\n                    String.valueOf(workflow.getWorkflowVersion()),\n                    workflow.getOwnerApp());\n            return workflowId;\n        } catch (Exception e) {\n            Monitors.recordWorkflowStartError(\n                    workflowDefinition.getName(), WorkflowContext.get().getClientApp());\n            LOGGER.error(\"Unable to start workflow: {}\", workflowDefinition.getName(), e);\n\n            // It's possible the remove workflow call hits an exception as well, in that case we\n            // want to log both errors to help diagnosis.\n            try {\n                executionDAOFacade.removeWorkflow(workflowId, false);\n            } catch (Exception rwe) {\n                LOGGER.error(\"Could not remove the workflowId: \" + workflowId, rwe);\n            }\n            throw e;\n        }\n    }\n\n    /*\n     * Acquire and hold the lock till the workflow creation action is completed (in primary and secondary datastores).\n     * This is to ensure that workflow creation action precedes any other action on a given workflow.\n     */\n    private void createAndEvaluate(WorkflowModel workflow) {\n        if (!executionLockService.acquireLock(workflow.getWorkflowId())) {\n            throw new TransientException(\"Error acquiring lock when creating workflow: {}\");\n        }\n        try {\n            executionDAOFacade.createWorkflow(workflow);\n            LOGGER.debug(\n                    \"A new instance of workflow: {} created with id: {}\",\n                    workflow.getWorkflowName(),\n                    workflow.getWorkflowId());\n            executionDAOFacade.populateWorkflowAndTaskPayloadData(workflow);\n            eventPublisher.publishEvent(new WorkflowEvaluationEvent(workflow));\n        } finally {\n            executionLockService.releaseLock(workflow.getWorkflowId());\n        }\n    }\n\n    /**\n     * Performs validations for starting a workflow\n     *\n     * @throws IllegalArgumentException if the validation fails.\n     */\n    private void validateWorkflow(\n            WorkflowDef workflowDef,\n            Map<String, Object> workflowInput,\n            String externalStoragePath) {\n        // Check if the input to the workflow is not null\n        if (workflowInput == null && StringUtils.isBlank(externalStoragePath)) {\n            LOGGER.error(\"The input for the workflow '{}' cannot be NULL\", workflowDef.getName());\n            Monitors.recordWorkflowStartError(\n                    workflowDef.getName(), WorkflowContext.get().getClientApp());\n\n            throw new IllegalArgumentException(\"NULL input passed when starting workflow\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/operation/WorkflowOperation.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.operation;\n\npublic interface WorkflowOperation<T, R> {\n\n    R execute(T input);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/reconciliation/WorkflowReconciler.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.reconciliation;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.LifecycleAwareComponent;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport static com.netflix.conductor.core.utils.Utils.DECIDER_QUEUE;\n\n/**\n * Periodically polls all running workflows in the system and evaluates them for timeouts and/or\n * maintain consistency.\n */\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.workflow-reconciler.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class WorkflowReconciler extends LifecycleAwareComponent {\n\n    private final WorkflowSweeper workflowSweeper;\n    private final QueueDAO queueDAO;\n    private final int sweeperThreadCount;\n    private final int sweeperWorkflowPollTimeout;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowReconciler.class);\n\n    public WorkflowReconciler(\n            WorkflowSweeper workflowSweeper, QueueDAO queueDAO, ConductorProperties properties) {\n        this.workflowSweeper = workflowSweeper;\n        this.queueDAO = queueDAO;\n        this.sweeperThreadCount = properties.getSweeperThreadCount();\n        this.sweeperWorkflowPollTimeout =\n                (int) properties.getSweeperWorkflowPollTimeout().toMillis();\n        LOGGER.info(\n                \"WorkflowReconciler initialized with {} sweeper threads\",\n                properties.getSweeperThreadCount());\n    }\n\n    @Scheduled(\n            fixedDelayString = \"${conductor.sweep-frequency.millis:500}\",\n            initialDelayString = \"${conductor.sweep-frequency.millis:500}\")\n    public void pollAndSweep() {\n        try {\n            if (!isRunning()) {\n                LOGGER.debug(\"Component stopped, skip workflow sweep\");\n            } else {\n                List<String> workflowIds =\n                        queueDAO.pop(DECIDER_QUEUE, sweeperThreadCount, sweeperWorkflowPollTimeout);\n                if (workflowIds != null) {\n                    // wait for all workflow ids to be \"swept\"\n                    CompletableFuture.allOf(\n                                    workflowIds.stream()\n                                            .map(workflowSweeper::sweepAsync)\n                                            .toArray(CompletableFuture[]::new))\n                            .get();\n                    LOGGER.debug(\n                            \"Sweeper processed {} from the decider queue\",\n                            String.join(\",\", workflowIds));\n                }\n                // NOTE: Disabling the sweeper implicitly disables this metric.\n                recordQueueDepth();\n            }\n        } catch (Exception e) {\n            Monitors.error(WorkflowReconciler.class.getSimpleName(), \"poll\");\n            LOGGER.error(\"Error when polling for workflows\", e);\n            if (e instanceof InterruptedException) {\n                // Restore interrupted state...\n                Thread.currentThread().interrupt();\n            }\n        }\n    }\n\n    private void recordQueueDepth() {\n        int currentQueueSize = queueDAO.getSize(DECIDER_QUEUE);\n        Monitors.recordGauge(DECIDER_QUEUE, currentQueueSize);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/reconciliation/WorkflowRepairService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.reconciliation;\n\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Predicate;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.dao.ExecutionDAO;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/**\n * A helper service that tries to keep ExecutionDAO and QueueDAO in sync, based on the task or\n * workflow state.\n *\n * <p>This service expects that the underlying Queueing layer implements {@link\n * QueueDAO#containsMessage(String, String)} method. This can be controlled with <code>\n * conductor.workflow-repair-service.enabled</code> property.\n */\n@Service\n@ConditionalOnProperty(name = \"conductor.workflow-repair-service.enabled\", havingValue = \"true\")\npublic class WorkflowRepairService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowRepairService.class);\n    private final ExecutionDAO executionDAO;\n    private final QueueDAO queueDAO;\n    private final ConductorProperties properties;\n    private SystemTaskRegistry systemTaskRegistry;\n\n    /*\n    For system task -> Verify the task isAsync() and not isAsyncComplete() or isAsyncComplete() in SCHEDULED state,\n    and in SCHEDULED or IN_PROGRESS state. (Example: SUB_WORKFLOW tasks in SCHEDULED state)\n    For simple task -> Verify the task is in SCHEDULED state.\n    */\n    private final Predicate<TaskModel> isTaskRepairable =\n            task -> {\n                if (systemTaskRegistry.isSystemTask(task.getTaskType())) { // If system task\n                    WorkflowSystemTask workflowSystemTask =\n                            systemTaskRegistry.get(task.getTaskType());\n                    return workflowSystemTask.isAsync()\n                            && (!workflowSystemTask.isAsyncComplete(task)\n                                    || (workflowSystemTask.isAsyncComplete(task)\n                                            && task.getStatus() == TaskModel.Status.SCHEDULED))\n                            && (task.getStatus() == TaskModel.Status.IN_PROGRESS\n                                    || task.getStatus() == TaskModel.Status.SCHEDULED);\n                } else { // Else if simple task\n                    return task.getStatus() == TaskModel.Status.SCHEDULED;\n                }\n            };\n\n    public WorkflowRepairService(\n            ExecutionDAO executionDAO,\n            QueueDAO queueDAO,\n            ConductorProperties properties,\n            SystemTaskRegistry systemTaskRegistry) {\n        this.executionDAO = executionDAO;\n        this.queueDAO = queueDAO;\n        this.properties = properties;\n        this.systemTaskRegistry = systemTaskRegistry;\n        LOGGER.info(\"WorkflowRepairService Initialized\");\n    }\n\n    /**\n     * Verify and repair if the workflowId exists in deciderQueue, and then if each scheduled task\n     * has relevant message in the queue.\n     */\n    public boolean verifyAndRepairWorkflow(String workflowId, boolean includeTasks) {\n        WorkflowModel workflow = executionDAO.getWorkflow(workflowId, includeTasks);\n        AtomicBoolean repaired = new AtomicBoolean(false);\n        repaired.set(verifyAndRepairDeciderQueue(workflow));\n        if (includeTasks) {\n            workflow.getTasks().forEach(task -> repaired.set(verifyAndRepairTask(task)));\n        }\n        return repaired.get();\n    }\n\n    /** Verify and repair tasks in a workflow. */\n    public void verifyAndRepairWorkflowTasks(String workflowId) {\n        WorkflowModel workflow =\n                Optional.ofNullable(executionDAO.getWorkflow(workflowId, true))\n                        .orElseThrow(\n                                () ->\n                                        new NotFoundException(\n                                                \"Could not find workflow: \" + workflowId));\n        verifyAndRepairWorkflowTasks(workflow);\n    }\n\n    /** Verify and repair tasks in a workflow. */\n    public void verifyAndRepairWorkflowTasks(WorkflowModel workflow) {\n        workflow.getTasks().forEach(this::verifyAndRepairTask);\n        // repair the parent workflow if needed\n        verifyAndRepairWorkflow(workflow.getParentWorkflowId());\n    }\n\n    /**\n     * Verify and fix if Workflow decider queue contains this workflowId.\n     *\n     * @return true - if the workflow was queued for repair\n     */\n    private boolean verifyAndRepairDeciderQueue(WorkflowModel workflow) {\n        if (!workflow.getStatus().isTerminal()) {\n            return verifyAndRepairWorkflow(workflow.getWorkflowId());\n        }\n        return false;\n    }\n\n    /**\n     * Verify if ExecutionDAO and QueueDAO agree for the provided task.\n     *\n     * @param task the task to be repaired\n     * @return true - if the task was queued for repair\n     */\n    @VisibleForTesting\n    boolean verifyAndRepairTask(TaskModel task) {\n        if (isTaskRepairable.test(task)) {\n            // Ensure QueueDAO contains this taskId\n            String taskQueueName = QueueUtils.getQueueName(task);\n            if (!queueDAO.containsMessage(taskQueueName, task.getTaskId())) {\n                queueDAO.push(taskQueueName, task.getTaskId(), task.getCallbackAfterSeconds());\n                LOGGER.info(\n                        \"Task {} in workflow {} re-queued for repairs\",\n                        task.getTaskId(),\n                        task.getWorkflowInstanceId());\n                Monitors.recordQueueMessageRepushFromRepairService(task.getTaskDefName());\n                return true;\n            }\n        } else if (task.getTaskType().equals(TaskType.TASK_TYPE_SUB_WORKFLOW)\n                && task.getStatus() == TaskModel.Status.IN_PROGRESS) {\n            WorkflowModel subWorkflow = executionDAO.getWorkflow(task.getSubWorkflowId(), false);\n            if (subWorkflow.getStatus().isTerminal()) {\n                LOGGER.info(\n                        \"Repairing sub workflow task {} for sub workflow {} in workflow {}\",\n                        task.getTaskId(),\n                        task.getSubWorkflowId(),\n                        task.getWorkflowInstanceId());\n                repairSubWorkflowTask(task, subWorkflow);\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private boolean verifyAndRepairWorkflow(String workflowId) {\n        if (StringUtils.isNotEmpty(workflowId)) {\n            String queueName = Utils.DECIDER_QUEUE;\n            if (!queueDAO.containsMessage(queueName, workflowId)) {\n                queueDAO.push(\n                        queueName, workflowId, properties.getWorkflowOffsetTimeout().getSeconds());\n                LOGGER.info(\"Workflow {} re-queued for repairs\", workflowId);\n                Monitors.recordQueueMessageRepushFromRepairService(queueName);\n                return true;\n            }\n            return false;\n        }\n        return false;\n    }\n\n    private void repairSubWorkflowTask(TaskModel task, WorkflowModel subWorkflow) {\n        switch (subWorkflow.getStatus()) {\n            case COMPLETED:\n                task.setStatus(TaskModel.Status.COMPLETED);\n                break;\n            case FAILED:\n                task.setStatus(TaskModel.Status.FAILED);\n                break;\n            case TERMINATED:\n                task.setStatus(TaskModel.Status.CANCELED);\n                break;\n            case TIMED_OUT:\n                task.setStatus(TaskModel.Status.TIMED_OUT);\n                break;\n        }\n        task.addOutput(subWorkflow.getOutput());\n        executionDAO.updateTask(task);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/reconciliation/WorkflowSweeper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.reconciliation;\n\nimport java.time.Instant;\nimport java.util.Optional;\nimport java.util.Random;\nimport java.util.concurrent.CompletableFuture;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.core.WorkflowContext;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.TaskModel.Status;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.core.config.SchedulerConfiguration.SWEEPER_EXECUTOR_NAME;\nimport static com.netflix.conductor.core.utils.Utils.DECIDER_QUEUE;\n\n@Component\npublic class WorkflowSweeper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowSweeper.class);\n\n    private final ConductorProperties properties;\n    private final WorkflowExecutor workflowExecutor;\n    private final WorkflowRepairService workflowRepairService;\n    private final QueueDAO queueDAO;\n    private final ExecutionDAOFacade executionDAOFacade;\n\n    private static final String CLASS_NAME = WorkflowSweeper.class.getSimpleName();\n\n    @Autowired\n    public WorkflowSweeper(\n            WorkflowExecutor workflowExecutor,\n            Optional<WorkflowRepairService> workflowRepairService,\n            ConductorProperties properties,\n            QueueDAO queueDAO,\n            ExecutionDAOFacade executionDAOFacade) {\n        this.properties = properties;\n        this.queueDAO = queueDAO;\n        this.workflowExecutor = workflowExecutor;\n        this.executionDAOFacade = executionDAOFacade;\n        this.workflowRepairService = workflowRepairService.orElse(null);\n        LOGGER.info(\"WorkflowSweeper initialized.\");\n    }\n\n    @Async(SWEEPER_EXECUTOR_NAME)\n    public CompletableFuture<Void> sweepAsync(String workflowId) {\n        sweep(workflowId);\n        return CompletableFuture.completedFuture(null);\n    }\n\n    public void sweep(String workflowId) {\n        WorkflowModel workflow = null;\n        try {\n            WorkflowContext workflowContext = new WorkflowContext(properties.getAppId());\n            WorkflowContext.set(workflowContext);\n            LOGGER.debug(\"Running sweeper for workflow {}\", workflowId);\n\n            workflow = executionDAOFacade.getWorkflowModel(workflowId, true);\n\n            if (workflowRepairService != null) {\n                // Verify and repair tasks in the workflow.\n                workflowRepairService.verifyAndRepairWorkflowTasks(workflow);\n            }\n\n            workflow = workflowExecutor.decideWithLock(workflow);\n            if (workflow != null && workflow.getStatus().isTerminal()) {\n                queueDAO.remove(DECIDER_QUEUE, workflowId);\n                return;\n            }\n\n        } catch (NotFoundException nfe) {\n            queueDAO.remove(DECIDER_QUEUE, workflowId);\n            LOGGER.info(\n                    \"Workflow NOT found for id:{}. Removed it from decider queue\", workflowId, nfe);\n            return;\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"sweep\");\n            LOGGER.error(\"Error running sweep for \" + workflowId, e);\n        }\n        long workflowOffsetTimeout =\n                workflowOffsetWithJitter(properties.getWorkflowOffsetTimeout().getSeconds());\n        if (workflow != null) {\n            long startTime = Instant.now().toEpochMilli();\n            unack(workflow, workflowOffsetTimeout);\n            long endTime = Instant.now().toEpochMilli();\n            Monitors.recordUnackTime(workflow.getWorkflowName(), endTime - startTime);\n        } else {\n            LOGGER.warn(\n                    \"Workflow with {} id can not be found. Attempting to unack using the id\",\n                    workflowId);\n            queueDAO.setUnackTimeout(DECIDER_QUEUE, workflowId, workflowOffsetTimeout * 1000);\n        }\n    }\n\n    @VisibleForTesting\n    void unack(WorkflowModel workflowModel, long workflowOffsetTimeout) {\n        long postponeDurationSeconds = 0;\n        for (TaskModel taskModel : workflowModel.getTasks()) {\n            if (taskModel.getStatus() == Status.IN_PROGRESS) {\n                if (taskModel.getTaskType().equals(TaskType.TASK_TYPE_WAIT)) {\n                    if (taskModel.getWaitTimeout() == 0) {\n                        postponeDurationSeconds = workflowOffsetTimeout;\n                    } else {\n                        long deltaInSeconds =\n                                (taskModel.getWaitTimeout() - System.currentTimeMillis()) / 1000;\n                        postponeDurationSeconds = (deltaInSeconds > 0) ? deltaInSeconds : 0;\n                    }\n                } else if (taskModel.getTaskType().equals(TaskType.TASK_TYPE_HUMAN)) {\n                    postponeDurationSeconds = workflowOffsetTimeout;\n                } else {\n                    postponeDurationSeconds =\n                            (taskModel.getResponseTimeoutSeconds() != 0)\n                                    ? taskModel.getResponseTimeoutSeconds() + 1\n                                    : workflowOffsetTimeout;\n                }\n                break;\n            } else if (taskModel.getStatus() == Status.SCHEDULED) {\n                Optional<TaskDef> taskDefinition = taskModel.getTaskDefinition();\n                if (taskDefinition.isPresent()) {\n                    TaskDef taskDef = taskDefinition.get();\n                    if (taskDef.getPollTimeoutSeconds() != null\n                            && taskDef.getPollTimeoutSeconds() != 0) {\n                        postponeDurationSeconds = taskDef.getPollTimeoutSeconds() + 1;\n                    } else {\n                        postponeDurationSeconds =\n                                (workflowModel.getWorkflowDefinition().getTimeoutSeconds() != 0)\n                                        ? workflowModel.getWorkflowDefinition().getTimeoutSeconds()\n                                                + 1\n                                        : workflowOffsetTimeout;\n                    }\n                } else {\n                    postponeDurationSeconds =\n                            (workflowModel.getWorkflowDefinition().getTimeoutSeconds() != 0)\n                                    ? workflowModel.getWorkflowDefinition().getTimeoutSeconds() + 1\n                                    : workflowOffsetTimeout;\n                }\n                break;\n            }\n        }\n        queueDAO.setUnackTimeout(\n                DECIDER_QUEUE, workflowModel.getWorkflowId(), postponeDurationSeconds * 1000);\n    }\n\n    /**\n     * jitter will be +- (1/3) workflowOffsetTimeout for example, if workflowOffsetTimeout is 45\n     * seconds, this function returns values between [30-60] seconds\n     *\n     * @param workflowOffsetTimeout\n     * @return\n     */\n    @VisibleForTesting\n    long workflowOffsetWithJitter(long workflowOffsetTimeout) {\n        long range = workflowOffsetTimeout / 3;\n        long jitter = new Random().nextInt((int) (2 * range + 1)) - range;\n        return workflowOffsetTimeout + jitter;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/storage/DummyPayloadStorage.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.storage;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.util.UUID;\n\nimport org.apache.commons.io.IOUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/**\n * A dummy implementation of {@link ExternalPayloadStorage} used when no external payload is\n * configured\n */\npublic class DummyPayloadStorage implements ExternalPayloadStorage {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DummyPayloadStorage.class);\n\n    private ObjectMapper objectMapper;\n    private File payloadDir;\n\n    public DummyPayloadStorage() {\n        try {\n            this.objectMapper = new ObjectMapper();\n            this.payloadDir = Files.createTempDirectory(\"payloads\").toFile();\n            LOGGER.info(\n                    \"{} initialized in directory: {}\",\n                    this.getClass().getSimpleName(),\n                    payloadDir.getAbsolutePath());\n        } catch (IOException ioException) {\n            LOGGER.error(\n                    \"Exception encountered while creating payloads directory : {}\",\n                    ioException.getMessage());\n        }\n    }\n\n    @Override\n    public ExternalStorageLocation getLocation(\n            Operation operation, PayloadType payloadType, String path) {\n        ExternalStorageLocation location = new ExternalStorageLocation();\n        location.setPath(path + UUID.randomUUID() + \".json\");\n        return location;\n    }\n\n    @Override\n    public void upload(String path, InputStream payload, long payloadSize) {\n        File file = new File(payloadDir, path);\n        String filePath = file.getAbsolutePath();\n        try {\n            if (!file.exists() && file.createNewFile()) {\n                LOGGER.debug(\"Created file: {}\", filePath);\n            }\n            IOUtils.copy(payload, new FileOutputStream(file));\n            LOGGER.debug(\"Written to {}\", filePath);\n        } catch (IOException e) {\n            // just handle this exception here and return empty map so that test will fail in case\n            // this exception is thrown\n            LOGGER.error(\"Error writing to {}\", filePath);\n        } finally {\n            try {\n                if (payload != null) {\n                    payload.close();\n                }\n            } catch (IOException e) {\n                LOGGER.warn(\"Unable to close input stream when writing to file\");\n            }\n        }\n    }\n\n    @Override\n    public InputStream download(String path) {\n        try {\n            LOGGER.debug(\"Reading from {}\", path);\n            return new FileInputStream(new File(payloadDir, path));\n        } catch (IOException e) {\n            LOGGER.error(\"Error reading {}\", path, e);\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/sync/Lock.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.sync;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Interface implemented by a distributed lock client.\n *\n * <p>A typical usage:\n *\n * <pre>\n *   if (acquireLock(workflowId, 5, TimeUnit.MILLISECONDS)) {\n *      [load and execute workflow....]\n *      ExecutionDAO.updateWorkflow(workflow);  //use optimistic locking\n *   } finally {\n *     releaseLock(workflowId)\n *   }\n * </pre>\n */\npublic interface Lock {\n\n    /**\n     * Acquires a re-entrant lock on lockId, blocks indefinitely on lockId until it succeeds\n     *\n     * @param lockId resource to lock on\n     */\n    void acquireLock(String lockId);\n\n    /**\n     * Acquires a re-entrant lock on lockId, blocks for timeToTry duration before giving up\n     *\n     * @param lockId resource to lock on\n     * @param timeToTry blocks up to timeToTry duration in attempt to acquire the lock\n     * @param unit time unit\n     * @return true, if successfully acquired\n     */\n    boolean acquireLock(String lockId, long timeToTry, TimeUnit unit);\n\n    /**\n     * Acquires a re-entrant lock on lockId with provided leaseTime duration. Blocks for timeToTry\n     * duration before giving up\n     *\n     * @param lockId resource to lock on\n     * @param timeToTry blocks up to timeToTry duration in attempt to acquire the lock\n     * @param leaseTime Lock lease expiration duration.\n     * @param unit time unit\n     * @return true, if successfully acquired\n     */\n    boolean acquireLock(String lockId, long timeToTry, long leaseTime, TimeUnit unit);\n\n    /**\n     * Release a previously acquired lock\n     *\n     * @param lockId resource to lock on\n     */\n    void releaseLock(String lockId);\n\n    /**\n     * Explicitly cleanup lock resources, if releasing it wouldn't do so.\n     *\n     * @param lockId resource to lock on\n     */\n    void deleteLock(String lockId);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/sync/local/LocalOnlyLock.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.sync.local;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.core.sync.Lock;\n\nimport com.github.benmanes.caffeine.cache.CacheLoader;\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport com.github.benmanes.caffeine.cache.LoadingCache;\n\npublic class LocalOnlyLock implements Lock {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(LocalOnlyLock.class);\n\n    private static final CacheLoader<String, ReentrantLock> LOADER = key -> new ReentrantLock(true);\n    private static final ConcurrentHashMap<String, ScheduledFuture<?>> SCHEDULEDFUTURES =\n            new ConcurrentHashMap<>();\n    private static final LoadingCache<String, ReentrantLock> LOCKIDTOSEMAPHOREMAP =\n            Caffeine.newBuilder().build(LOADER);\n    private static final ThreadGroup THREAD_GROUP = new ThreadGroup(\"LocalOnlyLock-scheduler\");\n    private static final ThreadFactory THREAD_FACTORY =\n            runnable -> new Thread(THREAD_GROUP, runnable);\n    private static final ScheduledExecutorService SCHEDULER =\n            Executors.newScheduledThreadPool(1, THREAD_FACTORY);\n\n    @Override\n    public void acquireLock(String lockId) {\n        LOGGER.trace(\"Locking {}\", lockId);\n        LOCKIDTOSEMAPHOREMAP.get(lockId).lock();\n    }\n\n    @Override\n    public boolean acquireLock(String lockId, long timeToTry, TimeUnit unit) {\n        try {\n            LOGGER.trace(\"Locking {} with timeout {} {}\", lockId, timeToTry, unit);\n            return LOCKIDTOSEMAPHOREMAP.get(lockId).tryLock(timeToTry, unit);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public boolean acquireLock(String lockId, long timeToTry, long leaseTime, TimeUnit unit) {\n        LOGGER.trace(\n                \"Locking {} with timeout {} {} for {} {}\",\n                lockId,\n                timeToTry,\n                unit,\n                leaseTime,\n                unit);\n        if (acquireLock(lockId, timeToTry, unit)) {\n            LOGGER.trace(\"Releasing {} automatically after {} {}\", lockId, leaseTime, unit);\n            SCHEDULEDFUTURES.put(\n                    lockId, SCHEDULER.schedule(() -> deleteLock(lockId), leaseTime, unit));\n            return true;\n        }\n        return false;\n    }\n\n    private void removeLeaseExpirationJob(String lockId) {\n        ScheduledFuture<?> schedFuture = SCHEDULEDFUTURES.get(lockId);\n        if (schedFuture != null && schedFuture.cancel(false)) {\n            SCHEDULEDFUTURES.remove(lockId);\n            LOGGER.trace(\"lockId {} removed from lease expiration job\", lockId);\n        }\n    }\n\n    @Override\n    public void releaseLock(String lockId) {\n        // Synchronized to prevent race condition between semaphore check and actual release\n        synchronized (LOCKIDTOSEMAPHOREMAP) {\n            if (LOCKIDTOSEMAPHOREMAP.getIfPresent(lockId) == null) {\n                return;\n            }\n            LOGGER.trace(\"Releasing {}\", lockId);\n            LOCKIDTOSEMAPHOREMAP.get(lockId).unlock();\n            removeLeaseExpirationJob(lockId);\n        }\n    }\n\n    @Override\n    public void deleteLock(String lockId) {\n        LOGGER.trace(\"Deleting {}\", lockId);\n        LOCKIDTOSEMAPHOREMAP.invalidate(lockId);\n    }\n\n    @VisibleForTesting\n    LoadingCache<String, ReentrantLock> cache() {\n        return LOCKIDTOSEMAPHOREMAP;\n    }\n\n    @VisibleForTesting\n    ConcurrentHashMap<String, ScheduledFuture<?>> scheduledFutures() {\n        return SCHEDULEDFUTURES;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/sync/local/LocalOnlyLockConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.sync.local;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.core.sync.Lock;\n\n@Configuration\n@ConditionalOnProperty(name = \"conductor.workflow-execution-lock.type\", havingValue = \"local_only\")\npublic class LocalOnlyLockConfiguration {\n\n    @Bean\n    public Lock provideLock() {\n        return new LocalOnlyLock();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/sync/noop/NoopLock.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.sync.noop;\n\nimport java.util.concurrent.TimeUnit;\n\nimport com.netflix.conductor.core.sync.Lock;\n\npublic class NoopLock implements Lock {\n\n    @Override\n    public void acquireLock(String lockId) {}\n\n    @Override\n    public boolean acquireLock(String lockId, long timeToTry, TimeUnit unit) {\n        return true;\n    }\n\n    @Override\n    public boolean acquireLock(String lockId, long timeToTry, long leaseTime, TimeUnit unit) {\n        return true;\n    }\n\n    @Override\n    public void releaseLock(String lockId) {}\n\n    @Override\n    public void deleteLock(String lockId) {}\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/DateTimeUtils.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.text.ParseException;\nimport java.time.Duration;\nimport java.util.Date;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.apache.commons.lang3.time.DateUtils;\n\npublic class DateTimeUtils {\n\n    private static final String[] patterns =\n            new String[] {\"yyyy-MM-dd HH:mm\", \"yyyy-MM-dd HH:mm z\", \"yyyy-MM-dd\"};\n\n    public static Duration parseDuration(String text) {\n        Matcher m =\n                Pattern.compile(\n                                \"\\\\s*(?:(\\\\d+)\\\\s*(?:days?|d))?\"\n                                        + \"\\\\s*(?:(\\\\d+)\\\\s*(?:hours?|hrs?|h))?\"\n                                        + \"\\\\s*(?:(\\\\d+)\\\\s*(?:minutes?|mins?|m))?\"\n                                        + \"\\\\s*(?:(\\\\d+)\\\\s*(?:seconds?|secs?|s))?\"\n                                        + \"\\\\s*\",\n                                Pattern.CASE_INSENSITIVE)\n                        .matcher(text);\n        if (!m.matches()) throw new IllegalArgumentException(\"Not valid duration: \" + text);\n\n        int days = (m.start(1) == -1 ? 0 : Integer.parseInt(m.group(1)));\n        int hours = (m.start(2) == -1 ? 0 : Integer.parseInt(m.group(2)));\n        int mins = (m.start(3) == -1 ? 0 : Integer.parseInt(m.group(3)));\n        int secs = (m.start(4) == -1 ? 0 : Integer.parseInt(m.group(4)));\n        return Duration.ofSeconds((days * 86400) + (hours * 60L + mins) * 60L + secs);\n    }\n\n    public static Date parseDate(String date) throws ParseException {\n        return DateUtils.parseDate(date, patterns);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/ExternalPayloadStorageUtils.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.apache.commons.io.IOUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/** Provides utility functions to upload and download payloads to {@link ExternalPayloadStorage} */\n@Component\npublic class ExternalPayloadStorageUtils {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ExternalPayloadStorageUtils.class);\n\n    private final ExternalPayloadStorage externalPayloadStorage;\n    private final ConductorProperties properties;\n    private final ObjectMapper objectMapper;\n\n    public ExternalPayloadStorageUtils(\n            ExternalPayloadStorage externalPayloadStorage,\n            ConductorProperties properties,\n            ObjectMapper objectMapper) {\n        this.externalPayloadStorage = externalPayloadStorage;\n        this.properties = properties;\n        this.objectMapper = objectMapper;\n    }\n\n    /**\n     * Download the payload from the given path.\n     *\n     * @param path the relative path of the payload in the {@link ExternalPayloadStorage}\n     * @return the payload object\n     * @throws NonTransientException in case of JSON parsing errors or download errors\n     */\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, Object> downloadPayload(String path) {\n        try (InputStream inputStream = externalPayloadStorage.download(path)) {\n            return objectMapper.readValue(\n                    IOUtils.toString(inputStream, StandardCharsets.UTF_8), Map.class);\n        } catch (TransientException te) {\n            throw te;\n        } catch (Exception e) {\n            LOGGER.error(\"Unable to download payload from external storage path: {}\", path, e);\n            throw new NonTransientException(\n                    \"Unable to download payload from external storage path: \" + path, e);\n        }\n    }\n\n    /**\n     * Verify the payload size and upload to external storage if necessary.\n     *\n     * @param entity the task or workflow for which the payload is to be verified and uploaded\n     * @param payloadType the {@link PayloadType} of the payload\n     * @param <T> {@link TaskModel} or {@link WorkflowModel}\n     * @throws NonTransientException in case of JSON parsing errors or upload errors\n     * @throws TerminateWorkflowException if the payload size is bigger than permissible limit as\n     *     per {@link ConductorProperties}\n     */\n    public <T> void verifyAndUpload(T entity, PayloadType payloadType) {\n        if (!shouldUpload(entity, payloadType)) return;\n\n        long threshold = 0L;\n        long maxThreshold = 0L;\n        Map<String, Object> payload = new HashMap<>();\n        String workflowId = \"\";\n        switch (payloadType) {\n            case TASK_INPUT:\n                threshold = properties.getTaskInputPayloadSizeThreshold().toKilobytes();\n                maxThreshold = properties.getMaxTaskInputPayloadSizeThreshold().toKilobytes();\n                payload = ((TaskModel) entity).getInputData();\n                workflowId = ((TaskModel) entity).getWorkflowInstanceId();\n                break;\n            case TASK_OUTPUT:\n                threshold = properties.getTaskOutputPayloadSizeThreshold().toKilobytes();\n                maxThreshold = properties.getMaxTaskOutputPayloadSizeThreshold().toKilobytes();\n                payload = ((TaskModel) entity).getOutputData();\n                workflowId = ((TaskModel) entity).getWorkflowInstanceId();\n                break;\n            case WORKFLOW_INPUT:\n                threshold = properties.getWorkflowInputPayloadSizeThreshold().toKilobytes();\n                maxThreshold = properties.getMaxWorkflowInputPayloadSizeThreshold().toKilobytes();\n                payload = ((WorkflowModel) entity).getInput();\n                workflowId = ((WorkflowModel) entity).getWorkflowId();\n                break;\n            case WORKFLOW_OUTPUT:\n                threshold = properties.getWorkflowOutputPayloadSizeThreshold().toKilobytes();\n                maxThreshold = properties.getMaxWorkflowOutputPayloadSizeThreshold().toKilobytes();\n                payload = ((WorkflowModel) entity).getOutput();\n                workflowId = ((WorkflowModel) entity).getWorkflowId();\n                break;\n        }\n\n        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {\n            objectMapper.writeValue(byteArrayOutputStream, payload);\n            byte[] payloadBytes = byteArrayOutputStream.toByteArray();\n            long payloadSize = payloadBytes.length;\n\n            final long maxThresholdInBytes = maxThreshold * 1024;\n            if (payloadSize > maxThresholdInBytes) {\n                if (entity instanceof TaskModel) {\n                    String errorMsg =\n                            String.format(\n                                    \"The payload size: %d of task: %s in workflow: %s  is greater than the permissible limit: %d bytes\",\n                                    payloadSize,\n                                    ((TaskModel) entity).getTaskId(),\n                                    ((TaskModel) entity).getWorkflowInstanceId(),\n                                    maxThresholdInBytes);\n                    failTask(((TaskModel) entity), payloadType, errorMsg);\n                } else {\n                    String errorMsg =\n                            String.format(\n                                    \"The payload size: %d of workflow: %s is greater than the permissible limit: %d bytes\",\n                                    payloadSize,\n                                    ((WorkflowModel) entity).getWorkflowId(),\n                                    maxThresholdInBytes);\n                    failWorkflow(((WorkflowModel) entity), payloadType, errorMsg);\n                }\n            } else if (payloadSize > threshold * 1024) {\n                String externalInputPayloadStoragePath, externalOutputPayloadStoragePath;\n                switch (payloadType) {\n                    case TASK_INPUT:\n                        externalInputPayloadStoragePath =\n                                uploadHelper(payloadBytes, payloadSize, PayloadType.TASK_INPUT);\n                        ((TaskModel) entity).externalizeInput(externalInputPayloadStoragePath);\n                        Monitors.recordExternalPayloadStorageUsage(\n                                ((TaskModel) entity).getTaskDefName(),\n                                ExternalPayloadStorage.Operation.WRITE.toString(),\n                                PayloadType.TASK_INPUT.toString());\n                        break;\n                    case TASK_OUTPUT:\n                        externalOutputPayloadStoragePath =\n                                uploadHelper(payloadBytes, payloadSize, PayloadType.TASK_OUTPUT);\n                        ((TaskModel) entity).externalizeOutput(externalOutputPayloadStoragePath);\n                        Monitors.recordExternalPayloadStorageUsage(\n                                ((TaskModel) entity).getTaskDefName(),\n                                ExternalPayloadStorage.Operation.WRITE.toString(),\n                                PayloadType.TASK_OUTPUT.toString());\n                        break;\n                    case WORKFLOW_INPUT:\n                        externalInputPayloadStoragePath =\n                                uploadHelper(payloadBytes, payloadSize, PayloadType.WORKFLOW_INPUT);\n                        ((WorkflowModel) entity).externalizeInput(externalInputPayloadStoragePath);\n                        Monitors.recordExternalPayloadStorageUsage(\n                                ((WorkflowModel) entity).getWorkflowName(),\n                                ExternalPayloadStorage.Operation.WRITE.toString(),\n                                PayloadType.WORKFLOW_INPUT.toString());\n                        break;\n                    case WORKFLOW_OUTPUT:\n                        externalOutputPayloadStoragePath =\n                                uploadHelper(\n                                        payloadBytes, payloadSize, PayloadType.WORKFLOW_OUTPUT);\n                        ((WorkflowModel) entity)\n                                .externalizeOutput(externalOutputPayloadStoragePath);\n                        Monitors.recordExternalPayloadStorageUsage(\n                                ((WorkflowModel) entity).getWorkflowName(),\n                                ExternalPayloadStorage.Operation.WRITE.toString(),\n                                PayloadType.WORKFLOW_OUTPUT.toString());\n                        break;\n                }\n            }\n        } catch (TransientException | TerminateWorkflowException te) {\n            throw te;\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Unable to upload payload to external storage for workflow: {}\", workflowId, e);\n            throw new NonTransientException(\n                    \"Unable to upload payload to external storage for workflow: \" + workflowId, e);\n        }\n    }\n\n    @VisibleForTesting\n    String uploadHelper(\n            byte[] payloadBytes, long payloadSize, ExternalPayloadStorage.PayloadType payloadType) {\n        ExternalStorageLocation location =\n                externalPayloadStorage.getLocation(\n                        ExternalPayloadStorage.Operation.WRITE, payloadType, \"\", payloadBytes);\n        externalPayloadStorage.upload(\n                location.getPath(), new ByteArrayInputStream(payloadBytes), payloadSize);\n        return location.getPath();\n    }\n\n    @VisibleForTesting\n    void failTask(TaskModel task, PayloadType payloadType, String errorMsg) {\n        LOGGER.error(errorMsg);\n        task.setReasonForIncompletion(errorMsg);\n        task.setStatus(TaskModel.Status.FAILED_WITH_TERMINAL_ERROR);\n        if (payloadType == PayloadType.TASK_INPUT) {\n            task.setInputData(new HashMap<>());\n        } else {\n            task.setOutputData(new HashMap<>());\n        }\n    }\n\n    @VisibleForTesting\n    void failWorkflow(WorkflowModel workflow, PayloadType payloadType, String errorMsg) {\n        LOGGER.error(errorMsg);\n        if (payloadType == PayloadType.WORKFLOW_INPUT) {\n            workflow.setInput(new HashMap<>());\n        } else {\n            workflow.setOutput(new HashMap<>());\n        }\n        throw new TerminateWorkflowException(errorMsg);\n    }\n\n    @VisibleForTesting\n    <T> boolean shouldUpload(T entity, PayloadType payloadType) {\n        if (entity instanceof TaskModel) {\n            TaskModel taskModel = (TaskModel) entity;\n            if (payloadType == PayloadType.TASK_INPUT) {\n                return !taskModel.getRawInputData().isEmpty();\n            } else {\n                return !taskModel.getRawOutputData().isEmpty();\n            }\n        } else {\n            WorkflowModel workflowModel = (WorkflowModel) entity;\n            if (payloadType == PayloadType.WORKFLOW_INPUT) {\n                return !workflowModel.getRawInput().isEmpty();\n            } else {\n                return !workflowModel.getRawOutput().isEmpty();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/IDGenerator.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.util.UUID;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.stereotype.Component;\n\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.id.generator\",\n        havingValue = \"default\",\n        matchIfMissing = true)\n/**\n * ID Generator used by Conductor Note on overriding the ID Generator: The default ID generator uses\n * UUID v4 as the ID format. By overriding this class it is possible to use different scheme for ID\n * generation. However, this is not normal and should only be done after very careful consideration.\n *\n * <p>Please note, if you use Cassandra persistence, the schema uses UUID as the column type and the\n * IDs have to be valid UUIDs supported by Cassandra.\n */\npublic class IDGenerator {\n\n    public IDGenerator() {}\n\n    public String generate() {\n        return UUID.randomUUID().toString();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/JsonUtils.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.springframework.stereotype.Component;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/** This class contains utility functions for parsing/expanding JSON. */\n@SuppressWarnings(\"unchecked\")\n@Component\npublic class JsonUtils {\n\n    private final ObjectMapper objectMapper;\n\n    public JsonUtils(ObjectMapper objectMapper) {\n        this.objectMapper = objectMapper;\n    }\n\n    /**\n     * Expands a JSON object into a java object\n     *\n     * @param input the object to be expanded\n     * @return the expanded object containing java types like {@link Map} and {@link List}\n     */\n    public Object expand(Object input) {\n        if (input instanceof List) {\n            expandList((List<Object>) input);\n            return input;\n        } else if (input instanceof Map) {\n            expandMap((Map<String, Object>) input);\n            return input;\n        } else if (input instanceof String) {\n            return getJson((String) input);\n        } else {\n            return input;\n        }\n    }\n\n    private void expandList(List<Object> input) {\n        for (Object value : input) {\n            if (value instanceof String) {\n                if (isJsonString(value.toString())) {\n                    value = getJson(value.toString());\n                }\n            } else if (value instanceof Map) {\n                expandMap((Map<String, Object>) value);\n            } else if (value instanceof List) {\n                expandList((List<Object>) value);\n            }\n        }\n    }\n\n    private void expandMap(Map<String, Object> input) {\n        for (Map.Entry<String, Object> entry : input.entrySet()) {\n            Object value = entry.getValue();\n            if (value instanceof String) {\n                if (isJsonString(value.toString())) {\n                    entry.setValue(getJson(value.toString()));\n                }\n            } else if (value instanceof Map) {\n                expandMap((Map<String, Object>) value);\n            } else if (value instanceof List) {\n                expandList((List<Object>) value);\n            }\n        }\n    }\n\n    /**\n     * Used to obtain a JSONified object from a string\n     *\n     * @param jsonAsString the json object represented in string form\n     * @return the JSONified object representation if the input is a valid json string if the input\n     *     is not a valid json string, it will be returned as-is and no exception is thrown\n     */\n    private Object getJson(String jsonAsString) {\n        try {\n            return objectMapper.readValue(jsonAsString, Object.class);\n        } catch (Exception e) {\n            return jsonAsString;\n        }\n    }\n\n    private boolean isJsonString(String jsonAsString) {\n        jsonAsString = jsonAsString.trim();\n        return jsonAsString.startsWith(\"{\") || jsonAsString.startsWith(\"[\");\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/ParametersUtils.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Objects;\nimport java.util.regex.Pattern;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.utils.EnvUtils;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.jayway.jsonpath.Configuration;\nimport com.jayway.jsonpath.DocumentContext;\nimport com.jayway.jsonpath.JsonPath;\nimport com.jayway.jsonpath.Option;\n\n/** Used to parse and resolve the JSONPath bindings in the workflow and task definitions. */\n@Component\npublic class ParametersUtils {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ParametersUtils.class);\n    private static final Pattern PATTERN =\n            Pattern.compile(\n                    \"(?=(?<!\\\\$)\\\\$\\\\{)(?:(?=.*?\\\\{(?!.*?\\\\1)(.*\\\\}(?!.*\\\\2).*))(?=.*?\\\\}(?!.*?\\\\2)(.*)).)+?.*?(?=\\\\1)[^{]*(?=\\\\2$)\",\n                    Pattern.DOTALL);\n\n    private final ObjectMapper objectMapper;\n    private final TypeReference<Map<String, Object>> map = new TypeReference<>() {};\n\n    public ParametersUtils(ObjectMapper objectMapper) {\n        this.objectMapper = objectMapper;\n    }\n\n    public Map<String, Object> getTaskInput(\n            Map<String, Object> inputParams,\n            WorkflowModel workflow,\n            TaskDef taskDefinition,\n            String taskId) {\n        if (workflow.getWorkflowDefinition().getSchemaVersion() > 1) {\n            return getTaskInputV2(inputParams, workflow, taskId, taskDefinition);\n        }\n        return getTaskInputV1(workflow, inputParams);\n    }\n\n    public Map<String, Object> getTaskInputV2(\n            Map<String, Object> input,\n            WorkflowModel workflow,\n            String taskId,\n            TaskDef taskDefinition) {\n        Map<String, Object> inputParams;\n\n        if (input != null) {\n            inputParams = clone(input);\n        } else {\n            inputParams = new HashMap<>();\n        }\n        if (taskDefinition != null && taskDefinition.getInputTemplate() != null) {\n            clone(taskDefinition.getInputTemplate()).forEach(inputParams::putIfAbsent);\n        }\n\n        Map<String, Map<String, Object>> inputMap = new HashMap<>();\n\n        Map<String, Object> workflowParams = new HashMap<>();\n        workflowParams.put(\"input\", workflow.getInput());\n        workflowParams.put(\"output\", workflow.getOutput());\n        workflowParams.put(\"status\", workflow.getStatus());\n        workflowParams.put(\"workflowId\", workflow.getWorkflowId());\n        workflowParams.put(\"parentWorkflowId\", workflow.getParentWorkflowId());\n        workflowParams.put(\"parentWorkflowTaskId\", workflow.getParentWorkflowTaskId());\n        workflowParams.put(\"workflowType\", workflow.getWorkflowName());\n        workflowParams.put(\"version\", workflow.getWorkflowVersion());\n        workflowParams.put(\"correlationId\", workflow.getCorrelationId());\n        workflowParams.put(\"reasonForIncompletion\", workflow.getReasonForIncompletion());\n        workflowParams.put(\"schemaVersion\", workflow.getWorkflowDefinition().getSchemaVersion());\n        workflowParams.put(\"variables\", workflow.getVariables());\n\n        inputMap.put(\"workflow\", workflowParams);\n\n        // For new workflow being started the list of tasks will be empty\n        workflow.getTasks().stream()\n                .map(TaskModel::getReferenceTaskName)\n                .map(workflow::getTaskByRefName)\n                .forEach(\n                        task -> {\n                            Map<String, Object> taskParams = new HashMap<>();\n                            taskParams.put(\"input\", task.getInputData());\n                            taskParams.put(\"output\", task.getOutputData());\n                            taskParams.put(\"taskType\", task.getTaskType());\n                            if (task.getStatus() != null) {\n                                taskParams.put(\"status\", task.getStatus().toString());\n                            }\n                            taskParams.put(\"referenceTaskName\", task.getReferenceTaskName());\n                            taskParams.put(\"retryCount\", task.getRetryCount());\n                            taskParams.put(\"correlationId\", task.getCorrelationId());\n                            taskParams.put(\"pollCount\", task.getPollCount());\n                            taskParams.put(\"taskDefName\", task.getTaskDefName());\n                            taskParams.put(\"scheduledTime\", task.getScheduledTime());\n                            taskParams.put(\"startTime\", task.getStartTime());\n                            taskParams.put(\"endTime\", task.getEndTime());\n                            taskParams.put(\"workflowInstanceId\", task.getWorkflowInstanceId());\n                            taskParams.put(\"taskId\", task.getTaskId());\n                            taskParams.put(\n                                    \"reasonForIncompletion\", task.getReasonForIncompletion());\n                            taskParams.put(\"callbackAfterSeconds\", task.getCallbackAfterSeconds());\n                            taskParams.put(\"workerId\", task.getWorkerId());\n                            taskParams.put(\"iteration\", task.getIteration());\n                            inputMap.put(\n                                    task.isLoopOverTask()\n                                            ? TaskUtils.removeIterationFromTaskRefName(\n                                                    task.getReferenceTaskName())\n                                            : task.getReferenceTaskName(),\n                                    taskParams);\n                        });\n\n        Configuration option =\n                Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS);\n        DocumentContext documentContext = JsonPath.parse(inputMap, option);\n        Map<String, Object> replacedTaskInput = replace(inputParams, documentContext, taskId);\n        if (taskDefinition != null && taskDefinition.getInputTemplate() != null) {\n            // If input for a given key resolves to null, try replacing it with one from\n            // inputTemplate, if it exists.\n            replacedTaskInput.replaceAll(\n                    (key, value) ->\n                            (value == null) ? taskDefinition.getInputTemplate().get(key) : value);\n        }\n        return replacedTaskInput;\n    }\n\n    // deep clone using json - POJO\n    private Map<String, Object> clone(Map<String, Object> inputTemplate) {\n        try {\n            byte[] bytes = objectMapper.writeValueAsBytes(inputTemplate);\n            return objectMapper.readValue(bytes, map);\n        } catch (IOException e) {\n            throw new RuntimeException(\"Unable to clone input params\", e);\n        }\n    }\n\n    public Map<String, Object> replace(Map<String, Object> input, Object json) {\n        Object doc;\n        if (json instanceof String) {\n            doc = JsonPath.parse(json.toString());\n        } else {\n            doc = json;\n        }\n        Configuration option =\n                Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS);\n        DocumentContext documentContext = JsonPath.parse(doc, option);\n        return replace(input, documentContext, null);\n    }\n\n    public Object replace(String paramString) {\n        Configuration option =\n                Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS);\n        DocumentContext documentContext = JsonPath.parse(Collections.emptyMap(), option);\n        return replaceVariables(paramString, documentContext, null);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private Map<String, Object> replace(\n            Map<String, Object> input, DocumentContext documentContext, String taskId) {\n        Map<String, Object> result = new HashMap<>();\n        for (Entry<String, Object> e : input.entrySet()) {\n            Object newValue;\n            Object value = e.getValue();\n            if (value instanceof String) {\n                newValue = replaceVariables(value.toString(), documentContext, taskId);\n            } else if (value instanceof Map) {\n                // recursive call\n                newValue = replace((Map<String, Object>) value, documentContext, taskId);\n            } else if (value instanceof List) {\n                newValue = replaceList((List<?>) value, taskId, documentContext);\n            } else {\n                newValue = value;\n            }\n            result.put(e.getKey(), newValue);\n        }\n        return result;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private Object replaceList(List<?> values, String taskId, DocumentContext io) {\n        List<Object> replacedList = new LinkedList<>();\n        for (Object listVal : values) {\n            if (listVal instanceof String) {\n                Object replaced = replaceVariables(listVal.toString(), io, taskId);\n                replacedList.add(replaced);\n            } else if (listVal instanceof Map) {\n                Object replaced = replace((Map<String, Object>) listVal, io, taskId);\n                replacedList.add(replaced);\n            } else if (listVal instanceof List) {\n                Object replaced = replaceList((List<?>) listVal, taskId, io);\n                replacedList.add(replaced);\n            } else {\n                replacedList.add(listVal);\n            }\n        }\n        return replacedList;\n    }\n\n    private Object replaceVariables(\n            String paramString, DocumentContext documentContext, String taskId) {\n        return replaceVariables(paramString, documentContext, taskId, 0);\n    }\n\n    private Object replaceVariables(\n            String paramString, DocumentContext documentContext, String taskId, int depth) {\n        var matcher = PATTERN.matcher(paramString);\n        var replacements = new LinkedList<Replacement>();\n        while (matcher.find()) {\n            var start = matcher.start();\n            var end = matcher.end();\n            var match = paramString.substring(start, end);\n            String paramPath = match.substring(2, match.length() - 1);\n            paramPath = replaceVariables(paramPath, documentContext, taskId, depth + 1).toString();\n            // if the paramPath is blank, meaning no value in between ${ and }\n            // like ${}, ${  } etc, set the value to empty string\n            if (StringUtils.isBlank(paramPath)) {\n                replacements.add(new Replacement(\"\", start, end));\n                continue;\n            }\n            if (EnvUtils.isEnvironmentVariable(paramPath)) {\n                String sysValue = EnvUtils.getSystemParametersValue(paramPath, taskId);\n                if (sysValue != null) {\n                    replacements.add(new Replacement(sysValue, start, end));\n                }\n            } else {\n                try {\n                    replacements.add(new Replacement(documentContext.read(paramPath), start, end));\n                } catch (Exception e) {\n                    LOGGER.warn(\n                            \"Error reading documentContext for paramPath: {}. Exception: {}\",\n                            paramPath,\n                            e);\n                    replacements.add(new Replacement(null, start, end));\n                }\n            }\n        }\n        if (replacements.size() == 1\n                && replacements.getFirst().getStartIndex() == 0\n                && replacements.getFirst().getEndIndex() == paramString.length()\n                && depth == 0) {\n            return replacements.get(0).getReplacement();\n        }\n        Collections.sort(replacements);\n        var builder = new StringBuilder(paramString);\n        for (int i = replacements.size() - 1; i >= 0; i--) {\n            var replacement = replacements.get(i);\n            builder.replace(\n                    replacement.getStartIndex(),\n                    replacement.getEndIndex(),\n                    Objects.toString(replacement.getReplacement()));\n        }\n        return builder.toString().replaceAll(\"\\\\$\\\\$\\\\{\", \"\\\\${\");\n    }\n\n    @Deprecated\n    // Workflow schema version 1 is deprecated and new workflows should be using version 2\n    private Map<String, Object> getTaskInputV1(\n            WorkflowModel workflow, Map<String, Object> inputParams) {\n        Map<String, Object> input = new HashMap<>();\n        if (inputParams == null) {\n            return input;\n        }\n        Map<String, Object> workflowInput = workflow.getInput();\n        inputParams.forEach(\n                (paramName, value) -> {\n                    String paramPath = \"\" + value;\n                    String[] paramPathComponents = paramPath.split(\"\\\\.\");\n                    Utils.checkArgument(\n                            paramPathComponents.length == 3,\n                            \"Invalid input expression for \"\n                                    + paramName\n                                    + \", paramPathComponents.size=\"\n                                    + paramPathComponents.length\n                                    + \", expression=\"\n                                    + paramPath);\n\n                    String source = paramPathComponents[0]; // workflow, or task reference name\n                    String type = paramPathComponents[1]; // input/output\n                    String name = paramPathComponents[2]; // name of the parameter\n                    if (\"workflow\".equals(source)) {\n                        input.put(paramName, workflowInput.get(name));\n                    } else {\n                        TaskModel task = workflow.getTaskByRefName(source);\n                        if (task != null) {\n                            if (\"input\".equals(type)) {\n                                input.put(paramName, task.getInputData().get(name));\n                            } else {\n                                input.put(paramName, task.getOutputData().get(name));\n                            }\n                        }\n                    }\n                });\n        return input;\n    }\n\n    public Map<String, Object> getWorkflowInput(\n            WorkflowDef workflowDef, Map<String, Object> inputParams) {\n        if (workflowDef != null && workflowDef.getInputTemplate() != null) {\n            clone(workflowDef.getInputTemplate()).forEach(inputParams::putIfAbsent);\n        }\n        return inputParams;\n    }\n\n    private static class Replacement implements Comparable<Replacement> {\n        private final int startIndex;\n        private final int endIndex;\n        private final Object replacement;\n\n        public Replacement(Object replacement, int startIndex, int endIndex) {\n            this.replacement = replacement;\n            this.startIndex = startIndex;\n            this.endIndex = endIndex;\n        }\n\n        public Object getReplacement() {\n            return replacement;\n        }\n\n        public int getStartIndex() {\n            return startIndex;\n        }\n\n        public int getEndIndex() {\n            return endIndex;\n        }\n\n        @Override\n        public int compareTo(Replacement o) {\n            return Long.compare(startIndex, o.startIndex);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/QueueUtils.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.model.TaskModel;\n\npublic class QueueUtils {\n\n    public static final String DOMAIN_SEPARATOR = \":\";\n    private static final String ISOLATION_SEPARATOR = \"-\";\n    private static final String EXECUTION_NAME_SPACE_SEPARATOR = \"@\";\n\n    public static String getQueueName(TaskModel taskModel) {\n        return getQueueName(\n                taskModel.getTaskType(),\n                taskModel.getDomain(),\n                taskModel.getIsolationGroupId(),\n                taskModel.getExecutionNameSpace());\n    }\n\n    public static String getQueueName(Task task) {\n        return getQueueName(\n                task.getTaskType(),\n                task.getDomain(),\n                task.getIsolationGroupId(),\n                task.getExecutionNameSpace());\n    }\n\n    /**\n     * Creates a queue name string using <code>taskType</code>, <code>domain</code>, <code>\n     * isolationGroupId</code> and <code>executionNamespace</code>.\n     *\n     * @return domain:taskType@eexecutionNameSpace-isolationGroupId.\n     */\n    public static String getQueueName(\n            String taskType, String domain, String isolationGroupId, String executionNamespace) {\n\n        String queueName;\n        if (domain == null) {\n            queueName = taskType;\n        } else {\n            queueName = domain + DOMAIN_SEPARATOR + taskType;\n        }\n\n        if (executionNamespace != null) {\n            queueName = queueName + EXECUTION_NAME_SPACE_SEPARATOR + executionNamespace;\n        }\n\n        if (isolationGroupId != null) {\n            queueName = queueName + ISOLATION_SEPARATOR + isolationGroupId;\n        }\n        return queueName;\n    }\n\n    public static String getQueueNameWithoutDomain(String queueName) {\n        return queueName.substring(queueName.indexOf(DOMAIN_SEPARATOR) + 1);\n    }\n\n    public static String getExecutionNameSpace(String queueName) {\n        if (StringUtils.contains(queueName, ISOLATION_SEPARATOR)\n                && StringUtils.contains(queueName, EXECUTION_NAME_SPACE_SEPARATOR)) {\n            return StringUtils.substringBetween(\n                    queueName, EXECUTION_NAME_SPACE_SEPARATOR, ISOLATION_SEPARATOR);\n        } else if (StringUtils.contains(queueName, EXECUTION_NAME_SPACE_SEPARATOR)) {\n            return StringUtils.substringAfter(queueName, EXECUTION_NAME_SPACE_SEPARATOR);\n        } else {\n            return StringUtils.EMPTY;\n        }\n    }\n\n    public static boolean isIsolatedQueue(String queue) {\n        return StringUtils.isNotBlank(getIsolationGroup(queue));\n    }\n\n    private static String getIsolationGroup(String queue) {\n        return StringUtils.substringAfter(queue, QueueUtils.ISOLATION_SEPARATOR);\n    }\n\n    public static String getTaskType(String queue) {\n\n        if (StringUtils.isBlank(queue)) {\n            return StringUtils.EMPTY;\n        }\n\n        int domainSeperatorIndex = StringUtils.indexOf(queue, DOMAIN_SEPARATOR);\n        int startIndex;\n        if (domainSeperatorIndex == -1) {\n            startIndex = 0;\n        } else {\n            startIndex = domainSeperatorIndex + 1;\n        }\n        int endIndex = StringUtils.indexOf(queue, EXECUTION_NAME_SPACE_SEPARATOR);\n\n        if (endIndex == -1) {\n            endIndex = StringUtils.lastIndexOf(queue, ISOLATION_SEPARATOR);\n        }\n        if (endIndex == -1) {\n            endIndex = queue.length();\n        }\n\n        return StringUtils.substring(queue, startIndex, endIndex);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/SemaphoreUtil.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.util.concurrent.Semaphore;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/** A class wrapping a semaphore which holds the number of permits available for processing. */\npublic class SemaphoreUtil {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtil.class);\n    private final Semaphore semaphore;\n\n    public SemaphoreUtil(int numSlots) {\n        LOGGER.debug(\"Semaphore util initialized with {} permits\", numSlots);\n        semaphore = new Semaphore(numSlots);\n    }\n\n    /**\n     * Signals if processing is allowed based on whether specified number of permits can be\n     * acquired.\n     *\n     * @param numSlots the number of permits to acquire\n     * @return {@code true} - if permit is acquired {@code false} - if permit could not be acquired\n     */\n    public boolean acquireSlots(int numSlots) {\n        boolean acquired = semaphore.tryAcquire(numSlots);\n        LOGGER.trace(\"Trying to acquire {} permit: {}\", numSlots, acquired);\n        return acquired;\n    }\n\n    /** Signals that processing is complete and the specified number of permits can be released. */\n    public void completeProcessing(int numSlots) {\n        LOGGER.trace(\"Completed execution; releasing permit\");\n        semaphore.release(numSlots);\n    }\n\n    /**\n     * Gets the number of slots available for processing.\n     *\n     * @return number of available permits\n     */\n    public int availableSlots() {\n        int available = semaphore.availablePermits();\n        LOGGER.trace(\"Number of available permits: {}\", available);\n        return available;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/core/utils/Utils.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.*;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.core.exception.TransientException;\n\npublic class Utils {\n\n    public static final String DECIDER_QUEUE = \"_deciderQueue\";\n\n    /**\n     * ID of the server. Can be host name, IP address or any other meaningful identifier\n     *\n     * @return canonical host name resolved for the instance, \"unknown\" if resolution fails\n     */\n    public static String getServerId() {\n        try {\n            return InetAddress.getLocalHost().getHostName();\n        } catch (UnknownHostException e) {\n            return \"unknown\";\n        }\n    }\n\n    /**\n     * Split string with \"|\" as delimiter.\n     *\n     * @param inputStr Input string\n     * @return List of String\n     */\n    public static List<String> convertStringToList(String inputStr) {\n        List<String> list = new ArrayList<>();\n        if (StringUtils.isNotBlank(inputStr)) {\n            list = Arrays.asList(inputStr.split(\"\\\\|\"));\n        }\n        return list;\n    }\n\n    /**\n     * Ensures the truth of an condition involving one or more parameters to the calling method.\n     *\n     * @param condition a boolean expression\n     * @param errorMessage The exception message use if the input condition is not valid\n     * @throws IllegalArgumentException if input condition is not valid.\n     */\n    public static void checkArgument(boolean condition, String errorMessage) {\n        if (!condition) {\n            throw new IllegalArgumentException(errorMessage);\n        }\n    }\n\n    /**\n     * This method checks if the object is null or empty.\n     *\n     * @param object input of type {@link Object}.\n     * @param errorMessage The exception message use if the object is empty or null.\n     * @throws NullPointerException if input object is not valid.\n     */\n    public static void checkNotNull(Object object, String errorMessage) {\n        if (object == null) {\n            throw new NullPointerException(errorMessage);\n        }\n    }\n\n    /**\n     * Used to determine if the exception is thrown due to a transient failure and the operation is\n     * expected to succeed upon retrying.\n     *\n     * @param throwable the exception that is thrown\n     * @return true - if the exception is a transient failure\n     *     <p>false - if the exception is non-transient\n     */\n    public static boolean isTransientException(Throwable throwable) {\n        if (throwable != null) {\n            return throwable instanceof TransientException;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/ConcurrentExecutionLimitDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.model.TaskModel;\n\n/**\n * A contract to support concurrency limits of tasks.\n *\n * @since v3.3.5.\n */\npublic interface ConcurrentExecutionLimitDAO {\n\n    default void addTaskToLimit(TaskModel task) {\n        throw new UnsupportedOperationException(\n                getClass() + \" does not support addTaskToLimit method.\");\n    }\n\n    default void removeTaskFromLimit(TaskModel task) {\n        throw new UnsupportedOperationException(\n                getClass() + \" does not support removeTaskFromLimit method.\");\n    }\n\n    /**\n     * Checks if the number of tasks in progress for the given taskDef will exceed the limit if the\n     * task is scheduled to be in progress (given to the worker or for system tasks start() method\n     * called)\n     *\n     * @param task The task to be executed. Limit is set in the Task's definition\n     * @return true if by executing this task, the limit is breached. false otherwise.\n     * @see TaskDef#concurrencyLimit()\n     */\n    boolean exceedsLimit(TaskModel task);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/EventHandlerDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\n\n/** An abstraction to enable different Event Handler store implementations */\npublic interface EventHandlerDAO {\n\n    /**\n     * @param eventHandler Event handler to be added.\n     *     <p><em>NOTE:</em> Will throw an exception if an event handler already exists with the\n     *     name\n     */\n    void addEventHandler(EventHandler eventHandler);\n\n    /**\n     * @param eventHandler Event handler to be updated.\n     */\n    void updateEventHandler(EventHandler eventHandler);\n\n    /**\n     * @param name Removes the event handler from the system\n     */\n    void removeEventHandler(String name);\n\n    /**\n     * @return All the event handlers registered in the system\n     */\n    List<EventHandler> getAllEventHandlers();\n\n    /**\n     * @param event name of the event\n     * @param activeOnly if true, returns only the active handlers\n     * @return Returns the list of all the event handlers for a given event\n     */\n    List<EventHandler> getEventHandlersForEvent(String event, boolean activeOnly);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/ExecutionDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\n/** Data access layer for storing workflow executions */\npublic interface ExecutionDAO {\n\n    /**\n     * @param taskName Name of the task\n     * @param workflowId Workflow instance id\n     * @return List of pending tasks (in_progress)\n     */\n    List<TaskModel> getPendingTasksByWorkflow(String taskName, String workflowId);\n\n    /**\n     * @param taskType Type of task\n     * @param startKey start\n     * @param count number of tasks to return\n     * @return List of tasks starting from startKey\n     */\n    List<TaskModel> getTasks(String taskType, String startKey, int count);\n\n    /**\n     * @param tasks tasks to be created\n     * @return List of tasks that were created.\n     *     <p><b>Note on the primary key constraint</b>\n     *     <p>For a given task reference name and retryCount should be considered unique/primary\n     *     key. Given two tasks with the same reference name and retryCount only one should be added\n     *     to the database.\n     */\n    List<TaskModel> createTasks(List<TaskModel> tasks);\n\n    /**\n     * @param task Task to be updated\n     */\n    void updateTask(TaskModel task);\n\n    /**\n     * Checks if the number of tasks in progress for the given taskDef will exceed the limit if the\n     * task is scheduled to be in progress (given to the worker or for system tasks start() method\n     * called)\n     *\n     * @param task The task to be executed. Limit is set in the Task's definition\n     * @return true if by executing this task, the limit is breached. false otherwise.\n     * @see TaskDef#concurrencyLimit()\n     * @deprecated Since v3.3.5. Use {@link ConcurrentExecutionLimitDAO#exceedsLimit(TaskModel)}.\n     */\n    @Deprecated\n    default boolean exceedsInProgressLimit(TaskModel task) {\n        throw new UnsupportedOperationException(\n                getClass() + \"does not support exceedsInProgressLimit\");\n    }\n\n    /**\n     * @param taskId id of the task to be removed.\n     * @return true if the deletion is successful, false otherwise.\n     */\n    boolean removeTask(String taskId);\n\n    /**\n     * @param taskId Task instance id\n     * @return Task\n     */\n    TaskModel getTask(String taskId);\n\n    /**\n     * @param taskIds Task instance ids\n     * @return List of tasks\n     */\n    List<TaskModel> getTasks(List<String> taskIds);\n\n    /**\n     * @param taskType Type of the task for which to retrieve the list of pending tasks\n     * @return List of pending tasks\n     */\n    List<TaskModel> getPendingTasksForTaskType(String taskType);\n\n    /**\n     * @param workflowId Workflow instance id\n     * @return List of tasks for the given workflow instance id\n     */\n    List<TaskModel> getTasksForWorkflow(String workflowId);\n\n    /**\n     * @param workflow Workflow to be created\n     * @return Id of the newly created workflow\n     */\n    String createWorkflow(WorkflowModel workflow);\n\n    /**\n     * @param workflow Workflow to be updated\n     * @return Id of the updated workflow\n     */\n    String updateWorkflow(WorkflowModel workflow);\n\n    /**\n     * @param workflowId workflow instance id\n     * @return true if the deletion is successful, false otherwise\n     */\n    boolean removeWorkflow(String workflowId);\n\n    /**\n     * Removes the workflow with ttl seconds\n     *\n     * @param workflowId workflowId workflow instance id\n     * @param ttlSeconds time to live in seconds.\n     * @return\n     */\n    boolean removeWorkflowWithExpiry(String workflowId, int ttlSeconds);\n\n    /**\n     * @param workflowType Workflow Type\n     * @param workflowId workflow instance id\n     */\n    void removeFromPendingWorkflow(String workflowType, String workflowId);\n\n    /**\n     * @param workflowId workflow instance id\n     * @return Workflow\n     */\n    WorkflowModel getWorkflow(String workflowId);\n\n    /**\n     * @param workflowId workflow instance id\n     * @param includeTasks if set, includes the tasks (pending and completed) sorted by Task\n     *     Sequence number in Workflow.\n     * @return Workflow instance details\n     */\n    WorkflowModel getWorkflow(String workflowId, boolean includeTasks);\n\n    /**\n     * @param workflowName name of the workflow\n     * @param version the workflow version\n     * @return List of workflow ids which are running\n     */\n    List<String> getRunningWorkflowIds(String workflowName, int version);\n\n    /**\n     * @param workflowName Name of the workflow\n     * @param version the workflow version\n     * @return List of workflows that are running\n     */\n    List<WorkflowModel> getPendingWorkflowsByType(String workflowName, int version);\n\n    /**\n     * @param workflowName Name of the workflow\n     * @return No. of running workflows\n     */\n    long getPendingWorkflowCount(String workflowName);\n\n    /**\n     * @param taskDefName Name of the task\n     * @return Number of task currently in IN_PROGRESS status\n     */\n    long getInProgressTaskCount(String taskDefName);\n\n    /**\n     * @param workflowName Name of the workflow\n     * @param startTime epoch time\n     * @param endTime epoch time\n     * @return List of workflows between start and end time\n     */\n    List<WorkflowModel> getWorkflowsByType(String workflowName, Long startTime, Long endTime);\n\n    /**\n     * @param workflowName workflow name\n     * @param correlationId Correlation Id\n     * @param includeTasks Option to includeTasks in results\n     * @return List of workflows by correlation id\n     */\n    List<WorkflowModel> getWorkflowsByCorrelationId(\n            String workflowName, String correlationId, boolean includeTasks);\n\n    /**\n     * @return true, if the DAO implementation is capable of searching across workflows false, if\n     *     the DAO implementation cannot perform searches across workflows (and needs to use\n     *     indexDAO)\n     */\n    boolean canSearchAcrossWorkflows();\n\n    // Events\n\n    /**\n     * @param eventExecution Event Execution to be stored\n     * @return true if the event was added. false otherwise when the event by id is already already\n     *     stored.\n     */\n    boolean addEventExecution(EventExecution eventExecution);\n\n    /**\n     * @param eventExecution Event execution to be updated\n     */\n    void updateEventExecution(EventExecution eventExecution);\n\n    /**\n     * @param eventExecution Event execution to be removed\n     */\n    void removeEventExecution(EventExecution eventExecution);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/IndexDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.events.queue.Message;\n\n/** DAO to index the workflow and task details for searching. */\npublic interface IndexDAO {\n\n    /** Setup method in charge or initializing/populating the index. */\n    void setup() throws Exception;\n\n    /**\n     * This method should return an unique identifier of the indexed doc\n     *\n     * @param workflow Workflow to be indexed\n     */\n    void indexWorkflow(WorkflowSummary workflow);\n\n    /**\n     * This method should return an unique identifier of the indexed doc\n     *\n     * @param workflow Workflow to be indexed\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncIndexWorkflow(WorkflowSummary workflow);\n\n    /**\n     * @param task Task to be indexed\n     */\n    void indexTask(TaskSummary task);\n\n    /**\n     * @param task Task to be indexed asynchronously\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncIndexTask(TaskSummary task);\n\n    /**\n     * @param query SQL like query for workflow search parameters.\n     * @param freeText Additional query in free text. Lucene syntax\n     * @param start start start index for pagination\n     * @param count count # of workflow ids to be returned\n     * @param sort sort options\n     * @return List of workflow ids for the matching query\n     */\n    SearchResult<String> searchWorkflows(\n            String query, String freeText, int start, int count, List<String> sort);\n\n    /**\n     * @param query SQL like query for workflow search parameters.\n     * @param freeText Additional query in free text. Lucene syntax\n     * @param start start start index for pagination\n     * @param count count # of workflow ids to be returned\n     * @param sort sort options\n     * @return List of workflows for the matching query\n     */\n    SearchResult<WorkflowSummary> searchWorkflowSummary(\n            String query, String freeText, int start, int count, List<String> sort);\n\n    /**\n     * @param query SQL like query for task search parameters.\n     * @param freeText Additional query in free text. Lucene syntax\n     * @param start start start index for pagination\n     * @param count count # of task ids to be returned\n     * @param sort sort options\n     * @return List of task ids for the matching query\n     */\n    SearchResult<String> searchTasks(\n            String query, String freeText, int start, int count, List<String> sort);\n\n    /**\n     * @param query SQL like query for task search parameters.\n     * @param freeText Additional query in free text. Lucene syntax\n     * @param start start start index for pagination\n     * @param count count # of task ids to be returned\n     * @param sort sort options\n     * @return List of tasks for the matching query\n     */\n    SearchResult<TaskSummary> searchTaskSummary(\n            String query, String freeText, int start, int count, List<String> sort);\n\n    /**\n     * Remove the workflow index\n     *\n     * @param workflowId workflow to be removed\n     */\n    void removeWorkflow(String workflowId);\n\n    /**\n     * Remove the workflow index\n     *\n     * @param workflowId workflow to be removed\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncRemoveWorkflow(String workflowId);\n\n    /**\n     * Updates the index\n     *\n     * @param workflowInstanceId id of the workflow\n     * @param keys keys to be updated\n     * @param values values. Number of keys and values MUST match.\n     */\n    void updateWorkflow(String workflowInstanceId, String[] keys, Object[] values);\n\n    /**\n     * Updates the index\n     *\n     * @param workflowInstanceId id of the workflow\n     * @param keys keys to be updated\n     * @param values values. Number of keys and values MUST match.\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncUpdateWorkflow(\n            String workflowInstanceId, String[] keys, Object[] values);\n\n    /**\n     * Remove the task index\n     *\n     * @param workflowId workflow containing task\n     * @param taskId task to be removed\n     */\n    void removeTask(String workflowId, String taskId);\n\n    /**\n     * Remove the task index asynchronously\n     *\n     * @param workflowId workflow containing task\n     * @param taskId task to be removed\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncRemoveTask(String workflowId, String taskId);\n\n    /**\n     * Updates the index\n     *\n     * @param workflowId id of the workflow\n     * @param taskId id of the task\n     * @param keys keys to be updated\n     * @param values values. Number of keys and values MUST match.\n     */\n    void updateTask(String workflowId, String taskId, String[] keys, Object[] values);\n\n    /**\n     * Updates the index\n     *\n     * @param workflowId id of the workflow\n     * @param taskId id of the task\n     * @param keys keys to be updated\n     * @param values values. Number of keys and values MUST match.\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncUpdateTask(\n            String workflowId, String taskId, String[] keys, Object[] values);\n\n    /**\n     * Retrieves a specific field from the index\n     *\n     * @param workflowInstanceId id of the workflow\n     * @param key field to be retrieved\n     * @return value of the field as string\n     */\n    String get(String workflowInstanceId, String key);\n\n    /**\n     * @param logs Task Execution logs to be indexed\n     */\n    void addTaskExecutionLogs(List<TaskExecLog> logs);\n\n    /**\n     * @param logs Task Execution logs to be indexed\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncAddTaskExecutionLogs(List<TaskExecLog> logs);\n\n    /**\n     * @param taskId Id of the task for which to fetch the execution logs\n     * @return Returns the task execution logs for given task id\n     */\n    List<TaskExecLog> getTaskExecutionLogs(String taskId);\n\n    /**\n     * @param eventExecution Event Execution to be indexed\n     */\n    void addEventExecution(EventExecution eventExecution);\n\n    List<EventExecution> getEventExecutions(String event);\n\n    /**\n     * @param eventExecution Event Execution to be indexed\n     * @return CompletableFuture of type void\n     */\n    CompletableFuture<Void> asyncAddEventExecution(EventExecution eventExecution);\n\n    /**\n     * Adds an incoming external message into the index\n     *\n     * @param queue Name of the registered queue\n     * @param msg Message\n     */\n    void addMessage(String queue, Message msg);\n\n    /**\n     * Adds an incoming external message into the index\n     *\n     * @param queue Name of the registered queue\n     * @param message {@link Message}\n     * @return CompletableFuture of type Void\n     */\n    CompletableFuture<Void> asyncAddMessage(String queue, Message message);\n\n    List<Message> getMessages(String queue);\n\n    /**\n     * Search for Workflows completed or failed beyond archiveTtlDays\n     *\n     * @param indexName Name of the index to search\n     * @param archiveTtlDays Archival Time to Live\n     * @return List of worlflow Ids matching the pattern\n     */\n    List<String> searchArchivableWorkflows(String indexName, long archiveTtlDays);\n\n    /**\n     * Get total workflow counts that matches the query\n     *\n     * @param query SQL like query for workflow search parameters.\n     * @param freeText Additional query in free text. Lucene syntax\n     * @return Number of matches for the query\n     */\n    long getWorkflowCount(String query, String freeText);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/MetadataDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\n\n/** Data access layer for the workflow metadata - task definitions and workflow definitions */\npublic interface MetadataDAO {\n\n    /**\n     * @param taskDef task definition to be created\n     */\n    TaskDef createTaskDef(TaskDef taskDef);\n\n    /**\n     * @param taskDef task definition to be updated.\n     * @return name of the task definition\n     */\n    TaskDef updateTaskDef(TaskDef taskDef);\n\n    /**\n     * @param name Name of the task\n     * @return Task Definition\n     */\n    TaskDef getTaskDef(String name);\n\n    /**\n     * @return All the task definitions\n     */\n    List<TaskDef> getAllTaskDefs();\n\n    /**\n     * @param name Name of the task\n     */\n    void removeTaskDef(String name);\n\n    /**\n     * @param def workflow definition\n     */\n    void createWorkflowDef(WorkflowDef def);\n\n    /**\n     * @param def workflow definition\n     */\n    void updateWorkflowDef(WorkflowDef def);\n\n    /**\n     * @param name Name of the workflow\n     * @return Workflow Definition\n     */\n    Optional<WorkflowDef> getLatestWorkflowDef(String name);\n\n    /**\n     * @param name Name of the workflow\n     * @param version version\n     * @return workflow definition\n     */\n    Optional<WorkflowDef> getWorkflowDef(String name, int version);\n\n    /**\n     * @param name Name of the workflow definition to be removed\n     * @param version Version of the workflow definition to be removed\n     */\n    void removeWorkflowDef(String name, Integer version);\n\n    /**\n     * @return List of all the workflow definitions\n     */\n    List<WorkflowDef> getAllWorkflowDefs();\n\n    /**\n     * @return List the latest versions of the workflow definitions\n     */\n    List<WorkflowDef> getAllWorkflowDefsLatestVersions();\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/PollDataDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.tasks.PollData;\n\n/** An abstraction to enable different PollData store implementations */\npublic interface PollDataDAO {\n\n    /**\n     * Updates the {@link PollData} information with the most recently polled data for a task queue.\n     *\n     * @param taskDefName name of the task as specified in the task definition\n     * @param domain domain in which this task is being polled from\n     * @param workerId the identifier of the worker polling for this task\n     */\n    void updateLastPollData(String taskDefName, String domain, String workerId);\n\n    /**\n     * Retrieve the {@link PollData} for the given task in the given domain.\n     *\n     * @param taskDefName name of the task as specified in the task definition\n     * @param domain domain for which {@link PollData} is being requested\n     * @return the {@link PollData} for the given task queue in the specified domain\n     */\n    PollData getPollData(String taskDefName, String domain);\n\n    /**\n     * Retrieve the {@link PollData} for the given task across all domains.\n     *\n     * @param taskDefName name of the task as specified in the task definition\n     * @return the {@link PollData} for the given task queue in all domains\n     */\n    List<PollData> getPollData(String taskDefName);\n\n    /**\n     * Retrieve the {@link PollData} for all task types\n     *\n     * @return the {@link PollData} for all task types\n     */\n    default List<PollData> getAllPollData() {\n        throw new UnsupportedOperationException(\n                \"The selected PollDataDAO (\"\n                        + this.getClass().getSimpleName()\n                        + \") does not implement the getAllPollData() method\");\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/QueueDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport com.netflix.conductor.core.events.queue.Message;\n\n/** DAO responsible for managing queuing for the tasks. */\npublic interface QueueDAO {\n\n    /**\n     * @param queueName name of the queue\n     * @param id message id\n     * @param offsetTimeInSecond time in seconds, after which the message should be marked visible.\n     *     (for timed queues)\n     */\n    void push(String queueName, String id, long offsetTimeInSecond);\n\n    /**\n     * @param queueName name of the queue\n     * @param id message id\n     * @param priority message priority (between 0 and 99)\n     * @param offsetTimeInSecond time in seconds, after which the message should be marked visible.\n     *     (for timed queues)\n     */\n    void push(String queueName, String id, int priority, long offsetTimeInSecond);\n\n    /**\n     * @param queueName Name of the queue\n     * @param messages messages to be pushed.\n     */\n    void push(String queueName, List<Message> messages);\n\n    /**\n     * @param queueName Name of the queue\n     * @param id message id\n     * @param offsetTimeInSecond time in seconds, after which the message should be marked visible.\n     *     (for timed queues)\n     * @return true if the element was added to the queue. false otherwise indicating the element\n     *     already exists in the queue.\n     */\n    boolean pushIfNotExists(String queueName, String id, long offsetTimeInSecond);\n\n    /**\n     * @param queueName Name of the queue\n     * @param id message id\n     * @param priority message priority (between 0 and 99)\n     * @param offsetTimeInSecond time in seconds, after which the message should be marked visible.\n     *     (for timed queues)\n     * @return true if the element was added to the queue. false otherwise indicating the element\n     *     already exists in the queue.\n     */\n    boolean pushIfNotExists(String queueName, String id, int priority, long offsetTimeInSecond);\n\n    /**\n     * @param queueName Name of the queue\n     * @param count number of messages to be read from the queue\n     * @param timeout timeout in milliseconds\n     * @return list of elements from the named queue\n     */\n    List<String> pop(String queueName, int count, int timeout);\n\n    /**\n     * @param queueName Name of the queue\n     * @param count number of messages to be read from the queue\n     * @param timeout timeout in milliseconds\n     * @return list of elements from the named queue\n     */\n    List<Message> pollMessages(String queueName, int count, int timeout);\n\n    /**\n     * @param queueName Name of the queue\n     * @param messageId Message id\n     */\n    void remove(String queueName, String messageId);\n\n    /**\n     * @param queueName Name of the queue\n     * @return size of the queue\n     */\n    int getSize(String queueName);\n\n    /**\n     * @param queueName Name of the queue\n     * @param messageId Message Id\n     * @return true if the message was found and ack'ed\n     */\n    boolean ack(String queueName, String messageId);\n\n    /**\n     * Extend the lease of the unacknowledged message for longer period.\n     *\n     * @param queueName Name of the queue\n     * @param messageId Message Id\n     * @param unackTimeout timeout in milliseconds for which the unack lease should be extended.\n     *     (replaces the current value with this value)\n     * @return true if the message was updated with extended lease. false otherwise.\n     */\n    boolean setUnackTimeout(String queueName, String messageId, long unackTimeout);\n\n    /**\n     * @param queueName Name of the queue\n     */\n    void flush(String queueName);\n\n    /**\n     * @return key : queue name, value: size of the queue\n     */\n    Map<String, Long> queuesDetail();\n\n    /**\n     * @return key : queue name, value: map of shard name to size and unack queue size\n     */\n    Map<String, Map<String, Map<String, Long>>> queuesDetailVerbose();\n\n    default void processUnacks(String queueName) {}\n\n    /**\n     * Resets the offsetTime on a message to 0, without pulling out the message from the queue\n     *\n     * @param queueName name of the queue\n     * @param id message id\n     * @return true if the message is in queue and the change was successful else returns false\n     */\n    boolean resetOffsetTime(String queueName, String id);\n\n    /**\n     * Postpone a given message with postponeDurationInSeconds, so that the message won't be\n     * available for further polls until specified duration. By default, the message is removed and\n     * pushed backed with postponeDurationInSeconds to be backwards compatible.\n     *\n     * @param queueName name of the queue\n     * @param messageId message id\n     * @param priority message priority (between 0 and 99)\n     * @param postponeDurationInSeconds duration in seconds by which the message is to be postponed\n     */\n    default boolean postpone(\n            String queueName, String messageId, int priority, long postponeDurationInSeconds) {\n        remove(queueName, messageId);\n        push(queueName, messageId, priority, postponeDurationInSeconds);\n        return true;\n    }\n\n    /**\n     * Check if the message with given messageId exists in the Queue.\n     *\n     * @param queueName\n     * @param messageId\n     * @return\n     */\n    default boolean containsMessage(String queueName, String messageId) {\n        throw new UnsupportedOperationException(\n                \"Please ensure your provided Queue implementation overrides and implements this method.\");\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/dao/RateLimitingDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.model.TaskModel;\n\n/** An abstraction to enable different Rate Limiting implementations */\npublic interface RateLimitingDAO {\n\n    /**\n     * Checks if the Task is rate limited or not based on the {@link\n     * TaskModel#getRateLimitPerFrequency()} and {@link TaskModel#getRateLimitFrequencyInSeconds()}\n     *\n     * @param task: which needs to be evaluated whether it is rateLimited or not\n     * @return true: If the {@link TaskModel} is rateLimited false: If the {@link TaskModel} is not\n     *     rateLimited\n     */\n    boolean exceedsRateLimitPerFrequency(TaskModel task, TaskDef taskDef);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/metrics/Monitors.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.metrics;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DistributionSummary;\nimport com.netflix.spectator.api.Gauge;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.Timer;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\n\npublic class Monitors {\n\n    private static final Registry registry = Spectator.globalRegistry();\n\n    public static final String NO_DOMAIN = \"NO_DOMAIN\";\n\n    private static final Map<String, Map<Map<String, String>, Counter>> counters =\n            new ConcurrentHashMap<>();\n\n    private static final Map<String, Map<Map<String, String>, PercentileTimer>> timers =\n            new ConcurrentHashMap<>();\n\n    private static final Map<String, Map<Map<String, String>, Gauge>> gauges =\n            new ConcurrentHashMap<>();\n\n    private static final Map<String, Map<Map<String, String>, DistributionSummary>>\n            distributionSummaries = new ConcurrentHashMap<>();\n\n    public static final String classQualifier = \"WorkflowMonitor\";\n\n    private Monitors() {}\n\n    /**\n     * Increment a counter that is used to measure the rate at which some event is occurring.\n     * Consider a simple queue, counters would be used to measure things like the rate at which\n     * items are being inserted and removed.\n     *\n     * @param className\n     * @param name\n     * @param additionalTags\n     */\n    private static void counter(String className, String name, String... additionalTags) {\n        getCounter(className, name, additionalTags).increment();\n    }\n\n    /**\n     * Set a gauge is a handle to get the current value. Typical examples for gauges would be the\n     * size of a queue or number of threads in the running state. Since gauges are sampled, there is\n     * no information about what might have occurred between samples.\n     *\n     * @param className\n     * @param name\n     * @param measurement\n     * @param additionalTags\n     */\n    private static void gauge(\n            String className, String name, long measurement, String... additionalTags) {\n        getGauge(className, name, additionalTags).set(measurement);\n    }\n\n    /**\n     * Records a value for an event as a distribution summary. Unlike a gauge, this is sampled\n     * multiple times during a minute or everytime a new value is recorded.\n     *\n     * @param className\n     * @param name\n     * @param additionalTags\n     */\n    private static void distributionSummary(\n            String className, String name, long value, String... additionalTags) {\n        getDistributionSummary(className, name, additionalTags).record(value);\n    }\n\n    private static Timer getTimer(String className, String name, String... additionalTags) {\n        Map<String, String> tags = toMap(className, additionalTags);\n        return timers.computeIfAbsent(name, s -> new ConcurrentHashMap<>())\n                .computeIfAbsent(\n                        tags,\n                        t -> {\n                            Id id = registry.createId(name, tags);\n                            return PercentileTimer.get(registry, id);\n                        });\n    }\n\n    private static Counter getCounter(String className, String name, String... additionalTags) {\n        Map<String, String> tags = toMap(className, additionalTags);\n\n        return counters.computeIfAbsent(name, s -> new ConcurrentHashMap<>())\n                .computeIfAbsent(\n                        tags,\n                        t -> {\n                            Id id = registry.createId(name, tags);\n                            return registry.counter(id);\n                        });\n    }\n\n    private static Gauge getGauge(String className, String name, String... additionalTags) {\n        Map<String, String> tags = toMap(className, additionalTags);\n\n        return gauges.computeIfAbsent(name, s -> new ConcurrentHashMap<>())\n                .computeIfAbsent(\n                        tags,\n                        t -> {\n                            Id id = registry.createId(name, tags);\n                            return registry.gauge(id);\n                        });\n    }\n\n    private static DistributionSummary getDistributionSummary(\n            String className, String name, String... additionalTags) {\n        Map<String, String> tags = toMap(className, additionalTags);\n\n        return distributionSummaries\n                .computeIfAbsent(name, s -> new ConcurrentHashMap<>())\n                .computeIfAbsent(\n                        tags,\n                        t -> {\n                            Id id = registry.createId(name, tags);\n                            return registry.distributionSummary(id);\n                        });\n    }\n\n    private static Map<String, String> toMap(String className, String... additionalTags) {\n        Map<String, String> tags = new HashMap<>();\n        tags.put(\"class\", className);\n        for (int j = 0; j < additionalTags.length - 1; j++) {\n            String tk = additionalTags[j];\n            String tv = \"\" + additionalTags[j + 1];\n            if (!tv.isEmpty()) {\n                tags.put(tk, tv);\n            }\n            j++;\n        }\n        return tags;\n    }\n\n    /**\n     * @param className Name of the class\n     * @param methodName Method name\n     */\n    public static void error(String className, String methodName) {\n        getCounter(className, \"workflow_server_error\", \"methodName\", methodName).increment();\n    }\n\n    public static void recordGauge(String name, long count) {\n        gauge(classQualifier, name, count);\n    }\n\n    public static void recordCounter(String name, long count, String... additionalTags) {\n        getCounter(classQualifier, name, additionalTags).increment(count);\n    }\n\n    public static void recordQueueWaitTime(String taskType, long queueWaitTime) {\n        getTimer(classQualifier, \"task_queue_wait\", \"taskType\", taskType)\n                .record(queueWaitTime, TimeUnit.MILLISECONDS);\n    }\n\n    public static void recordTaskExecutionTime(\n            String taskType, long duration, boolean includesRetries, TaskModel.Status status) {\n        getTimer(\n                        classQualifier,\n                        \"task_execution\",\n                        \"taskType\",\n                        taskType,\n                        \"includeRetries\",\n                        \"\" + includesRetries,\n                        \"status\",\n                        status.name())\n                .record(duration, TimeUnit.MILLISECONDS);\n    }\n\n    public static void recordWorkflowDecisionTime(long duration) {\n        getTimer(classQualifier, \"workflow_decision\").record(duration, TimeUnit.MILLISECONDS);\n    }\n\n    public static void recordTaskPollError(String taskType, String exception) {\n        recordTaskPollError(taskType, NO_DOMAIN, exception);\n    }\n\n    public static void recordTaskPollError(String taskType, String domain, String exception) {\n        counter(\n                classQualifier,\n                \"task_poll_error\",\n                \"taskType\",\n                taskType,\n                \"domain\",\n                domain,\n                \"exception\",\n                exception);\n    }\n\n    public static void recordTaskPoll(String taskType) {\n        counter(classQualifier, \"task_poll\", \"taskType\", taskType);\n    }\n\n    public static void recordTaskPollCount(String taskType, int count) {\n        recordTaskPollCount(taskType, NO_DOMAIN, count);\n    }\n\n    public static void recordTaskPollCount(String taskType, String domain, int count) {\n        getCounter(classQualifier, \"task_poll_count\", \"taskType\", taskType, \"domain\", domain)\n                .increment(count);\n    }\n\n    public static void recordQueueDepth(String taskType, long size, String ownerApp) {\n        gauge(\n                classQualifier,\n                \"task_queue_depth\",\n                size,\n                \"taskType\",\n                taskType,\n                \"ownerApp\",\n                StringUtils.defaultIfBlank(ownerApp, \"unknown\"));\n    }\n\n    public static void recordTaskInProgress(String taskType, long size, String ownerApp) {\n        gauge(\n                classQualifier,\n                \"task_in_progress\",\n                size,\n                \"taskType\",\n                taskType,\n                \"ownerApp\",\n                StringUtils.defaultIfBlank(ownerApp, \"unknown\"));\n    }\n\n    public static void recordRunningWorkflows(long count, String name, String ownerApp) {\n        gauge(\n                classQualifier,\n                \"workflow_running\",\n                count,\n                \"workflowName\",\n                name,\n                \"ownerApp\",\n                StringUtils.defaultIfBlank(ownerApp, \"unknown\"));\n    }\n\n    public static void recordNumTasksInWorkflow(long count, String name, String version) {\n        distributionSummary(\n                classQualifier,\n                \"tasks_in_workflow\",\n                count,\n                \"workflowName\",\n                name,\n                \"version\",\n                version);\n    }\n\n    public static void recordTaskTimeout(String taskType) {\n        counter(classQualifier, \"task_timeout\", \"taskType\", taskType);\n    }\n\n    public static void recordTaskResponseTimeout(String taskType) {\n        counter(classQualifier, \"task_response_timeout\", \"taskType\", taskType);\n    }\n\n    public static void recordTaskPendingTime(String taskType, String workflowType, long duration) {\n        gauge(\n                classQualifier,\n                \"task_pending_time\",\n                duration,\n                \"workflowName\",\n                workflowType,\n                \"taskType\",\n                taskType);\n    }\n\n    public static void recordWorkflowTermination(\n            String workflowType, WorkflowModel.Status status, String ownerApp) {\n        counter(\n                classQualifier,\n                \"workflow_failure\",\n                \"workflowName\",\n                workflowType,\n                \"status\",\n                status.name(),\n                \"ownerApp\",\n                StringUtils.defaultIfBlank(ownerApp, \"unknown\"));\n    }\n\n    public static void recordWorkflowStartSuccess(\n            String workflowType, String version, String ownerApp) {\n        counter(\n                classQualifier,\n                \"workflow_start_success\",\n                \"workflowName\",\n                workflowType,\n                \"version\",\n                version,\n                \"ownerApp\",\n                StringUtils.defaultIfBlank(ownerApp, \"unknown\"));\n    }\n\n    public static void recordWorkflowStartError(String workflowType, String ownerApp) {\n        counter(\n                classQualifier,\n                \"workflow_start_error\",\n                \"workflowName\",\n                workflowType,\n                \"ownerApp\",\n                StringUtils.defaultIfBlank(ownerApp, \"unknown\"));\n    }\n\n    public static void recordUpdateConflict(\n            String taskType, String workflowType, WorkflowModel.Status status) {\n        counter(\n                classQualifier,\n                \"task_update_conflict\",\n                \"workflowName\",\n                workflowType,\n                \"taskType\",\n                taskType,\n                \"workflowStatus\",\n                status.name());\n    }\n\n    public static void recordUpdateConflict(\n            String taskType, String workflowType, TaskModel.Status status) {\n        counter(\n                classQualifier,\n                \"task_update_conflict\",\n                \"workflowName\",\n                workflowType,\n                \"taskType\",\n                taskType,\n                \"taskStatus\",\n                status.name());\n    }\n\n    public static void recordTaskUpdateError(String taskType, String workflowType) {\n        counter(\n                classQualifier,\n                \"task_update_error\",\n                \"workflowName\",\n                workflowType,\n                \"taskType\",\n                taskType);\n    }\n\n    public static void recordTaskExtendLeaseError(String taskType, String workflowType) {\n        counter(\n                classQualifier,\n                \"task_extendLease_error\",\n                \"workflowName\",\n                workflowType,\n                \"taskType\",\n                taskType);\n    }\n\n    public static void recordTaskQueueOpError(String taskType, String workflowType) {\n        counter(\n                classQualifier,\n                \"task_queue_op_error\",\n                \"workflowName\",\n                workflowType,\n                \"taskType\",\n                taskType);\n    }\n\n    public static void recordWorkflowCompletion(\n            String workflowType, long duration, String ownerApp) {\n        getTimer(\n                        classQualifier,\n                        \"workflow_execution\",\n                        \"workflowName\",\n                        workflowType,\n                        \"ownerApp\",\n                        StringUtils.defaultIfBlank(ownerApp, \"unknown\"))\n                .record(duration, TimeUnit.MILLISECONDS);\n    }\n\n    public static void recordUnackTime(String workflowType, long duration) {\n        getTimer(classQualifier, \"workflow_unack\", \"workflowName\", workflowType)\n                .record(duration, TimeUnit.MILLISECONDS);\n    }\n\n    public static void recordTaskRateLimited(String taskDefName, int limit) {\n        gauge(classQualifier, \"task_rate_limited\", limit, \"taskType\", taskDefName);\n    }\n\n    public static void recordTaskConcurrentExecutionLimited(String taskDefName, int limit) {\n        gauge(classQualifier, \"task_concurrent_execution_limited\", limit, \"taskType\", taskDefName);\n    }\n\n    public static void recordEventQueueMessagesProcessed(\n            String queueType, String queueName, int count) {\n        getCounter(\n                        classQualifier,\n                        \"event_queue_messages_processed\",\n                        \"queueType\",\n                        queueType,\n                        \"queueName\",\n                        queueName)\n                .increment(count);\n    }\n\n    public static void recordObservableQMessageReceivedErrors(String queueType) {\n        counter(classQualifier, \"observable_queue_error\", \"queueType\", queueType);\n    }\n\n    public static void recordEventQueueMessagesHandled(String queueType, String queueName) {\n        counter(\n                classQualifier,\n                \"event_queue_messages_handled\",\n                \"queueType\",\n                queueType,\n                \"queueName\",\n                queueName);\n    }\n\n    public static void recordEventQueueMessagesError(String queueType, String queueName) {\n        counter(\n                classQualifier,\n                \"event_queue_messages_error\",\n                \"queueType\",\n                queueType,\n                \"queueName\",\n                queueName);\n    }\n\n    public static void recordEventExecutionSuccess(String event, String handler, String action) {\n        counter(\n                classQualifier,\n                \"event_execution_success\",\n                \"event\",\n                event,\n                \"handler\",\n                handler,\n                \"action\",\n                action);\n    }\n\n    public static void recordEventExecutionError(\n            String event, String handler, String action, String exceptionClazz) {\n        counter(\n                classQualifier,\n                \"event_execution_error\",\n                \"event\",\n                event,\n                \"handler\",\n                handler,\n                \"action\",\n                action,\n                \"exception\",\n                exceptionClazz);\n    }\n\n    public static void recordEventActionError(String action, String entityName, String event) {\n        counter(\n                classQualifier,\n                \"event_action_error\",\n                \"action\",\n                action,\n                \"entityName\",\n                entityName,\n                \"event\",\n                event);\n    }\n\n    public static void recordDaoRequests(\n            String dao, String action, String taskType, String workflowType) {\n        counter(\n                classQualifier,\n                \"dao_requests\",\n                \"dao\",\n                dao,\n                \"action\",\n                action,\n                \"taskType\",\n                StringUtils.defaultIfBlank(taskType, \"unknown\"),\n                \"workflowType\",\n                StringUtils.defaultIfBlank(workflowType, \"unknown\"));\n    }\n\n    public static void recordDaoEventRequests(String dao, String action, String event) {\n        counter(classQualifier, \"dao_event_requests\", \"dao\", dao, \"action\", action, \"event\", event);\n    }\n\n    public static void recordDaoPayloadSize(\n            String dao, String action, String taskType, String workflowType, int size) {\n        gauge(\n                classQualifier,\n                \"dao_payload_size\",\n                size,\n                \"dao\",\n                dao,\n                \"action\",\n                action,\n                \"taskType\",\n                StringUtils.defaultIfBlank(taskType, \"unknown\"),\n                \"workflowType\",\n                StringUtils.defaultIfBlank(workflowType, \"unknown\"));\n    }\n\n    public static void recordExternalPayloadStorageUsage(\n            String name, String operation, String payloadType) {\n        counter(\n                classQualifier,\n                \"external_payload_storage_usage\",\n                \"name\",\n                name,\n                \"operation\",\n                operation,\n                \"payloadType\",\n                payloadType);\n    }\n\n    public static void recordDaoError(String dao, String action) {\n        counter(classQualifier, \"dao_errors\", \"dao\", dao, \"action\", action);\n    }\n\n    public static void recordAckTaskError(String taskType) {\n        counter(classQualifier, \"task_ack_error\", \"taskType\", taskType);\n    }\n\n    public static void recordESIndexTime(String action, String docType, long val) {\n        getTimer(Monitors.classQualifier, action, \"docType\", docType)\n                .record(val, TimeUnit.MILLISECONDS);\n    }\n\n    public static void recordWorkerQueueSize(String queueType, int val) {\n        gauge(Monitors.classQualifier, \"indexing_worker_queue\", val, \"queueType\", queueType);\n    }\n\n    public static void recordDiscardedIndexingCount(String queueType) {\n        counter(Monitors.classQualifier, \"discarded_index_count\", \"queueType\", queueType);\n    }\n\n    public static void recordAcquireLockUnsuccessful() {\n        counter(classQualifier, \"acquire_lock_unsuccessful\");\n    }\n\n    public static void recordAcquireLockFailure(String exceptionClassName) {\n        counter(classQualifier, \"acquire_lock_failure\", \"exceptionType\", exceptionClassName);\n    }\n\n    public static void recordWorkflowArchived(String workflowType, WorkflowModel.Status status) {\n        counter(\n                classQualifier,\n                \"workflow_archived\",\n                \"workflowName\",\n                workflowType,\n                \"workflowStatus\",\n                status.name());\n    }\n\n    public static void recordArchivalDelayQueueSize(int val) {\n        gauge(classQualifier, \"workflow_archival_delay_queue_size\", val);\n    }\n\n    public static void recordDiscardedArchivalCount() {\n        counter(classQualifier, \"discarded_archival_count\");\n    }\n\n    public static void recordSystemTaskWorkerPollingLimited(String queueName) {\n        counter(classQualifier, \"system_task_worker_polling_limited\", \"queueName\", queueName);\n    }\n\n    public static void recordEventQueuePollSize(String queueType, int val) {\n        gauge(Monitors.classQualifier, \"event_queue_poll\", val, \"queueType\", queueType);\n    }\n\n    public static void recordQueueMessageRepushFromRepairService(String queueName) {\n        counter(classQualifier, \"queue_message_repushed\", \"queueName\", queueName);\n    }\n\n    public static void recordTaskExecLogSize(int val) {\n        gauge(classQualifier, \"task_exec_log_size\", val);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/metrics/WorkflowMonitor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.metrics;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.VisibleForTesting;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.service.MetadataService;\n\nimport static com.netflix.conductor.core.execution.tasks.SystemTaskRegistry.ASYNC_SYSTEM_TASKS_QUALIFIER;\n\n@Component\n@ConditionalOnProperty(\n        name = \"conductor.workflow-monitor.enabled\",\n        havingValue = \"true\",\n        matchIfMissing = true)\npublic class WorkflowMonitor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowMonitor.class);\n\n    private final MetadataService metadataService;\n    private final QueueDAO queueDAO;\n    private final ExecutionDAOFacade executionDAOFacade;\n    private final int metadataRefreshInterval;\n    private final Set<WorkflowSystemTask> asyncSystemTasks;\n\n    private List<TaskDef> taskDefs;\n    private List<WorkflowDef> workflowDefs;\n    private int refreshCounter = 0;\n\n    public WorkflowMonitor(\n            MetadataService metadataService,\n            QueueDAO queueDAO,\n            ExecutionDAOFacade executionDAOFacade,\n            @Value(\"${conductor.workflow-monitor.metadata-refresh-interval:10}\")\n                    int metadataRefreshInterval,\n            @Qualifier(ASYNC_SYSTEM_TASKS_QUALIFIER) Set<WorkflowSystemTask> asyncSystemTasks) {\n        this.metadataService = metadataService;\n        this.queueDAO = queueDAO;\n        this.executionDAOFacade = executionDAOFacade;\n        this.metadataRefreshInterval = metadataRefreshInterval;\n        this.asyncSystemTasks = asyncSystemTasks;\n        LOGGER.info(\"{} initialized.\", WorkflowMonitor.class.getSimpleName());\n    }\n\n    @Scheduled(\n            initialDelayString = \"${conductor.workflow-monitor.stats.initial-delay:120000}\",\n            fixedDelayString = \"${conductor.workflow-monitor.stats.delay:60000}\")\n    public void reportMetrics() {\n        try {\n            if (refreshCounter <= 0) {\n                workflowDefs = metadataService.getWorkflowDefs();\n                taskDefs = new ArrayList<>(metadataService.getTaskDefs());\n                refreshCounter = metadataRefreshInterval;\n            }\n\n            getPendingWorkflowToOwnerAppMap(workflowDefs)\n                    .forEach(\n                            (workflowName, ownerApp) -> {\n                                long count =\n                                        executionDAOFacade.getPendingWorkflowCount(workflowName);\n                                Monitors.recordRunningWorkflows(count, workflowName, ownerApp);\n                            });\n\n            taskDefs.forEach(\n                    taskDef -> {\n                        long size = queueDAO.getSize(taskDef.getName());\n                        long inProgressCount =\n                                executionDAOFacade.getInProgressTaskCount(taskDef.getName());\n                        Monitors.recordQueueDepth(taskDef.getName(), size, taskDef.getOwnerApp());\n                        if (taskDef.concurrencyLimit() > 0) {\n                            Monitors.recordTaskInProgress(\n                                    taskDef.getName(), inProgressCount, taskDef.getOwnerApp());\n                        }\n                    });\n\n            asyncSystemTasks.forEach(\n                    workflowSystemTask -> {\n                        long size = queueDAO.getSize(workflowSystemTask.getTaskType());\n                        long inProgressCount =\n                                executionDAOFacade.getInProgressTaskCount(\n                                        workflowSystemTask.getTaskType());\n                        Monitors.recordQueueDepth(workflowSystemTask.getTaskType(), size, \"system\");\n                        Monitors.recordTaskInProgress(\n                                workflowSystemTask.getTaskType(), inProgressCount, \"system\");\n                    });\n\n            refreshCounter--;\n        } catch (Exception e) {\n            LOGGER.error(\"Error while publishing scheduled metrics\", e);\n        }\n    }\n\n    /**\n     * Pending workflow data does not contain information about version. We only need the owner app\n     * and workflow name, and we only need to query for the workflow once.\n     */\n    @VisibleForTesting\n    Map<String, String> getPendingWorkflowToOwnerAppMap(List<WorkflowDef> workflowDefs) {\n        final Map<String, List<WorkflowDef>> groupedWorkflowDefs =\n                workflowDefs.stream().collect(Collectors.groupingBy(WorkflowDef::getName));\n\n        Map<String, String> workflowNameToOwnerMap = new HashMap<>();\n        groupedWorkflowDefs.forEach(\n                (key, value) -> {\n                    final WorkflowDef workflowDef =\n                            value.stream()\n                                    .max(Comparator.comparing(WorkflowDef::getVersion))\n                                    .orElseThrow(NoSuchElementException::new);\n\n                    workflowNameToOwnerMap.put(key, workflowDef.getOwnerApp());\n                });\n        return workflowNameToOwnerMap;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/model/TaskModel.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.model;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.beans.BeanUtils;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.protobuf.Any;\n\npublic class TaskModel {\n\n    public enum Status {\n        IN_PROGRESS(false, true, true),\n        CANCELED(true, false, false),\n        FAILED(true, false, true),\n        FAILED_WITH_TERMINAL_ERROR(true, false, false),\n        COMPLETED(true, true, true),\n        COMPLETED_WITH_ERRORS(true, true, true),\n        SCHEDULED(false, true, true),\n        TIMED_OUT(true, false, true),\n        SKIPPED(true, true, false);\n\n        private final boolean terminal;\n\n        private final boolean successful;\n\n        private final boolean retriable;\n\n        Status(boolean terminal, boolean successful, boolean retriable) {\n            this.terminal = terminal;\n            this.successful = successful;\n            this.retriable = retriable;\n        }\n\n        public boolean isTerminal() {\n            return terminal;\n        }\n\n        public boolean isSuccessful() {\n            return successful;\n        }\n\n        public boolean isRetriable() {\n            return retriable;\n        }\n    }\n\n    private String taskType;\n\n    private Status status;\n\n    private String referenceTaskName;\n\n    private int retryCount;\n\n    private int seq;\n\n    private String correlationId;\n\n    private int pollCount;\n\n    private String taskDefName;\n\n    /** Time when the task was scheduled */\n    private long scheduledTime;\n\n    /** Time when the task was first polled */\n    private long startTime;\n\n    /** Time when the task completed executing */\n    private long endTime;\n\n    /** Time when the task was last updated */\n    private long updateTime;\n\n    private int startDelayInSeconds;\n\n    private String retriedTaskId;\n\n    private boolean retried;\n\n    private boolean executed;\n\n    private boolean callbackFromWorker = true;\n\n    private long responseTimeoutSeconds;\n\n    private String workflowInstanceId;\n\n    private String workflowType;\n\n    private String taskId;\n\n    private String reasonForIncompletion;\n\n    private long callbackAfterSeconds;\n\n    private String workerId;\n\n    private WorkflowTask workflowTask;\n\n    private String domain;\n\n    private Any inputMessage;\n\n    private Any outputMessage;\n\n    private int rateLimitPerFrequency;\n\n    private int rateLimitFrequencyInSeconds;\n\n    private String externalInputPayloadStoragePath;\n\n    private String externalOutputPayloadStoragePath;\n\n    private int workflowPriority;\n\n    private String executionNameSpace;\n\n    private String isolationGroupId;\n\n    private int iteration;\n\n    private String subWorkflowId;\n\n    // Timeout after which the wait task should be marked as completed\n    private long waitTimeout;\n\n    /**\n     * Used to note that a sub workflow associated with SUB_WORKFLOW task has an action performed on\n     * it directly.\n     */\n    private boolean subworkflowChanged;\n\n    @JsonIgnore private Map<String, Object> inputPayload = new HashMap<>();\n\n    @JsonIgnore private Map<String, Object> outputPayload = new HashMap<>();\n\n    @JsonIgnore private Map<String, Object> inputData = new HashMap<>();\n\n    @JsonIgnore private Map<String, Object> outputData = new HashMap<>();\n\n    public String getTaskType() {\n        return taskType;\n    }\n\n    public void setTaskType(String taskType) {\n        this.taskType = taskType;\n    }\n\n    public Status getStatus() {\n        return status;\n    }\n\n    public void setStatus(Status status) {\n        this.status = status;\n    }\n\n    @JsonIgnore\n    public Map<String, Object> getInputData() {\n        if (!inputPayload.isEmpty() && !inputData.isEmpty()) {\n            inputData.putAll(inputPayload);\n            inputPayload = new HashMap<>();\n            return inputData;\n        } else if (inputPayload.isEmpty()) {\n            return inputData;\n        } else {\n            return inputPayload;\n        }\n    }\n\n    @JsonIgnore\n    public void setInputData(Map<String, Object> inputData) {\n        if (inputData == null) {\n            inputData = new HashMap<>();\n        }\n        this.inputData = inputData;\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @JsonProperty(\"inputData\")\n    @Deprecated\n    public void setRawInputData(Map<String, Object> inputData) {\n        setInputData(inputData);\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @JsonProperty(\"inputData\")\n    @Deprecated\n    public Map<String, Object> getRawInputData() {\n        return inputData;\n    }\n\n    public String getReferenceTaskName() {\n        return referenceTaskName;\n    }\n\n    public void setReferenceTaskName(String referenceTaskName) {\n        this.referenceTaskName = referenceTaskName;\n    }\n\n    public int getRetryCount() {\n        return retryCount;\n    }\n\n    public void setRetryCount(int retryCount) {\n        this.retryCount = retryCount;\n    }\n\n    public int getSeq() {\n        return seq;\n    }\n\n    public void setSeq(int seq) {\n        this.seq = seq;\n    }\n\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    public int getPollCount() {\n        return pollCount;\n    }\n\n    public void setPollCount(int pollCount) {\n        this.pollCount = pollCount;\n    }\n\n    public String getTaskDefName() {\n        if (taskDefName == null || \"\".equals(taskDefName)) {\n            taskDefName = taskType;\n        }\n        return taskDefName;\n    }\n\n    public void setTaskDefName(String taskDefName) {\n        this.taskDefName = taskDefName;\n    }\n\n    public long getScheduledTime() {\n        return scheduledTime;\n    }\n\n    public void setScheduledTime(long scheduledTime) {\n        this.scheduledTime = scheduledTime;\n    }\n\n    public long getStartTime() {\n        return startTime;\n    }\n\n    public void setStartTime(long startTime) {\n        this.startTime = startTime;\n    }\n\n    public long getEndTime() {\n        return endTime;\n    }\n\n    public void setEndTime(long endTime) {\n        this.endTime = endTime;\n    }\n\n    public long getUpdateTime() {\n        return updateTime;\n    }\n\n    public void setUpdateTime(long updateTime) {\n        this.updateTime = updateTime;\n    }\n\n    public int getStartDelayInSeconds() {\n        return startDelayInSeconds;\n    }\n\n    public void setStartDelayInSeconds(int startDelayInSeconds) {\n        this.startDelayInSeconds = startDelayInSeconds;\n    }\n\n    public String getRetriedTaskId() {\n        return retriedTaskId;\n    }\n\n    public void setRetriedTaskId(String retriedTaskId) {\n        this.retriedTaskId = retriedTaskId;\n    }\n\n    public boolean isRetried() {\n        return retried;\n    }\n\n    public void setRetried(boolean retried) {\n        this.retried = retried;\n    }\n\n    public boolean isExecuted() {\n        return executed;\n    }\n\n    public void setExecuted(boolean executed) {\n        this.executed = executed;\n    }\n\n    public boolean isCallbackFromWorker() {\n        return callbackFromWorker;\n    }\n\n    public void setCallbackFromWorker(boolean callbackFromWorker) {\n        this.callbackFromWorker = callbackFromWorker;\n    }\n\n    public long getResponseTimeoutSeconds() {\n        return responseTimeoutSeconds;\n    }\n\n    public void setResponseTimeoutSeconds(long responseTimeoutSeconds) {\n        this.responseTimeoutSeconds = responseTimeoutSeconds;\n    }\n\n    public String getWorkflowInstanceId() {\n        return workflowInstanceId;\n    }\n\n    public void setWorkflowInstanceId(String workflowInstanceId) {\n        this.workflowInstanceId = workflowInstanceId;\n    }\n\n    public String getWorkflowType() {\n        return workflowType;\n    }\n\n    public void setWorkflowType(String workflowType) {\n        this.workflowType = workflowType;\n    }\n\n    public String getTaskId() {\n        return taskId;\n    }\n\n    public void setTaskId(String taskId) {\n        this.taskId = taskId;\n    }\n\n    public String getReasonForIncompletion() {\n        return reasonForIncompletion;\n    }\n\n    public void setReasonForIncompletion(String reasonForIncompletion) {\n        this.reasonForIncompletion = reasonForIncompletion;\n    }\n\n    public long getCallbackAfterSeconds() {\n        return callbackAfterSeconds;\n    }\n\n    public void setCallbackAfterSeconds(long callbackAfterSeconds) {\n        this.callbackAfterSeconds = callbackAfterSeconds;\n    }\n\n    public String getWorkerId() {\n        return workerId;\n    }\n\n    public void setWorkerId(String workerId) {\n        this.workerId = workerId;\n    }\n\n    @JsonIgnore\n    public Map<String, Object> getOutputData() {\n        if (!outputPayload.isEmpty() && !outputData.isEmpty()) {\n            // Combine payload + data\n            // data has precedence over payload because:\n            //  with external storage enabled, payload contains the old values\n            //  while data contains the latest and if payload took precedence, it\n            //  would remove latest outputs\n            outputPayload.forEach(outputData::putIfAbsent);\n            outputPayload = new HashMap<>();\n            return outputData;\n        } else if (outputPayload.isEmpty()) {\n            return outputData;\n        } else {\n            return outputPayload;\n        }\n    }\n\n    @JsonIgnore\n    public void setOutputData(Map<String, Object> outputData) {\n        if (outputData == null) {\n            outputData = new HashMap<>();\n        }\n        this.outputData = outputData;\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @JsonProperty(\"outputData\")\n    @Deprecated\n    public void setRawOutputData(Map<String, Object> inputData) {\n        setOutputData(inputData);\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @JsonProperty(\"outputData\")\n    @Deprecated\n    public Map<String, Object> getRawOutputData() {\n        return outputData;\n    }\n\n    public WorkflowTask getWorkflowTask() {\n        return workflowTask;\n    }\n\n    public void setWorkflowTask(WorkflowTask workflowTask) {\n        this.workflowTask = workflowTask;\n    }\n\n    public String getDomain() {\n        return domain;\n    }\n\n    public void setDomain(String domain) {\n        this.domain = domain;\n    }\n\n    public Any getInputMessage() {\n        return inputMessage;\n    }\n\n    public void setInputMessage(Any inputMessage) {\n        this.inputMessage = inputMessage;\n    }\n\n    public Any getOutputMessage() {\n        return outputMessage;\n    }\n\n    public void setOutputMessage(Any outputMessage) {\n        this.outputMessage = outputMessage;\n    }\n\n    public int getRateLimitPerFrequency() {\n        return rateLimitPerFrequency;\n    }\n\n    public void setRateLimitPerFrequency(int rateLimitPerFrequency) {\n        this.rateLimitPerFrequency = rateLimitPerFrequency;\n    }\n\n    public int getRateLimitFrequencyInSeconds() {\n        return rateLimitFrequencyInSeconds;\n    }\n\n    public void setRateLimitFrequencyInSeconds(int rateLimitFrequencyInSeconds) {\n        this.rateLimitFrequencyInSeconds = rateLimitFrequencyInSeconds;\n    }\n\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    public String getExternalOutputPayloadStoragePath() {\n        return externalOutputPayloadStoragePath;\n    }\n\n    public void setExternalOutputPayloadStoragePath(String externalOutputPayloadStoragePath) {\n        this.externalOutputPayloadStoragePath = externalOutputPayloadStoragePath;\n    }\n\n    public int getWorkflowPriority() {\n        return workflowPriority;\n    }\n\n    public void setWorkflowPriority(int workflowPriority) {\n        this.workflowPriority = workflowPriority;\n    }\n\n    public String getExecutionNameSpace() {\n        return executionNameSpace;\n    }\n\n    public void setExecutionNameSpace(String executionNameSpace) {\n        this.executionNameSpace = executionNameSpace;\n    }\n\n    public String getIsolationGroupId() {\n        return isolationGroupId;\n    }\n\n    public void setIsolationGroupId(String isolationGroupId) {\n        this.isolationGroupId = isolationGroupId;\n    }\n\n    public int getIteration() {\n        return iteration;\n    }\n\n    public void setIteration(int iteration) {\n        this.iteration = iteration;\n    }\n\n    public String getSubWorkflowId() {\n        // For backwards compatibility\n        if (StringUtils.isNotBlank(subWorkflowId)) {\n            return subWorkflowId;\n        } else {\n            return this.getOutputData() != null && this.getOutputData().get(\"subWorkflowId\") != null\n                    ? (String) this.getOutputData().get(\"subWorkflowId\")\n                    : this.getInputData() != null\n                            ? (String) this.getInputData().get(\"subWorkflowId\")\n                            : null;\n        }\n    }\n\n    public void setSubWorkflowId(String subWorkflowId) {\n        this.subWorkflowId = subWorkflowId;\n        // For backwards compatibility\n        if (this.outputData != null && this.outputData.containsKey(\"subWorkflowId\")) {\n            this.outputData.put(\"subWorkflowId\", subWorkflowId);\n        }\n    }\n\n    public boolean isSubworkflowChanged() {\n        return subworkflowChanged;\n    }\n\n    public void setSubworkflowChanged(boolean subworkflowChanged) {\n        this.subworkflowChanged = subworkflowChanged;\n    }\n\n    public void incrementPollCount() {\n        ++this.pollCount;\n    }\n\n    /**\n     * @return {@link Optional} containing the task definition if available\n     */\n    public Optional<TaskDef> getTaskDefinition() {\n        return Optional.ofNullable(this.getWorkflowTask()).map(WorkflowTask::getTaskDefinition);\n    }\n\n    public boolean isLoopOverTask() {\n        return iteration > 0;\n    }\n\n    public long getWaitTimeout() {\n        return waitTimeout;\n    }\n\n    public void setWaitTimeout(long waitTimeout) {\n        this.waitTimeout = waitTimeout;\n    }\n\n    /**\n     * @return the queueWaitTime\n     */\n    public long getQueueWaitTime() {\n        if (this.startTime > 0 && this.scheduledTime > 0) {\n            if (this.updateTime > 0 && getCallbackAfterSeconds() > 0) {\n                long waitTime =\n                        System.currentTimeMillis()\n                                - (this.updateTime + (getCallbackAfterSeconds() * 1000));\n                return waitTime > 0 ? waitTime : 0;\n            } else {\n                return this.startTime - this.scheduledTime;\n            }\n        }\n        return 0L;\n    }\n\n    /**\n     * @return a copy of the task instance\n     */\n    public TaskModel copy() {\n        TaskModel copy = new TaskModel();\n        BeanUtils.copyProperties(this, copy);\n        return copy;\n    }\n\n    public void externalizeInput(String path) {\n        this.inputPayload = this.inputData;\n        this.inputData = new HashMap<>();\n        this.externalInputPayloadStoragePath = path;\n    }\n\n    public void externalizeOutput(String path) {\n        this.outputPayload = this.outputData;\n        this.outputData = new HashMap<>();\n        this.externalOutputPayloadStoragePath = path;\n    }\n\n    public void internalizeInput(Map<String, Object> data) {\n        this.inputData = new HashMap<>();\n        this.inputPayload = data;\n    }\n\n    public void internalizeOutput(Map<String, Object> data) {\n        this.outputData = new HashMap<>();\n        this.outputPayload = data;\n    }\n\n    @Override\n    public String toString() {\n        return \"TaskModel{\"\n                + \"taskType='\"\n                + taskType\n                + '\\''\n                + \", status=\"\n                + status\n                + \", inputData=\"\n                + inputData\n                + \", referenceTaskName='\"\n                + referenceTaskName\n                + '\\''\n                + \", retryCount=\"\n                + retryCount\n                + \", seq=\"\n                + seq\n                + \", correlationId='\"\n                + correlationId\n                + '\\''\n                + \", pollCount=\"\n                + pollCount\n                + \", taskDefName='\"\n                + taskDefName\n                + '\\''\n                + \", scheduledTime=\"\n                + scheduledTime\n                + \", startTime=\"\n                + startTime\n                + \", endTime=\"\n                + endTime\n                + \", updateTime=\"\n                + updateTime\n                + \", startDelayInSeconds=\"\n                + startDelayInSeconds\n                + \", retriedTaskId='\"\n                + retriedTaskId\n                + '\\''\n                + \", retried=\"\n                + retried\n                + \", executed=\"\n                + executed\n                + \", callbackFromWorker=\"\n                + callbackFromWorker\n                + \", responseTimeoutSeconds=\"\n                + responseTimeoutSeconds\n                + \", workflowInstanceId='\"\n                + workflowInstanceId\n                + '\\''\n                + \", workflowType='\"\n                + workflowType\n                + '\\''\n                + \", taskId='\"\n                + taskId\n                + '\\''\n                + \", reasonForIncompletion='\"\n                + reasonForIncompletion\n                + '\\''\n                + \", callbackAfterSeconds=\"\n                + callbackAfterSeconds\n                + \", workerId='\"\n                + workerId\n                + '\\''\n                + \", outputData=\"\n                + outputData\n                + \", workflowTask=\"\n                + workflowTask\n                + \", domain='\"\n                + domain\n                + '\\''\n                + \", waitTimeout='\"\n                + waitTimeout\n                + '\\''\n                + \", inputMessage=\"\n                + inputMessage\n                + \", outputMessage=\"\n                + outputMessage\n                + \", rateLimitPerFrequency=\"\n                + rateLimitPerFrequency\n                + \", rateLimitFrequencyInSeconds=\"\n                + rateLimitFrequencyInSeconds\n                + \", externalInputPayloadStoragePath='\"\n                + externalInputPayloadStoragePath\n                + '\\''\n                + \", externalOutputPayloadStoragePath='\"\n                + externalOutputPayloadStoragePath\n                + '\\''\n                + \", workflowPriority=\"\n                + workflowPriority\n                + \", executionNameSpace='\"\n                + executionNameSpace\n                + '\\''\n                + \", isolationGroupId='\"\n                + isolationGroupId\n                + '\\''\n                + \", iteration=\"\n                + iteration\n                + \", subWorkflowId='\"\n                + subWorkflowId\n                + '\\''\n                + \", subworkflowChanged=\"\n                + subworkflowChanged\n                + '}';\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        TaskModel taskModel = (TaskModel) o;\n        return getRetryCount() == taskModel.getRetryCount()\n                && getSeq() == taskModel.getSeq()\n                && getPollCount() == taskModel.getPollCount()\n                && getScheduledTime() == taskModel.getScheduledTime()\n                && getStartTime() == taskModel.getStartTime()\n                && getEndTime() == taskModel.getEndTime()\n                && getUpdateTime() == taskModel.getUpdateTime()\n                && getStartDelayInSeconds() == taskModel.getStartDelayInSeconds()\n                && isRetried() == taskModel.isRetried()\n                && isExecuted() == taskModel.isExecuted()\n                && isCallbackFromWorker() == taskModel.isCallbackFromWorker()\n                && getResponseTimeoutSeconds() == taskModel.getResponseTimeoutSeconds()\n                && getCallbackAfterSeconds() == taskModel.getCallbackAfterSeconds()\n                && getRateLimitPerFrequency() == taskModel.getRateLimitPerFrequency()\n                && getRateLimitFrequencyInSeconds() == taskModel.getRateLimitFrequencyInSeconds()\n                && getWorkflowPriority() == taskModel.getWorkflowPriority()\n                && getIteration() == taskModel.getIteration()\n                && isSubworkflowChanged() == taskModel.isSubworkflowChanged()\n                && Objects.equals(getTaskType(), taskModel.getTaskType())\n                && getStatus() == taskModel.getStatus()\n                && Objects.equals(getInputData(), taskModel.getInputData())\n                && Objects.equals(getReferenceTaskName(), taskModel.getReferenceTaskName())\n                && Objects.equals(getCorrelationId(), taskModel.getCorrelationId())\n                && Objects.equals(getTaskDefName(), taskModel.getTaskDefName())\n                && Objects.equals(getRetriedTaskId(), taskModel.getRetriedTaskId())\n                && Objects.equals(getWorkflowInstanceId(), taskModel.getWorkflowInstanceId())\n                && Objects.equals(getWorkflowType(), taskModel.getWorkflowType())\n                && Objects.equals(getTaskId(), taskModel.getTaskId())\n                && Objects.equals(getReasonForIncompletion(), taskModel.getReasonForIncompletion())\n                && Objects.equals(getWorkerId(), taskModel.getWorkerId())\n                && Objects.equals(getWaitTimeout(), taskModel.getWaitTimeout())\n                && Objects.equals(outputData, taskModel.outputData)\n                && Objects.equals(outputPayload, taskModel.outputPayload)\n                && Objects.equals(getWorkflowTask(), taskModel.getWorkflowTask())\n                && Objects.equals(getDomain(), taskModel.getDomain())\n                && Objects.equals(getInputMessage(), taskModel.getInputMessage())\n                && Objects.equals(getOutputMessage(), taskModel.getOutputMessage())\n                && Objects.equals(\n                        getExternalInputPayloadStoragePath(),\n                        taskModel.getExternalInputPayloadStoragePath())\n                && Objects.equals(\n                        getExternalOutputPayloadStoragePath(),\n                        taskModel.getExternalOutputPayloadStoragePath())\n                && Objects.equals(getExecutionNameSpace(), taskModel.getExecutionNameSpace())\n                && Objects.equals(getIsolationGroupId(), taskModel.getIsolationGroupId())\n                && Objects.equals(getSubWorkflowId(), taskModel.getSubWorkflowId());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                getTaskType(),\n                getStatus(),\n                getInputData(),\n                getReferenceTaskName(),\n                getRetryCount(),\n                getSeq(),\n                getCorrelationId(),\n                getPollCount(),\n                getTaskDefName(),\n                getScheduledTime(),\n                getStartTime(),\n                getEndTime(),\n                getUpdateTime(),\n                getStartDelayInSeconds(),\n                getRetriedTaskId(),\n                isRetried(),\n                isExecuted(),\n                isCallbackFromWorker(),\n                getResponseTimeoutSeconds(),\n                getWorkflowInstanceId(),\n                getWorkflowType(),\n                getTaskId(),\n                getReasonForIncompletion(),\n                getCallbackAfterSeconds(),\n                getWorkerId(),\n                getWaitTimeout(),\n                outputData,\n                outputPayload,\n                getWorkflowTask(),\n                getDomain(),\n                getInputMessage(),\n                getOutputMessage(),\n                getRateLimitPerFrequency(),\n                getRateLimitFrequencyInSeconds(),\n                getExternalInputPayloadStoragePath(),\n                getExternalOutputPayloadStoragePath(),\n                getWorkflowPriority(),\n                getExecutionNameSpace(),\n                getIsolationGroupId(),\n                getIteration(),\n                getSubWorkflowId(),\n                isSubworkflowChanged());\n    }\n\n    public Task toTask() {\n        Task task = new Task();\n        BeanUtils.copyProperties(this, task);\n        task.setStatus(Task.Status.valueOf(status.name()));\n\n        // ensure that input/output is properly represented\n        if (externalInputPayloadStoragePath != null) {\n            task.setInputData(new HashMap<>());\n        }\n        if (externalOutputPayloadStoragePath != null) {\n            task.setOutputData(new HashMap<>());\n        }\n        return task;\n    }\n\n    public static Task.Status mapToTaskStatus(TaskModel.Status status) {\n        return Task.Status.valueOf(status.name());\n    }\n\n    public void addInput(String key, Object value) {\n        this.inputData.put(key, value);\n    }\n\n    public void addInput(Map<String, Object> inputData) {\n        if (inputData != null) {\n            this.inputData.putAll(inputData);\n        }\n    }\n\n    public void addOutput(String key, Object value) {\n        this.outputData.put(key, value);\n    }\n\n    public void addOutput(Map<String, Object> outputData) {\n        if (outputData != null) {\n            this.outputData.putAll(outputData);\n        }\n    }\n\n    public void clearOutput() {\n        this.outputData.clear();\n        this.outputPayload.clear();\n        this.externalOutputPayloadStoragePath = null;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/model/WorkflowModel.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.model;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.beans.BeanUtils;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.core.utils.Utils;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class WorkflowModel {\n\n    public enum Status {\n        RUNNING(false, false),\n        COMPLETED(true, true),\n        FAILED(true, false),\n        TIMED_OUT(true, false),\n        TERMINATED(true, false),\n        PAUSED(false, true);\n\n        private final boolean terminal;\n        private final boolean successful;\n\n        Status(boolean terminal, boolean successful) {\n            this.terminal = terminal;\n            this.successful = successful;\n        }\n\n        public boolean isTerminal() {\n            return terminal;\n        }\n\n        public boolean isSuccessful() {\n            return successful;\n        }\n    }\n\n    private Status status = Status.RUNNING;\n\n    private long endTime;\n\n    private String workflowId;\n\n    private String parentWorkflowId;\n\n    private String parentWorkflowTaskId;\n\n    private List<TaskModel> tasks = new LinkedList<>();\n\n    private String correlationId;\n\n    private String reRunFromWorkflowId;\n\n    private String reasonForIncompletion;\n\n    private String event;\n\n    private Map<String, String> taskToDomain = new HashMap<>();\n\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private Set<String> failedReferenceTaskNames = new HashSet<>();\n\n    @JsonInclude(JsonInclude.Include.NON_EMPTY)\n    private Set<String> failedTaskNames = new HashSet<>();\n\n    private WorkflowDef workflowDefinition;\n\n    private String externalInputPayloadStoragePath;\n\n    private String externalOutputPayloadStoragePath;\n\n    private int priority;\n\n    private Map<String, Object> variables = new HashMap<>();\n\n    private long lastRetriedTime;\n\n    private String ownerApp;\n\n    private Long createTime;\n\n    private Long updatedTime;\n\n    private String createdBy;\n\n    private String updatedBy;\n\n    // Capture the failed taskId if the workflow execution failed because of task failure\n    private String failedTaskId;\n\n    private Status previousStatus;\n\n    @JsonIgnore private Map<String, Object> input = new HashMap<>();\n\n    @JsonIgnore private Map<String, Object> output = new HashMap<>();\n\n    @JsonIgnore private Map<String, Object> inputPayload = new HashMap<>();\n\n    @JsonIgnore private Map<String, Object> outputPayload = new HashMap<>();\n\n    public Status getPreviousStatus() {\n        return previousStatus;\n    }\n\n    public void setPreviousStatus(Status status) {\n        this.previousStatus = status;\n    }\n\n    public Status getStatus() {\n        return status;\n    }\n\n    public void setStatus(Status status) {\n        // update previous status if current status changed\n        if (this.status != status) {\n            setPreviousStatus(this.status);\n        }\n        this.status = status;\n    }\n\n    public long getEndTime() {\n        return endTime;\n    }\n\n    public void setEndTime(long endTime) {\n        this.endTime = endTime;\n    }\n\n    public String getWorkflowId() {\n        return workflowId;\n    }\n\n    public void setWorkflowId(String workflowId) {\n        this.workflowId = workflowId;\n    }\n\n    public String getParentWorkflowId() {\n        return parentWorkflowId;\n    }\n\n    public void setParentWorkflowId(String parentWorkflowId) {\n        this.parentWorkflowId = parentWorkflowId;\n    }\n\n    public String getParentWorkflowTaskId() {\n        return parentWorkflowTaskId;\n    }\n\n    public void setParentWorkflowTaskId(String parentWorkflowTaskId) {\n        this.parentWorkflowTaskId = parentWorkflowTaskId;\n    }\n\n    public List<TaskModel> getTasks() {\n        return tasks;\n    }\n\n    public void setTasks(List<TaskModel> tasks) {\n        this.tasks = tasks;\n    }\n\n    @JsonIgnore\n    public Map<String, Object> getInput() {\n        if (!inputPayload.isEmpty() && !input.isEmpty()) {\n            input.putAll(inputPayload);\n            inputPayload = new HashMap<>();\n            return input;\n        } else if (inputPayload.isEmpty()) {\n            return input;\n        } else {\n            return inputPayload;\n        }\n    }\n\n    @JsonIgnore\n    public void setInput(Map<String, Object> input) {\n        if (input == null) {\n            input = new HashMap<>();\n        }\n        this.input = input;\n    }\n\n    @JsonIgnore\n    public Map<String, Object> getOutput() {\n        if (!outputPayload.isEmpty() && !output.isEmpty()) {\n            output.putAll(outputPayload);\n            outputPayload = new HashMap<>();\n            return output;\n        } else if (outputPayload.isEmpty()) {\n            return output;\n        } else {\n            return outputPayload;\n        }\n    }\n\n    @JsonIgnore\n    public void setOutput(Map<String, Object> output) {\n        if (output == null) {\n            output = new HashMap<>();\n        }\n        this.output = output;\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @Deprecated\n    @JsonProperty(\"input\")\n    public Map<String, Object> getRawInput() {\n        return input;\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @Deprecated\n    @JsonProperty(\"input\")\n    public void setRawInput(Map<String, Object> input) {\n        setInput(input);\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @Deprecated\n    @JsonProperty(\"output\")\n    public Map<String, Object> getRawOutput() {\n        return output;\n    }\n\n    /**\n     * @deprecated Used only for JSON serialization and deserialization.\n     */\n    @Deprecated\n    @JsonProperty(\"output\")\n    public void setRawOutput(Map<String, Object> output) {\n        setOutput(output);\n    }\n\n    public String getCorrelationId() {\n        return correlationId;\n    }\n\n    public void setCorrelationId(String correlationId) {\n        this.correlationId = correlationId;\n    }\n\n    public String getReRunFromWorkflowId() {\n        return reRunFromWorkflowId;\n    }\n\n    public void setReRunFromWorkflowId(String reRunFromWorkflowId) {\n        this.reRunFromWorkflowId = reRunFromWorkflowId;\n    }\n\n    public String getReasonForIncompletion() {\n        return reasonForIncompletion;\n    }\n\n    public void setReasonForIncompletion(String reasonForIncompletion) {\n        this.reasonForIncompletion = reasonForIncompletion;\n    }\n\n    public String getEvent() {\n        return event;\n    }\n\n    public void setEvent(String event) {\n        this.event = event;\n    }\n\n    public Map<String, String> getTaskToDomain() {\n        return taskToDomain;\n    }\n\n    public void setTaskToDomain(Map<String, String> taskToDomain) {\n        this.taskToDomain = taskToDomain;\n    }\n\n    public Set<String> getFailedReferenceTaskNames() {\n        return failedReferenceTaskNames;\n    }\n\n    public void setFailedReferenceTaskNames(Set<String> failedReferenceTaskNames) {\n        this.failedReferenceTaskNames = failedReferenceTaskNames;\n    }\n\n    public Set<String> getFailedTaskNames() {\n        return failedTaskNames;\n    }\n\n    public void setFailedTaskNames(Set<String> failedTaskNames) {\n        this.failedTaskNames = failedTaskNames;\n    }\n\n    public WorkflowDef getWorkflowDefinition() {\n        return workflowDefinition;\n    }\n\n    public void setWorkflowDefinition(WorkflowDef workflowDefinition) {\n        this.workflowDefinition = workflowDefinition;\n    }\n\n    public String getExternalInputPayloadStoragePath() {\n        return externalInputPayloadStoragePath;\n    }\n\n    public void setExternalInputPayloadStoragePath(String externalInputPayloadStoragePath) {\n        this.externalInputPayloadStoragePath = externalInputPayloadStoragePath;\n    }\n\n    public String getExternalOutputPayloadStoragePath() {\n        return externalOutputPayloadStoragePath;\n    }\n\n    public void setExternalOutputPayloadStoragePath(String externalOutputPayloadStoragePath) {\n        this.externalOutputPayloadStoragePath = externalOutputPayloadStoragePath;\n    }\n\n    public int getPriority() {\n        return priority;\n    }\n\n    public void setPriority(int priority) {\n        if (priority < 0 || priority > 99) {\n            throw new IllegalArgumentException(\"priority MUST be between 0 and 99 (inclusive)\");\n        }\n        this.priority = priority;\n    }\n\n    public Map<String, Object> getVariables() {\n        return variables;\n    }\n\n    public void setVariables(Map<String, Object> variables) {\n        this.variables = variables;\n    }\n\n    public long getLastRetriedTime() {\n        return lastRetriedTime;\n    }\n\n    public void setLastRetriedTime(long lastRetriedTime) {\n        this.lastRetriedTime = lastRetriedTime;\n    }\n\n    public String getOwnerApp() {\n        return ownerApp;\n    }\n\n    public void setOwnerApp(String ownerApp) {\n        this.ownerApp = ownerApp;\n    }\n\n    public Long getCreateTime() {\n        return createTime;\n    }\n\n    public void setCreateTime(Long createTime) {\n        this.createTime = createTime;\n    }\n\n    public Long getUpdatedTime() {\n        return updatedTime;\n    }\n\n    public void setUpdatedTime(Long updatedTime) {\n        this.updatedTime = updatedTime;\n    }\n\n    public String getCreatedBy() {\n        return createdBy;\n    }\n\n    public void setCreatedBy(String createdBy) {\n        this.createdBy = createdBy;\n    }\n\n    public String getUpdatedBy() {\n        return updatedBy;\n    }\n\n    public void setUpdatedBy(String updatedBy) {\n        this.updatedBy = updatedBy;\n    }\n\n    public String getFailedTaskId() {\n        return failedTaskId;\n    }\n\n    public void setFailedTaskId(String failedTaskId) {\n        this.failedTaskId = failedTaskId;\n    }\n\n    /**\n     * Convenience method for accessing the workflow definition name.\n     *\n     * @return the workflow definition name.\n     */\n    public String getWorkflowName() {\n        Utils.checkNotNull(workflowDefinition, \"Workflow definition is null\");\n        return workflowDefinition.getName();\n    }\n\n    /**\n     * Convenience method for accessing the workflow definition version.\n     *\n     * @return the workflow definition version.\n     */\n    public int getWorkflowVersion() {\n        Utils.checkNotNull(workflowDefinition, \"Workflow definition is null\");\n        return workflowDefinition.getVersion();\n    }\n\n    public boolean hasParent() {\n        return StringUtils.isNotEmpty(parentWorkflowId);\n    }\n\n    /**\n     * A string representation of all relevant fields that identify this workflow. Intended for use\n     * in log and other system generated messages.\n     */\n    public String toShortString() {\n        String name = workflowDefinition != null ? workflowDefinition.getName() : null;\n        Integer version = workflowDefinition != null ? workflowDefinition.getVersion() : null;\n        return String.format(\"%s.%s/%s\", name, version, workflowId);\n    }\n\n    public TaskModel getTaskByRefName(String refName) {\n        if (refName == null) {\n            throw new RuntimeException(\n                    \"refName passed is null.  Check the workflow execution.  For dynamic tasks, make sure referenceTaskName is set to a not null value\");\n        }\n        LinkedList<TaskModel> found = new LinkedList<>();\n        for (TaskModel task : tasks) {\n            if (task.getReferenceTaskName() == null) {\n                throw new RuntimeException(\n                        \"Task \"\n                                + task.getTaskDefName()\n                                + \", seq=\"\n                                + task.getSeq()\n                                + \" does not have reference name specified.\");\n            }\n            if (task.getReferenceTaskName().equals(refName)) {\n                found.add(task);\n            }\n        }\n        if (found.isEmpty()) {\n            return null;\n        }\n        return found.getLast();\n    }\n\n    public void externalizeInput(String path) {\n        this.inputPayload = this.input;\n        this.input = new HashMap<>();\n        this.externalInputPayloadStoragePath = path;\n    }\n\n    public void externalizeOutput(String path) {\n        this.outputPayload = this.output;\n        this.output = new HashMap<>();\n        this.externalOutputPayloadStoragePath = path;\n    }\n\n    public void internalizeInput(Map<String, Object> data) {\n        this.input = new HashMap<>();\n        this.inputPayload = data;\n    }\n\n    public void internalizeOutput(Map<String, Object> data) {\n        this.output = new HashMap<>();\n        this.outputPayload = data;\n    }\n\n    @Override\n    public String toString() {\n        String name = workflowDefinition != null ? workflowDefinition.getName() : null;\n        Integer version = workflowDefinition != null ? workflowDefinition.getVersion() : null;\n        return String.format(\"%s.%s/%s.%s\", name, version, workflowId, status);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        WorkflowModel that = (WorkflowModel) o;\n        return getEndTime() == that.getEndTime()\n                && getPriority() == that.getPriority()\n                && getLastRetriedTime() == that.getLastRetriedTime()\n                && getStatus() == that.getStatus()\n                && Objects.equals(getWorkflowId(), that.getWorkflowId())\n                && Objects.equals(getParentWorkflowId(), that.getParentWorkflowId())\n                && Objects.equals(getParentWorkflowTaskId(), that.getParentWorkflowTaskId())\n                && Objects.equals(getTasks(), that.getTasks())\n                && Objects.equals(getInput(), that.getInput())\n                && Objects.equals(output, that.output)\n                && Objects.equals(outputPayload, that.outputPayload)\n                && Objects.equals(getCorrelationId(), that.getCorrelationId())\n                && Objects.equals(getReRunFromWorkflowId(), that.getReRunFromWorkflowId())\n                && Objects.equals(getReasonForIncompletion(), that.getReasonForIncompletion())\n                && Objects.equals(getEvent(), that.getEvent())\n                && Objects.equals(getTaskToDomain(), that.getTaskToDomain())\n                && Objects.equals(getFailedReferenceTaskNames(), that.getFailedReferenceTaskNames())\n                && Objects.equals(getFailedTaskNames(), that.getFailedTaskNames())\n                && Objects.equals(getWorkflowDefinition(), that.getWorkflowDefinition())\n                && Objects.equals(\n                        getExternalInputPayloadStoragePath(),\n                        that.getExternalInputPayloadStoragePath())\n                && Objects.equals(\n                        getExternalOutputPayloadStoragePath(),\n                        that.getExternalOutputPayloadStoragePath())\n                && Objects.equals(getVariables(), that.getVariables())\n                && Objects.equals(getOwnerApp(), that.getOwnerApp())\n                && Objects.equals(getCreateTime(), that.getCreateTime())\n                && Objects.equals(getUpdatedTime(), that.getUpdatedTime())\n                && Objects.equals(getCreatedBy(), that.getCreatedBy())\n                && Objects.equals(getUpdatedBy(), that.getUpdatedBy());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(\n                getStatus(),\n                getEndTime(),\n                getWorkflowId(),\n                getParentWorkflowId(),\n                getParentWorkflowTaskId(),\n                getTasks(),\n                getInput(),\n                output,\n                outputPayload,\n                getCorrelationId(),\n                getReRunFromWorkflowId(),\n                getReasonForIncompletion(),\n                getEvent(),\n                getTaskToDomain(),\n                getFailedReferenceTaskNames(),\n                getFailedTaskNames(),\n                getWorkflowDefinition(),\n                getExternalInputPayloadStoragePath(),\n                getExternalOutputPayloadStoragePath(),\n                getPriority(),\n                getVariables(),\n                getLastRetriedTime(),\n                getOwnerApp(),\n                getCreateTime(),\n                getUpdatedTime(),\n                getCreatedBy(),\n                getUpdatedBy());\n    }\n\n    public Workflow toWorkflow() {\n        Workflow workflow = new Workflow();\n        BeanUtils.copyProperties(this, workflow);\n        workflow.setStatus(Workflow.WorkflowStatus.valueOf(this.status.name()));\n        workflow.setTasks(tasks.stream().map(TaskModel::toTask).collect(Collectors.toList()));\n        workflow.setUpdateTime(this.updatedTime);\n\n        // ensure that input/output is properly represented\n        if (externalInputPayloadStoragePath != null) {\n            workflow.setInput(new HashMap<>());\n        }\n        if (externalOutputPayloadStoragePath != null) {\n            workflow.setOutput(new HashMap<>());\n        }\n        return workflow;\n    }\n\n    public void addInput(String key, Object value) {\n        this.input.put(key, value);\n    }\n\n    public void addInput(Map<String, Object> inputData) {\n        if (inputData != null) {\n            this.input.putAll(inputData);\n        }\n    }\n\n    public void addOutput(String key, Object value) {\n        this.output.put(key, value);\n    }\n\n    public void addOutput(Map<String, Object> outputData) {\n        if (outputData != null) {\n            this.output.putAll(outputData);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/AdminService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.validation.constraints.NotEmpty;\n\nimport org.springframework.validation.annotation.Validated;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\n\n@Validated\npublic interface AdminService {\n\n    /**\n     * Queue up all the running workflows for sweep.\n     *\n     * @param workflowId Id of the workflow\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    String requeueSweep(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId);\n\n    /**\n     * Get all the configuration parameters.\n     *\n     * @return all the configuration parameters.\n     */\n    Map<String, Object> getAllConfig();\n\n    /**\n     * Get the list of pending tasks for a given task type.\n     *\n     * @param taskType Name of the task\n     * @param start Start index of pagination\n     * @param count Number of entries\n     * @return list of pending {@link Task}\n     */\n    List<Task> getListOfPendingTask(\n            @NotEmpty(message = \"TaskType cannot be null or empty.\") String taskType,\n            Integer start,\n            Integer count);\n\n    /**\n     * Verify that the Workflow is consistent, and run repairs as needed.\n     *\n     * @param workflowId id of the workflow to be returned\n     * @return true, if repair was successful\n     */\n    boolean verifyAndRepairWorkflowConsistency(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId);\n\n    /**\n     * Get registered queues.\n     *\n     * @param verbose `true|false` for verbose logs\n     * @return map of event queues\n     */\n    Map<String, ?> getEventQueues(boolean verbose);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/AdminServiceImpl.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.springframework.boot.info.BuildProperties;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.Audit;\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.events.EventQueueManager;\nimport com.netflix.conductor.core.reconciliation.WorkflowRepairService;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.dao.QueueDAO;\n\n@Audit\n@Trace\n@Service\npublic class AdminServiceImpl implements AdminService {\n\n    private final ConductorProperties properties;\n    private final ExecutionService executionService;\n    private final QueueDAO queueDAO;\n    private final WorkflowRepairService workflowRepairService;\n    private final EventQueueManager eventQueueManager;\n    private final BuildProperties buildProperties;\n\n    public AdminServiceImpl(\n            ConductorProperties properties,\n            ExecutionService executionService,\n            QueueDAO queueDAO,\n            Optional<WorkflowRepairService> workflowRepairService,\n            Optional<EventQueueManager> eventQueueManager,\n            Optional<BuildProperties> buildProperties) {\n        this.properties = properties;\n        this.executionService = executionService;\n        this.queueDAO = queueDAO;\n        this.workflowRepairService = workflowRepairService.orElse(null);\n        this.eventQueueManager = eventQueueManager.orElse(null);\n        this.buildProperties = buildProperties.orElse(null);\n    }\n\n    /**\n     * Get all the configuration parameters.\n     *\n     * @return all the configuration parameters.\n     */\n    public Map<String, Object> getAllConfig() {\n        Map<String, Object> configs = properties.getAll();\n        configs.putAll(getBuildProperties());\n        return configs;\n    }\n\n    /**\n     * Get all build properties\n     *\n     * @return all the build properties.\n     */\n    private Map<String, Object> getBuildProperties() {\n        if (buildProperties == null) return Collections.emptyMap();\n        Map<String, Object> buildProps = new HashMap<>();\n        buildProps.put(\"version\", buildProperties.getVersion());\n        buildProps.put(\"buildDate\", buildProperties.getTime());\n        return buildProps;\n    }\n\n    /**\n     * Get the list of pending tasks for a given task type.\n     *\n     * @param taskType Name of the task\n     * @param start Start index of pagination\n     * @param count Number of entries\n     * @return list of pending {@link Task}\n     */\n    public List<Task> getListOfPendingTask(String taskType, Integer start, Integer count) {\n        List<Task> tasks = executionService.getPendingTasksForTaskType(taskType);\n        int total = start + count;\n        total = Math.min(tasks.size(), total);\n        if (start > tasks.size()) {\n            start = tasks.size();\n        }\n        return tasks.subList(start, total);\n    }\n\n    @Override\n    public boolean verifyAndRepairWorkflowConsistency(String workflowId) {\n        if (workflowRepairService == null) {\n            throw new IllegalStateException(\n                    WorkflowRepairService.class.getSimpleName() + \" is disabled.\");\n        }\n        return workflowRepairService.verifyAndRepairWorkflow(workflowId, true);\n    }\n\n    /**\n     * Queue up the workflow for sweep.\n     *\n     * @param workflowId Id of the workflow\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    public String requeueSweep(String workflowId) {\n        boolean pushed =\n                queueDAO.pushIfNotExists(\n                        Utils.DECIDER_QUEUE,\n                        workflowId,\n                        properties.getWorkflowOffsetTimeout().getSeconds());\n        return pushed + \".\" + workflowId;\n    }\n\n    /**\n     * Get registered queues.\n     *\n     * @param verbose `true|false` for verbose logs\n     * @return map of event queues\n     */\n    public Map<String, ?> getEventQueues(boolean verbose) {\n        if (eventQueueManager == null) {\n            throw new IllegalStateException(\"Event processing is DISABLED\");\n        }\n        return (verbose ? eventQueueManager.getQueueSizes() : eventQueueManager.getQueues());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/EventService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport org.springframework.validation.annotation.Validated;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\n\n@Validated\npublic interface EventService {\n\n    /**\n     * Add a new event handler.\n     *\n     * @param eventHandler Instance of {@link EventHandler}\n     */\n    void addEventHandler(\n            @NotNull(message = \"EventHandler cannot be null.\") @Valid EventHandler eventHandler);\n\n    /**\n     * Update an existing event handler.\n     *\n     * @param eventHandler Instance of {@link EventHandler}\n     */\n    void updateEventHandler(\n            @NotNull(message = \"EventHandler cannot be null.\") @Valid EventHandler eventHandler);\n\n    /**\n     * Remove an event handler.\n     *\n     * @param name Event name\n     */\n    void removeEventHandlerStatus(\n            @NotEmpty(message = \"EventHandler name cannot be null or empty.\") String name);\n\n    /**\n     * Get all the event handlers.\n     *\n     * @return list of {@link EventHandler}\n     */\n    List<EventHandler> getEventHandlers();\n\n    /**\n     * Get event handlers for a given event.\n     *\n     * @param event Event Name\n     * @param activeOnly `true|false` for active only events\n     * @return list of {@link EventHandler}\n     */\n    List<EventHandler> getEventHandlersForEvent(\n            @NotEmpty(message = \"Event cannot be null or empty.\") String event, boolean activeOnly);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/EventServiceImpl.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\n\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.core.events.EventQueues;\n\n@Service\npublic class EventServiceImpl implements EventService {\n\n    private final MetadataService metadataService;\n\n    public EventServiceImpl(MetadataService metadataService, EventQueues eventQueues) {\n        this.metadataService = metadataService;\n    }\n\n    /**\n     * Add a new event handler.\n     *\n     * @param eventHandler Instance of {@link EventHandler}\n     */\n    public void addEventHandler(EventHandler eventHandler) {\n        metadataService.addEventHandler(eventHandler);\n    }\n\n    /**\n     * Update an existing event handler.\n     *\n     * @param eventHandler Instance of {@link EventHandler}\n     */\n    public void updateEventHandler(EventHandler eventHandler) {\n        metadataService.updateEventHandler(eventHandler);\n    }\n\n    /**\n     * Remove an event handler.\n     *\n     * @param name Event name\n     */\n    public void removeEventHandlerStatus(String name) {\n        metadataService.removeEventHandlerStatus(name);\n    }\n\n    /**\n     * Get all the event handlers.\n     *\n     * @return list of {@link EventHandler}\n     */\n    public List<EventHandler> getEventHandlers() {\n        return metadataService.getAllEventHandlers();\n    }\n\n    /**\n     * Get event handlers for a given event.\n     *\n     * @param event Event Name\n     * @param activeOnly `true|false` for active only events\n     * @return list of {@link EventHandler}\n     */\n    public List<EventHandler> getEventHandlersForEvent(String event, boolean activeOnly) {\n        return metadataService.getEventHandlersForEvent(event, activeOnly);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/ExecutionLockService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.concurrent.TimeUnit;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.sync.Lock;\nimport com.netflix.conductor.metrics.Monitors;\n\n@Service\n@Trace\npublic class ExecutionLockService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionLockService.class);\n    private final ConductorProperties properties;\n    private final Lock lock;\n    private final long lockLeaseTime;\n    private final long lockTimeToTry;\n\n    @Autowired\n    public ExecutionLockService(ConductorProperties properties, Lock lock) {\n        this.properties = properties;\n        this.lock = lock;\n        this.lockLeaseTime = properties.getLockLeaseTime().toMillis();\n        this.lockTimeToTry = properties.getLockTimeToTry().toMillis();\n    }\n\n    /**\n     * Tries to acquire lock with reasonable timeToTry duration and lease time. Exits if a lock\n     * cannot be acquired. Considering that the workflow decide can be triggered through multiple\n     * entry points, and periodically through the sweeper service, do not block on acquiring the\n     * lock, as the order of execution of decides on a workflow doesn't matter.\n     *\n     * @param lockId\n     * @return\n     */\n    public boolean acquireLock(String lockId) {\n        return acquireLock(lockId, lockTimeToTry, lockLeaseTime);\n    }\n\n    public boolean acquireLock(String lockId, long timeToTryMs) {\n        return acquireLock(lockId, timeToTryMs, lockLeaseTime);\n    }\n\n    public boolean acquireLock(String lockId, long timeToTryMs, long leaseTimeMs) {\n        if (properties.isWorkflowExecutionLockEnabled()) {\n            if (!lock.acquireLock(lockId, timeToTryMs, leaseTimeMs, TimeUnit.MILLISECONDS)) {\n                LOGGER.debug(\n                        \"Thread {} failed to acquire lock to lockId {}.\",\n                        Thread.currentThread().getId(),\n                        lockId);\n                Monitors.recordAcquireLockUnsuccessful();\n                return false;\n            }\n            LOGGER.debug(\n                    \"Thread {} acquired lock to lockId {}.\",\n                    Thread.currentThread().getId(),\n                    lockId);\n        }\n        return true;\n    }\n\n    /**\n     * Blocks until it gets the lock for workflowId\n     *\n     * @param lockId\n     */\n    public void waitForLock(String lockId) {\n        if (properties.isWorkflowExecutionLockEnabled()) {\n            lock.acquireLock(lockId);\n            LOGGER.debug(\n                    \"Thread {} acquired lock to lockId {}.\",\n                    Thread.currentThread().getId(),\n                    lockId);\n        }\n    }\n\n    public void releaseLock(String lockId) {\n        if (properties.isWorkflowExecutionLockEnabled()) {\n            lock.releaseLock(lockId);\n            LOGGER.debug(\n                    \"Thread {} released lock to lockId {}.\",\n                    Thread.currentThread().getId(),\n                    lockId);\n        }\n    }\n\n    public void deleteLock(String lockId) {\n        if (properties.isWorkflowExecutionLockEnabled()) {\n            lock.deleteLock(lockId);\n            LOGGER.debug(\"Thread {} deleted lockId {}.\", Thread.currentThread().getId(), lockId);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/ExecutionService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.*;\nimport com.netflix.conductor.common.run.*;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage.Operation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\n\n@Trace\n@Service\npublic class ExecutionService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionService.class);\n\n    private final WorkflowExecutor workflowExecutor;\n    private final ExecutionDAOFacade executionDAOFacade;\n    private final QueueDAO queueDAO;\n    private final ExternalPayloadStorage externalPayloadStorage;\n    private final SystemTaskRegistry systemTaskRegistry;\n\n    private final long queueTaskMessagePostponeSecs;\n\n    private static final int MAX_POLL_TIMEOUT_MS = 5000;\n    private static final int POLL_COUNT_ONE = 1;\n    private static final int POLLING_TIMEOUT_IN_MS = 100;\n\n    public ExecutionService(\n            WorkflowExecutor workflowExecutor,\n            ExecutionDAOFacade executionDAOFacade,\n            QueueDAO queueDAO,\n            ConductorProperties properties,\n            ExternalPayloadStorage externalPayloadStorage,\n            SystemTaskRegistry systemTaskRegistry) {\n        this.workflowExecutor = workflowExecutor;\n        this.executionDAOFacade = executionDAOFacade;\n        this.queueDAO = queueDAO;\n        this.externalPayloadStorage = externalPayloadStorage;\n\n        this.queueTaskMessagePostponeSecs =\n                properties.getTaskExecutionPostponeDuration().getSeconds();\n        this.systemTaskRegistry = systemTaskRegistry;\n    }\n\n    public Task poll(String taskType, String workerId) {\n        return poll(taskType, workerId, null);\n    }\n\n    public Task poll(String taskType, String workerId, String domain) {\n\n        List<Task> tasks = poll(taskType, workerId, domain, 1, 100);\n        if (tasks.isEmpty()) {\n            return null;\n        }\n        return tasks.get(0);\n    }\n\n    public List<Task> poll(String taskType, String workerId, int count, int timeoutInMilliSecond) {\n        return poll(taskType, workerId, null, count, timeoutInMilliSecond);\n    }\n\n    public List<Task> poll(\n            String taskType, String workerId, String domain, int count, int timeoutInMilliSecond) {\n        if (timeoutInMilliSecond > MAX_POLL_TIMEOUT_MS) {\n            throw new IllegalArgumentException(\n                    \"Long Poll Timeout value cannot be more than 5 seconds\");\n        }\n        String queueName = QueueUtils.getQueueName(taskType, domain, null, null);\n\n        List<String> taskIds = new LinkedList<>();\n        List<Task> tasks = new LinkedList<>();\n        try {\n            taskIds = queueDAO.pop(queueName, count, timeoutInMilliSecond);\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Error polling for task: {} from worker: {} in domain: {}, count: {}\",\n                    taskType,\n                    workerId,\n                    domain,\n                    count,\n                    e);\n            Monitors.error(this.getClass().getCanonicalName(), \"taskPoll\");\n            Monitors.recordTaskPollError(taskType, domain, e.getClass().getSimpleName());\n        }\n\n        for (String taskId : taskIds) {\n            try {\n                TaskModel taskModel = executionDAOFacade.getTaskModel(taskId);\n                if (taskModel == null || taskModel.getStatus().isTerminal()) {\n                    // Remove taskId(s) without a valid Task/terminal state task from the queue\n                    queueDAO.remove(queueName, taskId);\n                    LOGGER.debug(\"Removed task: {} from the queue: {}\", taskId, queueName);\n                    continue;\n                }\n\n                if (executionDAOFacade.exceedsInProgressLimit(taskModel)) {\n                    // Postpone this message, so that it would be available for poll again.\n                    queueDAO.postpone(\n                            queueName,\n                            taskId,\n                            taskModel.getWorkflowPriority(),\n                            queueTaskMessagePostponeSecs);\n                    LOGGER.debug(\n                            \"Postponed task: {} in queue: {} by {} seconds\",\n                            taskId,\n                            queueName,\n                            queueTaskMessagePostponeSecs);\n                    continue;\n                }\n                TaskDef taskDef =\n                        taskModel.getTaskDefinition().isPresent()\n                                ? taskModel.getTaskDefinition().get()\n                                : null;\n                if (taskModel.getRateLimitPerFrequency() > 0\n                        && executionDAOFacade.exceedsRateLimitPerFrequency(taskModel, taskDef)) {\n                    // Postpone this message, so that it would be available for poll again.\n                    queueDAO.postpone(\n                            queueName,\n                            taskId,\n                            taskModel.getWorkflowPriority(),\n                            queueTaskMessagePostponeSecs);\n                    LOGGER.debug(\n                            \"RateLimit Execution limited for {}:{}, limit:{}\",\n                            taskId,\n                            taskModel.getTaskDefName(),\n                            taskModel.getRateLimitPerFrequency());\n                    continue;\n                }\n\n                taskModel.setStatus(TaskModel.Status.IN_PROGRESS);\n                if (taskModel.getStartTime() == 0) {\n                    taskModel.setStartTime(System.currentTimeMillis());\n                    Monitors.recordQueueWaitTime(\n                            taskModel.getTaskDefName(), taskModel.getQueueWaitTime());\n                }\n                taskModel.setCallbackAfterSeconds(\n                        0); // reset callbackAfterSeconds when giving the task to the worker\n                taskModel.setWorkerId(workerId);\n                taskModel.incrementPollCount();\n                executionDAOFacade.updateTask(taskModel);\n                tasks.add(taskModel.toTask());\n            } catch (Exception e) {\n                // db operation failed for dequeued message, re-enqueue with a delay\n                LOGGER.warn(\n                        \"DB operation failed for task: {}, postponing task in queue\", taskId, e);\n                Monitors.recordTaskPollError(taskType, domain, e.getClass().getSimpleName());\n                queueDAO.postpone(queueName, taskId, 0, queueTaskMessagePostponeSecs);\n            }\n        }\n        executionDAOFacade.updateTaskLastPoll(taskType, domain, workerId);\n        Monitors.recordTaskPoll(queueName);\n        tasks.forEach(this::ackTaskReceived);\n        return tasks;\n    }\n\n    public Task getLastPollTask(String taskType, String workerId, String domain) {\n        List<Task> tasks = poll(taskType, workerId, domain, POLL_COUNT_ONE, POLLING_TIMEOUT_IN_MS);\n        if (tasks.isEmpty()) {\n            LOGGER.debug(\n                    \"No Task available for the poll: /tasks/poll/{}?{}&{}\",\n                    taskType,\n                    workerId,\n                    domain);\n            return null;\n        }\n        Task task = tasks.get(0);\n        ackTaskReceived(task);\n        LOGGER.debug(\n                \"The Task {} being returned for /tasks/poll/{}?{}&{}\",\n                task,\n                taskType,\n                workerId,\n                domain);\n        return task;\n    }\n\n    public List<PollData> getPollData(String taskType) {\n        return executionDAOFacade.getTaskPollData(taskType);\n    }\n\n    public List<PollData> getAllPollData() {\n        try {\n            return executionDAOFacade.getAllPollData();\n        } catch (UnsupportedOperationException uoe) {\n            List<PollData> allPollData = new ArrayList<>();\n            Map<String, Long> queueSizes = queueDAO.queuesDetail();\n            queueSizes\n                    .keySet()\n                    .forEach(\n                            queueName -> {\n                                try {\n                                    if (!queueName.contains(QueueUtils.DOMAIN_SEPARATOR)) {\n                                        allPollData.addAll(\n                                                getPollData(\n                                                        QueueUtils.getQueueNameWithoutDomain(\n                                                                queueName)));\n                                    }\n                                } catch (Exception e) {\n                                    LOGGER.error(\"Unable to fetch all poll data!\", e);\n                                }\n                            });\n            return allPollData;\n        }\n    }\n\n    public void terminateWorkflow(String workflowId, String reason) {\n        workflowExecutor.terminateWorkflow(workflowId, reason);\n    }\n\n    public void updateTask(TaskResult taskResult) {\n        workflowExecutor.updateTask(taskResult);\n    }\n\n    public List<Task> getTasks(String taskType, String startKey, int count) {\n        return executionDAOFacade.getTasksByName(taskType, startKey, count);\n    }\n\n    public Task getTask(String taskId) {\n        return executionDAOFacade.getTask(taskId);\n    }\n\n    public Task getPendingTaskForWorkflow(String taskReferenceName, String workflowId) {\n        return executionDAOFacade.getTasksForWorkflow(workflowId).stream()\n                .filter(task -> !task.getStatus().isTerminal())\n                .filter(task -> task.getReferenceTaskName().equals(taskReferenceName))\n                .findFirst() // There can only be one task by a given reference name running at a\n                // time.\n                .orElse(null);\n    }\n\n    /**\n     * This method removes the task from the un-acked Queue\n     *\n     * @param taskId: the taskId that needs to be updated and removed from the unacked queue\n     * @return True in case of successful removal of the taskId from the un-acked queue\n     */\n    public boolean ackTaskReceived(String taskId) {\n        return Optional.ofNullable(getTask(taskId)).map(this::ackTaskReceived).orElse(false);\n    }\n\n    public boolean ackTaskReceived(Task task) {\n        return queueDAO.ack(QueueUtils.getQueueName(task), task.getTaskId());\n    }\n\n    public Map<String, Integer> getTaskQueueSizes(List<String> taskDefNames) {\n        Map<String, Integer> sizes = new HashMap<>();\n        for (String taskDefName : taskDefNames) {\n            sizes.put(taskDefName, getTaskQueueSize(taskDefName));\n        }\n        return sizes;\n    }\n\n    public Integer getTaskQueueSize(String queueName) {\n        return queueDAO.getSize(queueName);\n    }\n\n    public void removeTaskFromQueue(String taskId) {\n        Task task = getTask(taskId);\n        if (task == null) {\n            throw new NotFoundException(\"No such task found by taskId: %s\", taskId);\n        }\n        queueDAO.remove(QueueUtils.getQueueName(task), taskId);\n    }\n\n    public int requeuePendingTasks(String taskType) {\n\n        int count = 0;\n        List<Task> tasks = getPendingTasksForTaskType(taskType);\n\n        for (Task pending : tasks) {\n\n            if (systemTaskRegistry.isSystemTask(pending.getTaskType())) {\n                continue;\n            }\n            if (pending.getStatus().isTerminal()) {\n                continue;\n            }\n\n            LOGGER.debug(\n                    \"Requeuing Task: {} of taskType: {} in Workflow: {}\",\n                    pending.getTaskId(),\n                    pending.getTaskType(),\n                    pending.getWorkflowInstanceId());\n            boolean pushed = requeue(pending);\n            if (pushed) {\n                count++;\n            }\n        }\n        return count;\n    }\n\n    private boolean requeue(Task pending) {\n        long callback = pending.getCallbackAfterSeconds();\n        if (callback < 0) {\n            callback = 0;\n        }\n        queueDAO.remove(QueueUtils.getQueueName(pending), pending.getTaskId());\n        long now = System.currentTimeMillis();\n        callback = callback - ((now - pending.getUpdateTime()) / 1000);\n        if (callback < 0) {\n            callback = 0;\n        }\n        return queueDAO.pushIfNotExists(\n                QueueUtils.getQueueName(pending),\n                pending.getTaskId(),\n                pending.getWorkflowPriority(),\n                callback);\n    }\n\n    public List<Workflow> getWorkflowInstances(\n            String workflowName,\n            String correlationId,\n            boolean includeClosed,\n            boolean includeTasks) {\n\n        List<Workflow> workflows =\n                executionDAOFacade.getWorkflowsByCorrelationId(workflowName, correlationId, false);\n        return workflows.stream()\n                .parallel()\n                .filter(\n                        workflow -> {\n                            if (includeClosed\n                                    || workflow.getStatus()\n                                            .equals(Workflow.WorkflowStatus.RUNNING)) {\n                                // including tasks for subset of workflows to increase performance\n                                if (includeTasks) {\n                                    List<Task> tasks =\n                                            executionDAOFacade.getTasksForWorkflow(\n                                                    workflow.getWorkflowId());\n                                    tasks.sort(Comparator.comparingInt(Task::getSeq));\n                                    workflow.setTasks(tasks);\n                                }\n                                return true;\n                            } else {\n                                return false;\n                            }\n                        })\n                .collect(Collectors.toList());\n    }\n\n    public Workflow getExecutionStatus(String workflowId, boolean includeTasks) {\n        return executionDAOFacade.getWorkflow(workflowId, includeTasks);\n    }\n\n    public List<String> getRunningWorkflows(String workflowName, int version) {\n        return executionDAOFacade.getRunningWorkflowIds(workflowName, version);\n    }\n\n    public void removeWorkflow(String workflowId, boolean archiveWorkflow) {\n        executionDAOFacade.removeWorkflow(workflowId, archiveWorkflow);\n    }\n\n    public SearchResult<WorkflowSummary> search(\n            String query, String freeText, int start, int size, List<String> sortOptions) {\n        return executionDAOFacade.searchWorkflowSummary(query, freeText, start, size, sortOptions);\n    }\n\n    public SearchResult<Workflow> searchV2(\n            String query, String freeText, int start, int size, List<String> sortOptions) {\n\n        SearchResult<String> result =\n                executionDAOFacade.searchWorkflows(query, freeText, start, size, sortOptions);\n        List<Workflow> workflows =\n                result.getResults().stream()\n                        .parallel()\n                        .map(\n                                workflowId -> {\n                                    try {\n                                        return executionDAOFacade.getWorkflow(workflowId, false);\n                                    } catch (Exception e) {\n                                        LOGGER.error(\n                                                \"Error fetching workflow by id: {}\", workflowId, e);\n                                        return null;\n                                    }\n                                })\n                        .filter(Objects::nonNull)\n                        .collect(Collectors.toList());\n        int missing = result.getResults().size() - workflows.size();\n        long totalHits = result.getTotalHits() - missing;\n        return new SearchResult<>(totalHits, workflows);\n    }\n\n    public SearchResult<WorkflowSummary> searchWorkflowByTasks(\n            String query, String freeText, int start, int size, List<String> sortOptions) {\n        SearchResult<TaskSummary> taskSummarySearchResult =\n                searchTaskSummary(query, freeText, start, size, sortOptions);\n        List<WorkflowSummary> workflowSummaries =\n                taskSummarySearchResult.getResults().stream()\n                        .parallel()\n                        .map(\n                                taskSummary -> {\n                                    try {\n                                        String workflowId = taskSummary.getWorkflowId();\n                                        return new WorkflowSummary(\n                                                executionDAOFacade.getWorkflow(workflowId, false));\n                                    } catch (Exception e) {\n                                        LOGGER.error(\n                                                \"Error fetching workflow by id: {}\",\n                                                taskSummary.getWorkflowId(),\n                                                e);\n                                        return null;\n                                    }\n                                })\n                        .filter(Objects::nonNull)\n                        .distinct()\n                        .collect(Collectors.toList());\n        int missing = taskSummarySearchResult.getResults().size() - workflowSummaries.size();\n        long totalHits = taskSummarySearchResult.getTotalHits() - missing;\n        return new SearchResult<>(totalHits, workflowSummaries);\n    }\n\n    public SearchResult<Workflow> searchWorkflowByTasksV2(\n            String query, String freeText, int start, int size, List<String> sortOptions) {\n        SearchResult<TaskSummary> taskSummarySearchResult =\n                searchTasks(query, freeText, start, size, sortOptions);\n        List<Workflow> workflows =\n                taskSummarySearchResult.getResults().stream()\n                        .parallel()\n                        .map(\n                                taskSummary -> {\n                                    try {\n                                        String workflowId = taskSummary.getWorkflowId();\n                                        return executionDAOFacade.getWorkflow(workflowId, false);\n                                    } catch (Exception e) {\n                                        LOGGER.error(\n                                                \"Error fetching workflow by id: {}\",\n                                                taskSummary.getWorkflowId(),\n                                                e);\n                                        return null;\n                                    }\n                                })\n                        .filter(Objects::nonNull)\n                        .distinct()\n                        .collect(Collectors.toList());\n        int missing = taskSummarySearchResult.getResults().size() - workflows.size();\n        long totalHits = taskSummarySearchResult.getTotalHits() - missing;\n        return new SearchResult<>(totalHits, workflows);\n    }\n\n    public SearchResult<TaskSummary> searchTasks(\n            String query, String freeText, int start, int size, List<String> sortOptions) {\n\n        SearchResult<String> result =\n                executionDAOFacade.searchTasks(query, freeText, start, size, sortOptions);\n        List<TaskSummary> workflows =\n                result.getResults().stream()\n                        .parallel()\n                        .map(\n                                task -> {\n                                    try {\n                                        return new TaskSummary(executionDAOFacade.getTask(task));\n                                    } catch (Exception e) {\n                                        LOGGER.error(\"Error fetching task by id: {}\", task, e);\n                                        return null;\n                                    }\n                                })\n                        .filter(Objects::nonNull)\n                        .collect(Collectors.toList());\n        int missing = result.getResults().size() - workflows.size();\n        long totalHits = result.getTotalHits() - missing;\n        return new SearchResult<>(totalHits, workflows);\n    }\n\n    public SearchResult<TaskSummary> searchTaskSummary(\n            String query, String freeText, int start, int size, List<String> sortOptions) {\n        return executionDAOFacade.searchTaskSummary(query, freeText, start, size, sortOptions);\n    }\n\n    public SearchResult<TaskSummary> getSearchTasks(\n            String query,\n            String freeText,\n            int start,\n            /*@Max(value = MAX_SEARCH_SIZE, message = \"Cannot return more than {value} workflows.\" +\n            \" Please use pagination.\")*/ int size,\n            String sortString) {\n        return searchTaskSummary(\n                query, freeText, start, size, Utils.convertStringToList(sortString));\n    }\n\n    public SearchResult<Task> getSearchTasksV2(\n            String query, String freeText, int start, int size, String sortString) {\n        SearchResult<String> result =\n                executionDAOFacade.searchTasks(\n                        query, freeText, start, size, Utils.convertStringToList(sortString));\n        List<Task> tasks =\n                result.getResults().stream()\n                        .parallel()\n                        .map(\n                                task -> {\n                                    try {\n                                        return executionDAOFacade.getTask(task);\n                                    } catch (Exception e) {\n                                        LOGGER.error(\"Error fetching task by id: {}\", task, e);\n                                        return null;\n                                    }\n                                })\n                        .filter(Objects::nonNull)\n                        .collect(Collectors.toList());\n        int missing = result.getResults().size() - tasks.size();\n        long totalHits = result.getTotalHits() - missing;\n        return new SearchResult<>(totalHits, tasks);\n    }\n\n    public List<Task> getPendingTasksForTaskType(String taskType) {\n        return executionDAOFacade.getPendingTasksForTaskType(taskType);\n    }\n\n    public boolean addEventExecution(EventExecution eventExecution) {\n        return executionDAOFacade.addEventExecution(eventExecution);\n    }\n\n    public void removeEventExecution(EventExecution eventExecution) {\n        executionDAOFacade.removeEventExecution(eventExecution);\n    }\n\n    public void updateEventExecution(EventExecution eventExecution) {\n        executionDAOFacade.updateEventExecution(eventExecution);\n    }\n\n    /**\n     * @param queue Name of the registered queueDAO\n     * @param msg Message\n     */\n    public void addMessage(String queue, Message msg) {\n        executionDAOFacade.addMessage(queue, msg);\n    }\n\n    /**\n     * Adds task logs\n     *\n     * @param taskId Id of the task\n     * @param log logs\n     */\n    public void log(String taskId, String log) {\n        TaskExecLog executionLog = new TaskExecLog();\n        executionLog.setTaskId(taskId);\n        executionLog.setLog(log);\n        executionLog.setCreatedTime(System.currentTimeMillis());\n        executionDAOFacade.addTaskExecLog(Collections.singletonList(executionLog));\n    }\n\n    /**\n     * @param taskId Id of the task for which to retrieve logs\n     * @return Execution Logs (logged by the worker)\n     */\n    public List<TaskExecLog> getTaskLogs(String taskId) {\n        return executionDAOFacade.getTaskExecutionLogs(taskId);\n    }\n\n    /**\n     * Get external uri for the payload\n     *\n     * @param path the path for which the external storage location is to be populated\n     * @param operation the type of {@link Operation} to be performed\n     * @param type the {@link PayloadType} at the external uri\n     * @return the external uri at which the payload is stored/to be stored\n     */\n    public ExternalStorageLocation getExternalStorageLocation(\n            String path, String operation, String type) {\n        try {\n            ExternalPayloadStorage.Operation payloadOperation =\n                    ExternalPayloadStorage.Operation.valueOf(StringUtils.upperCase(operation));\n            ExternalPayloadStorage.PayloadType payloadType =\n                    ExternalPayloadStorage.PayloadType.valueOf(StringUtils.upperCase(type));\n            return externalPayloadStorage.getLocation(payloadOperation, payloadType, path);\n        } catch (Exception e) {\n            String errorMsg =\n                    String.format(\n                            \"Invalid input - Operation: %s, PayloadType: %s\", operation, type);\n            LOGGER.error(errorMsg);\n            throw new IllegalArgumentException(errorMsg);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/MetadataService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\nimport javax.validation.constraints.Size;\n\nimport org.springframework.validation.annotation.Validated;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDefSummary;\nimport com.netflix.conductor.common.model.BulkResponse;\n\n@Validated\npublic interface MetadataService {\n\n    /**\n     * @param taskDefinitions Task Definitions to register\n     */\n    void registerTaskDef(\n            @NotNull(message = \"TaskDefList cannot be empty or null\")\n                    @Size(min = 1, message = \"TaskDefList is empty\")\n                    List<@Valid TaskDef> taskDefinitions);\n\n    /**\n     * @param taskDefinition Task Definition to be updated\n     */\n    void updateTaskDef(@NotNull(message = \"TaskDef cannot be null\") @Valid TaskDef taskDefinition);\n\n    /**\n     * @param taskType Remove task definition\n     */\n    void unregisterTaskDef(@NotEmpty(message = \"TaskName cannot be null or empty\") String taskType);\n\n    /**\n     * @return List of all the registered tasks\n     */\n    List<TaskDef> getTaskDefs();\n\n    /**\n     * @param taskType Task to retrieve\n     * @return Task Definition\n     */\n    TaskDef getTaskDef(@NotEmpty(message = \"TaskType cannot be null or empty\") String taskType);\n\n    /**\n     * @param def Workflow definition to be updated\n     */\n    void updateWorkflowDef(@NotNull(message = \"WorkflowDef cannot be null\") @Valid WorkflowDef def);\n\n    /**\n     * @param workflowDefList Workflow definitions to be updated.\n     */\n    BulkResponse updateWorkflowDef(\n            @NotNull(message = \"WorkflowDef list name cannot be null or empty\")\n                    @Size(min = 1, message = \"WorkflowDefList is empty\")\n                    List<@NotNull(message = \"WorkflowDef cannot be null\") @Valid WorkflowDef>\n                            workflowDefList);\n\n    /**\n     * @param name Name of the workflow to retrieve\n     * @param version Optional. Version. If null, then retrieves the latest\n     * @return Workflow definition\n     */\n    WorkflowDef getWorkflowDef(\n            @NotEmpty(message = \"Workflow name cannot be null or empty\") String name,\n            Integer version);\n\n    /**\n     * @param name Name of the workflow to retrieve\n     * @return Latest version of the workflow definition\n     */\n    Optional<WorkflowDef> getLatestWorkflow(\n            @NotEmpty(message = \"Workflow name cannot be null or empty\") String name);\n\n    /**\n     * @return Returns all workflow defs (all versions)\n     */\n    List<WorkflowDef> getWorkflowDefs();\n\n    /**\n     * @return Returns workflow names and versions only (no definition bodies)\n     */\n    Map<String, ? extends Iterable<WorkflowDefSummary>> getWorkflowNamesAndVersions();\n\n    void registerWorkflowDef(\n            @NotNull(message = \"WorkflowDef cannot be null\") @Valid WorkflowDef workflowDef);\n\n    /**\n     * Validates a {@link WorkflowDef}.\n     *\n     * @param workflowDef The {@link WorkflowDef} object.\n     */\n    default void validateWorkflowDef(\n            @NotNull(message = \"WorkflowDef cannot be null\") @Valid WorkflowDef workflowDef) {\n        // do nothing, WorkflowDef is annotated with @Valid and calling this method will validate it\n    }\n\n    /**\n     * @param name Name of the workflow definition to be removed\n     * @param version Version of the workflow definition to be removed\n     */\n    void unregisterWorkflowDef(\n            @NotEmpty(message = \"Workflow name cannot be null or empty\") String name,\n            @NotNull(message = \"Version cannot be null\") Integer version);\n\n    /**\n     * @param eventHandler Event handler to be added. Will throw an exception if an event handler\n     *     already exists with the name\n     */\n    void addEventHandler(\n            @NotNull(message = \"EventHandler cannot be null\") @Valid EventHandler eventHandler);\n\n    /**\n     * @param eventHandler Event handler to be updated.\n     */\n    void updateEventHandler(\n            @NotNull(message = \"EventHandler cannot be null\") @Valid EventHandler eventHandler);\n\n    /**\n     * @param name Removes the event handler from the system\n     */\n    void removeEventHandlerStatus(\n            @NotEmpty(message = \"EventName cannot be null or empty\") String name);\n\n    /**\n     * @return All the event handlers registered in the system\n     */\n    List<EventHandler> getAllEventHandlers();\n\n    /**\n     * @param event name of the event\n     * @param activeOnly if true, returns only the active handlers\n     * @return Returns the list of all the event handlers for a given event\n     */\n    List<EventHandler> getEventHandlersForEvent(\n            @NotEmpty(message = \"EventName cannot be null or empty\") String event,\n            boolean activeOnly);\n\n    List<WorkflowDef> getWorkflowDefsLatestVersions();\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/MetadataServiceImpl.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.TreeSet;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.common.constraints.OwnerEmailMandatoryConstraint;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDefSummary;\nimport com.netflix.conductor.common.model.BulkResponse;\nimport com.netflix.conductor.core.WorkflowContext;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.dao.EventHandlerDAO;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.validations.ValidationContext;\n\n@Service\npublic class MetadataServiceImpl implements MetadataService {\n    private static final Logger LOGGER = LoggerFactory.getLogger(MetadataServiceImpl.class);\n    private final MetadataDAO metadataDAO;\n    private final EventHandlerDAO eventHandlerDAO;\n\n    public MetadataServiceImpl(\n            MetadataDAO metadataDAO,\n            EventHandlerDAO eventHandlerDAO,\n            ConductorProperties properties) {\n        this.metadataDAO = metadataDAO;\n        this.eventHandlerDAO = eventHandlerDAO;\n\n        ValidationContext.initialize(metadataDAO);\n        OwnerEmailMandatoryConstraint.WorkflowTaskValidValidator.setOwnerEmailMandatory(\n                properties.isOwnerEmailMandatory());\n    }\n\n    /**\n     * @param taskDefinitions Task Definitions to register\n     */\n    public void registerTaskDef(List<TaskDef> taskDefinitions) {\n        for (TaskDef taskDefinition : taskDefinitions) {\n            taskDefinition.setCreatedBy(WorkflowContext.get().getClientApp());\n            taskDefinition.setCreateTime(System.currentTimeMillis());\n            taskDefinition.setUpdatedBy(null);\n            taskDefinition.setUpdateTime(null);\n\n            metadataDAO.createTaskDef(taskDefinition);\n        }\n    }\n\n    @Override\n    public void validateWorkflowDef(WorkflowDef workflowDef) {\n        // do nothing, WorkflowDef is annotated with @Valid and calling this method will validate it\n    }\n\n    /**\n     * @param taskDefinition Task Definition to be updated\n     */\n    public void updateTaskDef(TaskDef taskDefinition) {\n        TaskDef existing = metadataDAO.getTaskDef(taskDefinition.getName());\n        if (existing == null) {\n            throw new NotFoundException(\"No such task by name %s\", taskDefinition.getName());\n        }\n        taskDefinition.setUpdatedBy(WorkflowContext.get().getClientApp());\n        taskDefinition.setUpdateTime(System.currentTimeMillis());\n        metadataDAO.updateTaskDef(taskDefinition);\n    }\n\n    /**\n     * @param taskType Remove task definition\n     */\n    public void unregisterTaskDef(String taskType) {\n        metadataDAO.removeTaskDef(taskType);\n    }\n\n    /**\n     * @return List of all the registered tasks\n     */\n    public List<TaskDef> getTaskDefs() {\n        return metadataDAO.getAllTaskDefs();\n    }\n\n    /**\n     * @param taskType Task to retrieve\n     * @return Task Definition\n     */\n    public TaskDef getTaskDef(String taskType) {\n        TaskDef taskDef = metadataDAO.getTaskDef(taskType);\n        if (taskDef == null) {\n            throw new NotFoundException(\"No such taskType found by name: %s\", taskType);\n        }\n        return taskDef;\n    }\n\n    /**\n     * @param workflowDef Workflow definition to be updated\n     */\n    public void updateWorkflowDef(WorkflowDef workflowDef) {\n        workflowDef.setUpdateTime(System.currentTimeMillis());\n        metadataDAO.updateWorkflowDef(workflowDef);\n    }\n\n    /**\n     * @param workflowDefList Workflow definitions to be updated.\n     */\n    public BulkResponse updateWorkflowDef(List<WorkflowDef> workflowDefList) {\n        BulkResponse bulkResponse = new BulkResponse();\n        for (WorkflowDef workflowDef : workflowDefList) {\n            try {\n                updateWorkflowDef(workflowDef);\n                bulkResponse.appendSuccessResponse(workflowDef.getName());\n            } catch (Exception e) {\n                LOGGER.error(\"bulk update workflow def failed, name {} \", workflowDef.getName(), e);\n                bulkResponse.appendFailedResponse(workflowDef.getName(), e.getMessage());\n            }\n        }\n        return bulkResponse;\n    }\n\n    /**\n     * @param name Name of the workflow to retrieve\n     * @param version Optional. Version. If null, then retrieves the latest\n     * @return Workflow definition\n     */\n    public WorkflowDef getWorkflowDef(String name, Integer version) {\n        Optional<WorkflowDef> workflowDef;\n        if (version == null) {\n            workflowDef = metadataDAO.getLatestWorkflowDef(name);\n        } else {\n            workflowDef = metadataDAO.getWorkflowDef(name, version);\n        }\n\n        return workflowDef.orElseThrow(\n                () ->\n                        new NotFoundException(\n                                \"No such workflow found by name: %s, version: %d\", name, version));\n    }\n\n    /**\n     * @param name Name of the workflow to retrieve\n     * @return Latest version of the workflow definition\n     */\n    public Optional<WorkflowDef> getLatestWorkflow(String name) {\n        return metadataDAO.getLatestWorkflowDef(name);\n    }\n\n    public List<WorkflowDef> getWorkflowDefs() {\n        return metadataDAO.getAllWorkflowDefs();\n    }\n\n    public void registerWorkflowDef(WorkflowDef workflowDef) {\n        workflowDef.setCreateTime(System.currentTimeMillis());\n        metadataDAO.createWorkflowDef(workflowDef);\n    }\n\n    /**\n     * @param name Name of the workflow definition to be removed\n     * @param version Version of the workflow definition to be removed\n     */\n    public void unregisterWorkflowDef(String name, Integer version) {\n        metadataDAO.removeWorkflowDef(name, version);\n    }\n\n    /**\n     * @param eventHandler Event handler to be added. Will throw an exception if an event handler\n     *     already exists with the name\n     */\n    public void addEventHandler(EventHandler eventHandler) {\n        eventHandlerDAO.addEventHandler(eventHandler);\n    }\n\n    /**\n     * @param eventHandler Event handler to be updated.\n     */\n    public void updateEventHandler(EventHandler eventHandler) {\n        eventHandlerDAO.updateEventHandler(eventHandler);\n    }\n\n    /**\n     * @param name Removes the event handler from the system\n     */\n    public void removeEventHandlerStatus(String name) {\n        eventHandlerDAO.removeEventHandler(name);\n    }\n\n    /**\n     * @return All the event handlers registered in the system\n     */\n    public List<EventHandler> getAllEventHandlers() {\n        return eventHandlerDAO.getAllEventHandlers();\n    }\n\n    /**\n     * @param event name of the event\n     * @param activeOnly if true, returns only the active handlers\n     * @return Returns the list of all the event handlers for a given event\n     */\n    public List<EventHandler> getEventHandlersForEvent(String event, boolean activeOnly) {\n        return eventHandlerDAO.getEventHandlersForEvent(event, activeOnly);\n    }\n\n    @Override\n    public List<WorkflowDef> getWorkflowDefsLatestVersions() {\n        return metadataDAO.getAllWorkflowDefsLatestVersions();\n    }\n\n    public Map<String, ? extends Iterable<WorkflowDefSummary>> getWorkflowNamesAndVersions() {\n        List<WorkflowDef> workflowDefs = metadataDAO.getAllWorkflowDefs();\n\n        Map<String, TreeSet<WorkflowDefSummary>> retval = new HashMap<>();\n        for (WorkflowDef def : workflowDefs) {\n            String workflowName = def.getName();\n            WorkflowDefSummary summary = fromWorkflowDef(def);\n\n            retval.putIfAbsent(workflowName, new TreeSet<WorkflowDefSummary>());\n\n            TreeSet<WorkflowDefSummary> versions = retval.get(workflowName);\n            versions.add(summary);\n        }\n\n        return retval;\n    }\n\n    private WorkflowDefSummary fromWorkflowDef(WorkflowDef def) {\n        WorkflowDefSummary summary = new WorkflowDefSummary();\n        summary.setName(def.getName());\n        summary.setVersion(def.getVersion());\n        summary.setCreateTime(def.getCreateTime());\n\n        return summary;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/TaskService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport org.springframework.validation.annotation.Validated;\n\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\n\n@Validated\npublic interface TaskService {\n\n    /**\n     * Poll for a task of a certain type.\n     *\n     * @param taskType Task name\n     * @param workerId Id of the workflow\n     * @param domain Domain of the workflow\n     * @return polled {@link Task}\n     */\n    Task poll(\n            @NotEmpty(message = \"TaskType cannot be null or empty.\") String taskType,\n            String workerId,\n            String domain);\n\n    /**\n     * Batch Poll for a task of a certain type.\n     *\n     * @param taskType Task Name\n     * @param workerId Id of the workflow\n     * @param domain Domain of the workflow\n     * @param count Number of tasks\n     * @param timeout Timeout for polling in milliseconds\n     * @return list of {@link Task}\n     */\n    List<Task> batchPoll(\n            @NotEmpty(message = \"TaskType cannot be null or empty.\") String taskType,\n            String workerId,\n            String domain,\n            Integer count,\n            Integer timeout);\n\n    /**\n     * Get in progress tasks. The results are paginated.\n     *\n     * @param taskType Task Name\n     * @param startKey Start index of pagination\n     * @param count Number of entries\n     * @return list of {@link Task}\n     */\n    List<Task> getTasks(\n            @NotEmpty(message = \"TaskType cannot be null or empty.\") String taskType,\n            String startKey,\n            Integer count);\n\n    /**\n     * Get in progress task for a given workflow id.\n     *\n     * @param workflowId Id of the workflow\n     * @param taskReferenceName Task reference name.\n     * @return instance of {@link Task}\n     */\n    Task getPendingTaskForWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId,\n            @NotEmpty(message = \"TaskReferenceName cannot be null or empty.\")\n                    String taskReferenceName);\n\n    /**\n     * Updates a task.\n     *\n     * @param taskResult Instance of {@link TaskResult}\n     * @return task Id of the updated task.\n     */\n    String updateTask(\n            @NotNull(message = \"TaskResult cannot be null or empty.\") @Valid TaskResult taskResult);\n\n    /**\n     * Ack Task is received.\n     *\n     * @param taskId Id of the task\n     * @param workerId Id of the worker\n     * @return `true|false` if task if received or not\n     */\n    String ackTaskReceived(\n            @NotEmpty(message = \"TaskId cannot be null or empty.\") String taskId, String workerId);\n\n    /**\n     * Ack Task is received.\n     *\n     * @param taskId Id of the task\n     * @return `true|false` if task if received or not\n     */\n    boolean ackTaskReceived(@NotEmpty(message = \"TaskId cannot be null or empty.\") String taskId);\n\n    /**\n     * Log Task Execution Details.\n     *\n     * @param taskId Id of the task\n     * @param log Details you want to log\n     */\n    void log(@NotEmpty(message = \"TaskId cannot be null or empty.\") String taskId, String log);\n\n    /**\n     * Get Task Execution Logs.\n     *\n     * @param taskId Id of the task.\n     * @return list of {@link TaskExecLog}\n     */\n    List<TaskExecLog> getTaskLogs(\n            @NotEmpty(message = \"TaskId cannot be null or empty.\") String taskId);\n\n    /**\n     * Get task by Id.\n     *\n     * @param taskId Id of the task.\n     * @return instance of {@link Task}\n     */\n    Task getTask(@NotEmpty(message = \"TaskId cannot be null or empty.\") String taskId);\n\n    /**\n     * Remove Task from a Task type queue.\n     *\n     * @param taskType Task Name\n     * @param taskId ID of the task\n     */\n    void removeTaskFromQueue(\n            @NotEmpty(message = \"TaskType cannot be null or empty.\") String taskType,\n            @NotEmpty(message = \"TaskId cannot be null or empty.\") String taskId);\n\n    /**\n     * Remove Task from a Task type queue.\n     *\n     * @param taskId ID of the task\n     */\n    void removeTaskFromQueue(@NotEmpty(message = \"TaskId cannot be null or empty.\") String taskId);\n\n    /**\n     * Get Task type queue sizes.\n     *\n     * @param taskTypes List of task types.\n     * @return map of task type as Key and queue size as value.\n     */\n    Map<String, Integer> getTaskQueueSizes(List<String> taskTypes);\n\n    /**\n     * Get the queue size for a Task Type. The input can optionally include <code>domain</code>,\n     * <code>isolationGroupId</code> and <code>executionNamespace</code>.\n     *\n     * @return\n     */\n    Integer getTaskQueueSize(\n            String taskType, String domain, String isolationGroupId, String executionNamespace);\n\n    /**\n     * Get the details about each queue.\n     *\n     * @return map of queue details.\n     */\n    Map<String, Map<String, Map<String, Long>>> allVerbose();\n\n    /**\n     * Get the details about each queue.\n     *\n     * @return map of details about each queue.\n     */\n    Map<String, Long> getAllQueueDetails();\n\n    /**\n     * Get the last poll data for a given task type.\n     *\n     * @param taskType Task Name\n     * @return list of {@link PollData}\n     */\n    List<PollData> getPollData(\n            @NotEmpty(message = \"TaskType cannot be null or empty.\") String taskType);\n\n    /**\n     * Get the last poll data for all task types.\n     *\n     * @return list of {@link PollData}\n     */\n    List<PollData> getAllPollData();\n\n    /**\n     * Requeue pending tasks.\n     *\n     * @param taskType Task name.\n     * @return number of tasks requeued.\n     */\n    String requeuePendingTask(\n            @NotEmpty(message = \"TaskType cannot be null or empty.\") String taskType);\n\n    /**\n     * Search for tasks based in payload and other parameters. Use sort options as ASC or DESC e.g.\n     * sort=name or sort=workflowId. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<TaskSummary> search(\n            int start, int size, String sort, String freeText, String query);\n\n    /**\n     * Search for tasks based in payload and other parameters. Use sort options as ASC or DESC e.g.\n     * sort=name or sort=workflowId. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<Task> searchV2(int start, int size, String sort, String freeText, String query);\n\n    /**\n     * Get the external storage location where the task output payload is stored/to be stored\n     *\n     * @param path the path for which the external storage location is to be populated\n     * @param operation the operation to be performed (read or write)\n     * @param payloadType the type of payload (input or output)\n     * @return {@link ExternalStorageLocation} containing the uri and the path to the payload is\n     *     stored in external storage\n     */\n    ExternalStorageLocation getExternalStorageLocation(\n            String path, String operation, String payloadType);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/TaskServiceImpl.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.Audit;\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.core.utils.QueueUtils;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.metrics.Monitors;\n\n@Audit\n@Trace\n@Service\npublic class TaskServiceImpl implements TaskService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskServiceImpl.class);\n    private final ExecutionService executionService;\n    private final QueueDAO queueDAO;\n\n    public TaskServiceImpl(ExecutionService executionService, QueueDAO queueDAO) {\n        this.executionService = executionService;\n        this.queueDAO = queueDAO;\n    }\n\n    /**\n     * Poll for a task of a certain type.\n     *\n     * @param taskType Task name\n     * @param workerId id of the workflow\n     * @param domain Domain of the workflow\n     * @return polled {@link Task}\n     */\n    public Task poll(String taskType, String workerId, String domain) {\n        LOGGER.debug(\"Task being polled: /tasks/poll/{}?{}&{}\", taskType, workerId, domain);\n        Task task = executionService.getLastPollTask(taskType, workerId, domain);\n        if (task != null) {\n            LOGGER.debug(\n                    \"The Task {} being returned for /tasks/poll/{}?{}&{}\",\n                    task,\n                    taskType,\n                    workerId,\n                    domain);\n        }\n        Monitors.recordTaskPollCount(taskType, domain, 1);\n        return task;\n    }\n\n    /**\n     * Batch Poll for a task of a certain type.\n     *\n     * @param taskType Task Name\n     * @param workerId id of the workflow\n     * @param domain Domain of the workflow\n     * @param count Number of tasks\n     * @param timeout Timeout for polling in milliseconds\n     * @return list of {@link Task}\n     */\n    public List<Task> batchPoll(\n            String taskType, String workerId, String domain, Integer count, Integer timeout) {\n        List<Task> polledTasks = executionService.poll(taskType, workerId, domain, count, timeout);\n        LOGGER.debug(\n                \"The Tasks {} being returned for /tasks/poll/{}?{}&{}\",\n                polledTasks.stream().map(Task::getTaskId).collect(Collectors.toList()),\n                taskType,\n                workerId,\n                domain);\n        Monitors.recordTaskPollCount(taskType, domain, polledTasks.size());\n        return polledTasks;\n    }\n\n    /**\n     * Get in progress tasks. The results are paginated.\n     *\n     * @param taskType Task Name\n     * @param startKey Start index of pagination\n     * @param count Number of entries\n     * @return list of {@link Task}\n     */\n    public List<Task> getTasks(String taskType, String startKey, Integer count) {\n        return executionService.getTasks(taskType, startKey, count);\n    }\n\n    /**\n     * Get in progress task for a given workflow id.\n     *\n     * @param workflowId id of the workflow\n     * @param taskReferenceName Task reference name.\n     * @return instance of {@link Task}\n     */\n    public Task getPendingTaskForWorkflow(String workflowId, String taskReferenceName) {\n        return executionService.getPendingTaskForWorkflow(taskReferenceName, workflowId);\n    }\n\n    /**\n     * Updates a task.\n     *\n     * @param taskResult Instance of {@link TaskResult}\n     * @return task Id of the updated task.\n     */\n    public String updateTask(TaskResult taskResult) {\n        LOGGER.debug(\n                \"Update Task: {} with callback time: {}\",\n                taskResult,\n                taskResult.getCallbackAfterSeconds());\n        executionService.updateTask(taskResult);\n        LOGGER.debug(\n                \"Task: {} updated successfully with callback time: {}\",\n                taskResult,\n                taskResult.getCallbackAfterSeconds());\n        return taskResult.getTaskId();\n    }\n\n    /**\n     * Ack Task is received.\n     *\n     * @param taskId id of the task\n     * @param workerId id of the worker\n     * @return `true|false` if task is received or not\n     */\n    public String ackTaskReceived(String taskId, String workerId) {\n        LOGGER.debug(\"Ack received for task: {} from worker: {}\", taskId, workerId);\n        return String.valueOf(ackTaskReceived(taskId));\n    }\n\n    /**\n     * Ack Task is received.\n     *\n     * @param taskId id of the task\n     * @return `true|false` if task is received or not\n     */\n    public boolean ackTaskReceived(String taskId) {\n        LOGGER.debug(\"Ack received for task: {}\", taskId);\n        AtomicBoolean ackResult = new AtomicBoolean(false);\n        try {\n            ackResult.set(executionService.ackTaskReceived(taskId));\n        } catch (Exception e) {\n            // Fail the task and let decide reevaluate the workflow, thereby preventing workflow\n            // being stuck from transient ack errors.\n            String errorMsg = String.format(\"Error when trying to ack task %s\", taskId);\n            LOGGER.error(errorMsg, e);\n            Task task = executionService.getTask(taskId);\n            Monitors.recordAckTaskError(task.getTaskType());\n            failTask(task, errorMsg);\n            ackResult.set(false);\n        }\n        return ackResult.get();\n    }\n\n    /** Updates the task with FAILED status; On exception, fails the workflow. */\n    private void failTask(Task task, String errorMsg) {\n        try {\n            TaskResult taskResult = new TaskResult();\n            taskResult.setStatus(TaskResult.Status.FAILED);\n            taskResult.setTaskId(task.getTaskId());\n            taskResult.setWorkflowInstanceId(task.getWorkflowInstanceId());\n            taskResult.setReasonForIncompletion(errorMsg);\n            executionService.updateTask(taskResult);\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Unable to fail task: {} in workflow: {}\",\n                    task.getTaskId(),\n                    task.getWorkflowInstanceId(),\n                    e);\n            executionService.terminateWorkflow(\n                    task.getWorkflowInstanceId(), \"Failed to ack task: \" + task.getTaskId());\n        }\n    }\n\n    /**\n     * Log Task Execution Details.\n     *\n     * @param taskId id of the task\n     * @param log Details you want to log\n     */\n    public void log(String taskId, String log) {\n        executionService.log(taskId, log);\n    }\n\n    /**\n     * Get Task Execution Logs.\n     *\n     * @param taskId id of the task.\n     * @return list of {@link TaskExecLog}\n     */\n    public List<TaskExecLog> getTaskLogs(String taskId) {\n        return executionService.getTaskLogs(taskId);\n    }\n\n    /**\n     * Get task by Id.\n     *\n     * @param taskId id of the task.\n     * @return instance of {@link Task}\n     */\n    public Task getTask(String taskId) {\n        return executionService.getTask(taskId);\n    }\n\n    /**\n     * Remove Task from a Task type queue.\n     *\n     * @param taskType Task Name\n     * @param taskId ID of the task\n     */\n    public void removeTaskFromQueue(String taskType, String taskId) {\n        executionService.removeTaskFromQueue(taskId);\n    }\n\n    /**\n     * Remove Task from a Task type queue.\n     *\n     * @param taskId ID of the task\n     */\n    public void removeTaskFromQueue(String taskId) {\n        executionService.removeTaskFromQueue(taskId);\n    }\n\n    /**\n     * Get Task type queue sizes.\n     *\n     * @param taskTypes List of task types.\n     * @return map of task type as Key and queue size as value.\n     */\n    public Map<String, Integer> getTaskQueueSizes(List<String> taskTypes) {\n        return executionService.getTaskQueueSizes(taskTypes);\n    }\n\n    @Override\n    public Integer getTaskQueueSize(\n            String taskType, String domain, String isolationGroupId, String executionNamespace) {\n        String queueName =\n                QueueUtils.getQueueName(\n                        taskType,\n                        StringUtils.trimToNull(domain),\n                        StringUtils.trimToNull(isolationGroupId),\n                        StringUtils.trimToNull(executionNamespace));\n\n        return executionService.getTaskQueueSize(queueName);\n    }\n\n    /**\n     * Get the details about each queue.\n     *\n     * @return map of queue details.\n     */\n    public Map<String, Map<String, Map<String, Long>>> allVerbose() {\n        return queueDAO.queuesDetailVerbose();\n    }\n\n    /**\n     * Get the details about each queue.\n     *\n     * @return map of details about each queue.\n     */\n    public Map<String, Long> getAllQueueDetails() {\n        return queueDAO.queuesDetail().entrySet().stream()\n                .sorted(Entry.comparingByKey())\n                .collect(\n                        Collectors.toMap(\n                                Entry::getKey,\n                                Entry::getValue,\n                                (v1, v2) -> v1,\n                                LinkedHashMap::new));\n    }\n\n    /**\n     * Get the last poll data for a given task type.\n     *\n     * @param taskType Task Name\n     * @return list of {@link PollData}\n     */\n    public List<PollData> getPollData(String taskType) {\n        return executionService.getPollData(taskType);\n    }\n\n    /**\n     * Get the last poll data for all task types.\n     *\n     * @return list of {@link PollData}\n     */\n    public List<PollData> getAllPollData() {\n        return executionService.getAllPollData();\n    }\n\n    /**\n     * Requeue pending tasks.\n     *\n     * @param taskType Task name.\n     * @return number of tasks requeued.\n     */\n    public String requeuePendingTask(String taskType) {\n        return String.valueOf(executionService.requeuePendingTasks(taskType));\n    }\n\n    /**\n     * Search for tasks based in payload and other parameters. Use sort options as ASC or DESC e.g.\n     * sort=name or sort=workflowId. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<TaskSummary> search(\n            int start, int size, String sort, String freeText, String query) {\n        return executionService.getSearchTasks(query, freeText, start, size, sort);\n    }\n\n    /**\n     * Search for tasks based in payload and other parameters. Use sort options as ASC or DESC e.g.\n     * sort=name or sort=workflowId. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<Task> searchV2(\n            int start, int size, String sort, String freeText, String query) {\n        return executionService.getSearchTasksV2(query, freeText, start, size, sort);\n    }\n\n    /**\n     * Get the external storage location where the task output payload is stored/to be stored\n     *\n     * @param path the path for which the external storage location is to be populated\n     * @param operation the operation to be performed (read or write)\n     * @param type the type of payload (input or output)\n     * @return {@link ExternalStorageLocation} containing the uri and the path to the payload is\n     *     stored in external storage\n     */\n    public ExternalStorageLocation getExternalStorageLocation(\n            String path, String operation, String type) {\n        return executionService.getExternalStorageLocation(path, operation, type);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/WorkflowBulkService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\n\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.Size;\n\nimport org.springframework.validation.annotation.Validated;\n\nimport com.netflix.conductor.common.model.BulkResponse;\n\n@Validated\npublic interface WorkflowBulkService {\n\n    int MAX_REQUEST_ITEMS = 1000;\n\n    BulkResponse pauseWorkflow(\n            @NotEmpty(message = \"WorkflowIds list cannot be null.\")\n                    @Size(\n                            max = MAX_REQUEST_ITEMS,\n                            message =\n                                    \"Cannot process more than {max} workflows. Please use multiple requests.\")\n                    List<String> workflowIds);\n\n    BulkResponse resumeWorkflow(\n            @NotEmpty(message = \"WorkflowIds list cannot be null.\")\n                    @Size(\n                            max = MAX_REQUEST_ITEMS,\n                            message =\n                                    \"Cannot process more than {max} workflows. Please use multiple requests.\")\n                    List<String> workflowIds);\n\n    BulkResponse restart(\n            @NotEmpty(message = \"WorkflowIds list cannot be null.\")\n                    @Size(\n                            max = MAX_REQUEST_ITEMS,\n                            message =\n                                    \"Cannot process more than {max} workflows. Please use multiple requests.\")\n                    List<String> workflowIds,\n            boolean useLatestDefinitions);\n\n    BulkResponse retry(\n            @NotEmpty(message = \"WorkflowIds list cannot be null.\")\n                    @Size(\n                            max = MAX_REQUEST_ITEMS,\n                            message =\n                                    \"Cannot process more than {max} workflows. Please use multiple requests.\")\n                    List<String> workflowIds);\n\n    BulkResponse terminate(\n            @NotEmpty(message = \"WorkflowIds list cannot be null.\")\n                    @Size(\n                            max = MAX_REQUEST_ITEMS,\n                            message =\n                                    \"Cannot process more than {max} workflows. Please use multiple requests.\")\n                    List<String> workflowIds,\n            String reason);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/WorkflowBulkServiceImpl.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.Audit;\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.model.BulkResponse;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\n\n@Audit\n@Trace\n@Service\npublic class WorkflowBulkServiceImpl implements WorkflowBulkService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowBulkService.class);\n    private final WorkflowExecutor workflowExecutor;\n\n    public WorkflowBulkServiceImpl(WorkflowExecutor workflowExecutor) {\n        this.workflowExecutor = workflowExecutor;\n    }\n\n    /**\n     * Pause the list of workflows.\n     *\n     * @param workflowIds - list of workflow Ids to perform pause operation on\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    public BulkResponse pauseWorkflow(List<String> workflowIds) {\n\n        BulkResponse bulkResponse = new BulkResponse();\n        for (String workflowId : workflowIds) {\n            try {\n                workflowExecutor.pauseWorkflow(workflowId);\n                bulkResponse.appendSuccessResponse(workflowId);\n            } catch (Exception e) {\n                LOGGER.error(\n                        \"bulk pauseWorkflow exception, workflowId {}, message: {} \",\n                        workflowId,\n                        e.getMessage(),\n                        e);\n                bulkResponse.appendFailedResponse(workflowId, e.getMessage());\n            }\n        }\n\n        return bulkResponse;\n    }\n\n    /**\n     * Resume the list of workflows.\n     *\n     * @param workflowIds - list of workflow Ids to perform resume operation on\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    public BulkResponse resumeWorkflow(List<String> workflowIds) {\n        BulkResponse bulkResponse = new BulkResponse();\n        for (String workflowId : workflowIds) {\n            try {\n                workflowExecutor.resumeWorkflow(workflowId);\n                bulkResponse.appendSuccessResponse(workflowId);\n            } catch (Exception e) {\n                LOGGER.error(\n                        \"bulk resumeWorkflow exception, workflowId {}, message: {} \",\n                        workflowId,\n                        e.getMessage(),\n                        e);\n                bulkResponse.appendFailedResponse(workflowId, e.getMessage());\n            }\n        }\n        return bulkResponse;\n    }\n\n    /**\n     * Restart the list of workflows.\n     *\n     * @param workflowIds - list of workflow Ids to perform restart operation on\n     * @param useLatestDefinitions if true, use latest workflow and task definitions upon restart\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    public BulkResponse restart(List<String> workflowIds, boolean useLatestDefinitions) {\n        BulkResponse bulkResponse = new BulkResponse();\n        for (String workflowId : workflowIds) {\n            try {\n                workflowExecutor.restart(workflowId, useLatestDefinitions);\n                bulkResponse.appendSuccessResponse(workflowId);\n            } catch (Exception e) {\n                LOGGER.error(\n                        \"bulk restart exception, workflowId {}, message: {} \",\n                        workflowId,\n                        e.getMessage(),\n                        e);\n                bulkResponse.appendFailedResponse(workflowId, e.getMessage());\n            }\n        }\n        return bulkResponse;\n    }\n\n    /**\n     * Retry the last failed task for each workflow from the list.\n     *\n     * @param workflowIds - list of workflow Ids to perform retry operation on\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    public BulkResponse retry(List<String> workflowIds) {\n        BulkResponse bulkResponse = new BulkResponse();\n        for (String workflowId : workflowIds) {\n            try {\n                workflowExecutor.retry(workflowId, false);\n                bulkResponse.appendSuccessResponse(workflowId);\n            } catch (Exception e) {\n                LOGGER.error(\n                        \"bulk retry exception, workflowId {}, message: {} \",\n                        workflowId,\n                        e.getMessage(),\n                        e);\n                bulkResponse.appendFailedResponse(workflowId, e.getMessage());\n            }\n        }\n        return bulkResponse;\n    }\n\n    /**\n     * Terminate workflows execution.\n     *\n     * @param workflowIds - list of workflow Ids to perform terminate operation on\n     * @param reason - description to be specified for the terminated workflow for future\n     *     references.\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    public BulkResponse terminate(List<String> workflowIds, String reason) {\n        BulkResponse bulkResponse = new BulkResponse();\n        for (String workflowId : workflowIds) {\n            try {\n                workflowExecutor.terminateWorkflow(workflowId, reason);\n                bulkResponse.appendSuccessResponse(workflowId);\n            } catch (Exception e) {\n                LOGGER.error(\n                        \"bulk terminate exception, workflowId {}, message: {} \",\n                        workflowId,\n                        e.getMessage(),\n                        e);\n                bulkResponse.appendFailedResponse(workflowId, e.getMessage());\n            }\n        }\n        return bulkResponse;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/WorkflowService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\n\nimport org.springframework.validation.annotation.Validated;\n\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.SkipTaskRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\n\n@Validated\npublic interface WorkflowService {\n\n    /**\n     * Start a new workflow with StartWorkflowRequest, which allows task to be executed in a domain.\n     *\n     * @param startWorkflowRequest StartWorkflow request for the workflow you want to start.\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    String startWorkflow(\n            @NotNull(message = \"StartWorkflowRequest cannot be null\") @Valid\n                    StartWorkflowRequest startWorkflowRequest);\n\n    /**\n     * Start a new workflow. Returns the ID of the workflow instance that can be later used for\n     * tracking.\n     *\n     * @param name Name of the workflow you want to start.\n     * @param version Version of the workflow you want to start.\n     * @param correlationId CorrelationID of the workflow you want to start.\n     * @param priority Priority of the workflow you want to start.\n     * @param input Input to the workflow you want to start.\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    String startWorkflow(\n            @NotEmpty(message = \"Workflow name cannot be null or empty\") String name,\n            Integer version,\n            String correlationId,\n            @Min(value = 0, message = \"0 is the minimum priority value\")\n                    @Max(value = 99, message = \"99 is the maximum priority value\")\n                    Integer priority,\n            Map<String, Object> input);\n\n    /**\n     * Start a new workflow. Returns the ID of the workflow instance that can be later used for\n     * tracking.\n     *\n     * @param name Name of the workflow you want to start.\n     * @param version Version of the workflow you want to start.\n     * @param correlationId CorrelationID of the workflow you want to start.\n     * @param priority Priority of the workflow you want to start.\n     * @param input Input to the workflow you want to start.\n     * @param externalInputPayloadStoragePath\n     * @param taskToDomain\n     * @param workflowDef - workflow definition\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    String startWorkflow(\n            String name,\n            Integer version,\n            String correlationId,\n            Integer priority,\n            Map<String, Object> input,\n            String externalInputPayloadStoragePath,\n            Map<String, String> taskToDomain,\n            WorkflowDef workflowDef);\n\n    /**\n     * Lists workflows for the given correlation id.\n     *\n     * @param name Name of the workflow.\n     * @param correlationId CorrelationID of the workflow you want to list.\n     * @param includeClosed IncludeClosed workflow which are not running.\n     * @param includeTasks Includes tasks associated with workflows.\n     * @return a list of {@link Workflow}\n     */\n    List<Workflow> getWorkflows(\n            @NotEmpty(message = \"Workflow name cannot be null or empty\") String name,\n            String correlationId,\n            boolean includeClosed,\n            boolean includeTasks);\n\n    /**\n     * Lists workflows for the given correlation id.\n     *\n     * @param name Name of the workflow.\n     * @param includeClosed CorrelationID of the workflow you want to start.\n     * @param includeTasks IncludeClosed workflow which are not running.\n     * @param correlationIds Includes tasks associated with workflows.\n     * @return a {@link Map} of {@link String} as key and a list of {@link Workflow} as value\n     */\n    Map<String, List<Workflow>> getWorkflows(\n            @NotEmpty(message = \"Workflow name cannot be null or empty\") String name,\n            boolean includeClosed,\n            boolean includeTasks,\n            List<String> correlationIds);\n\n    /**\n     * Gets the workflow by workflow Id.\n     *\n     * @param workflowId Id of the workflow.\n     * @param includeTasks Includes tasks associated with workflow.\n     * @return an instance of {@link Workflow}\n     */\n    Workflow getExecutionStatus(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId,\n            boolean includeTasks);\n\n    /**\n     * Removes the workflow from the system.\n     *\n     * @param workflowId WorkflowID of the workflow you want to remove from system.\n     * @param archiveWorkflow Archives the workflow and associated tasks instead of removing them.\n     */\n    void deleteWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId,\n            boolean archiveWorkflow);\n\n    /**\n     * Retrieves all the running workflows.\n     *\n     * @param workflowName Name of the workflow.\n     * @param version Version of the workflow.\n     * @param startTime Starttime of the workflow.\n     * @param endTime EndTime of the workflow\n     * @return a list of workflow Ids.\n     */\n    List<String> getRunningWorkflows(\n            @NotEmpty(message = \"Workflow name cannot be null or empty.\") String workflowName,\n            Integer version,\n            Long startTime,\n            Long endTime);\n\n    /**\n     * Starts the decision task for a workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    void decideWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId);\n\n    /**\n     * Pauses the workflow given a worklfowId.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    void pauseWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId);\n\n    /**\n     * Resumes the workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    void resumeWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId);\n\n    /**\n     * Skips a given task from a current running workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     * @param taskReferenceName The task reference name.\n     * @param skipTaskRequest {@link SkipTaskRequest} for task you want to skip.\n     */\n    void skipTaskFromWorkflow(\n            @NotEmpty(message = \"WorkflowId name cannot be null or empty.\") String workflowId,\n            @NotEmpty(message = \"TaskReferenceName cannot be null or empty.\")\n                    String taskReferenceName,\n            SkipTaskRequest skipTaskRequest);\n\n    /**\n     * Reruns the workflow from a specific task.\n     *\n     * @param workflowId WorkflowId of the workflow you want to rerun.\n     * @param request (@link RerunWorkflowRequest) for the workflow.\n     * @return WorkflowId of the rerun workflow.\n     */\n    String rerunWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId,\n            @NotNull(message = \"RerunWorkflowRequest cannot be null.\")\n                    RerunWorkflowRequest request);\n\n    /**\n     * Restarts a completed workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     * @param useLatestDefinitions if true, use the latest workflow and task definitions upon\n     *     restart\n     */\n    void restartWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId,\n            boolean useLatestDefinitions);\n\n    /**\n     * Retries the last failed task.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    void retryWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId,\n            boolean resumeSubworkflowTasks);\n\n    /**\n     * Resets callback times of all non-terminal SIMPLE tasks to 0.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    void resetWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId);\n\n    /**\n     * Terminate workflow execution.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     * @param reason Reason for terminating the workflow.\n     */\n    void terminateWorkflow(\n            @NotEmpty(message = \"WorkflowId cannot be null or empty.\") String workflowId,\n            String reason);\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<WorkflowSummary> searchWorkflows(\n            int start,\n            @Max(\n                            value = 5_000,\n                            message =\n                                    \"Cannot return more than {value} workflows. Please use pagination.\")\n                    int size,\n            String sort,\n            String freeText,\n            String query);\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<Workflow> searchWorkflowsV2(\n            int start,\n            @Max(\n                            value = 5_000,\n                            message =\n                                    \"Cannot return more than {value} workflows. Please use pagination.\")\n                    int size,\n            String sort,\n            String freeText,\n            String query);\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<WorkflowSummary> searchWorkflows(\n            int start,\n            @Max(\n                            value = 5_000,\n                            message =\n                                    \"Cannot return more than {value} workflows. Please use pagination.\")\n                    int size,\n            List<String> sort,\n            String freeText,\n            String query);\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<Workflow> searchWorkflowsV2(\n            int start,\n            @Max(\n                            value = 5_000,\n                            message =\n                                    \"Cannot return more than {value} workflows. Please use pagination.\")\n                    int size,\n            List<String> sort,\n            String freeText,\n            String query);\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<WorkflowSummary> searchWorkflowsByTasks(\n            int start, int size, String sort, String freeText, String query);\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<Workflow> searchWorkflowsByTasksV2(\n            int start, int size, String sort, String freeText, String query);\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<WorkflowSummary> searchWorkflowsByTasks(\n            int start, int size, List<String> sort, String freeText, String query);\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    SearchResult<Workflow> searchWorkflowsByTasksV2(\n            int start, int size, List<String> sort, String freeText, String query);\n\n    /**\n     * Get the external storage location where the workflow input payload is stored/to be stored\n     *\n     * @param path the path for which the external storage location is to be populated\n     * @param operation the operation to be performed (read or write)\n     * @param payloadType the type of payload (input or output)\n     * @return {@link ExternalStorageLocation} containing the uri and the path to the payload is\n     *     stored in external storage\n     */\n    ExternalStorageLocation getExternalStorageLocation(\n            String path, String operation, String payloadType);\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/WorkflowServiceImpl.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.annotations.Audit;\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.SkipTaskRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.core.utils.Utils;\n\n@Audit\n@Trace\n@Service\npublic class WorkflowServiceImpl implements WorkflowService {\n\n    private final WorkflowExecutor workflowExecutor;\n    private final ExecutionService executionService;\n    private final MetadataService metadataService;\n    private final StartWorkflowOperation startWorkflowOperation;\n\n    public WorkflowServiceImpl(\n            WorkflowExecutor workflowExecutor,\n            ExecutionService executionService,\n            MetadataService metadataService,\n            StartWorkflowOperation startWorkflowOperation) {\n        this.workflowExecutor = workflowExecutor;\n        this.executionService = executionService;\n        this.metadataService = metadataService;\n        this.startWorkflowOperation = startWorkflowOperation;\n    }\n\n    /**\n     * Start a new workflow with StartWorkflowRequest, which allows task to be executed in a domain.\n     *\n     * @param startWorkflowRequest StartWorkflow request for the workflow you want to start.\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    public String startWorkflow(StartWorkflowRequest startWorkflowRequest) {\n        return startWorkflowOperation.execute(new StartWorkflowInput(startWorkflowRequest));\n    }\n\n    /**\n     * Start a new workflow with StartWorkflowRequest, which allows task to be executed in a domain.\n     *\n     * @param name Name of the workflow you want to start.\n     * @param version Version of the workflow you want to start.\n     * @param correlationId CorrelationID of the workflow you want to start.\n     * @param priority Priority of the workflow you want to start.\n     * @param input Input to the workflow you want to start.\n     * @param externalInputPayloadStoragePath the relative path in external storage where input *\n     *     payload is located\n     * @param taskToDomain the task to domain mapping\n     * @param workflowDef - workflow definition\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    public String startWorkflow(\n            String name,\n            Integer version,\n            String correlationId,\n            Integer priority,\n            Map<String, Object> input,\n            String externalInputPayloadStoragePath,\n            Map<String, String> taskToDomain,\n            WorkflowDef workflowDef) {\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(name);\n        startWorkflowInput.setVersion(version);\n        startWorkflowInput.setCorrelationId(correlationId);\n        startWorkflowInput.setPriority(priority);\n        startWorkflowInput.setWorkflowInput(input);\n        startWorkflowInput.setExternalInputPayloadStoragePath(externalInputPayloadStoragePath);\n        startWorkflowInput.setTaskToDomain(taskToDomain);\n        startWorkflowInput.setWorkflowDefinition(workflowDef);\n\n        return startWorkflowOperation.execute(startWorkflowInput);\n    }\n\n    /**\n     * Start a new workflow. Returns the ID of the workflow instance that can be later used for\n     * tracking.\n     *\n     * @param name Name of the workflow you want to start.\n     * @param version Version of the workflow you want to start.\n     * @param correlationId CorrelationID of the workflow you want to start.\n     * @param priority Priority of the workflow you want to start.\n     * @param input Input to the workflow you want to start.\n     * @return the id of the workflow instance that can be use for tracking.\n     */\n    public String startWorkflow(\n            String name,\n            Integer version,\n            String correlationId,\n            Integer priority,\n            Map<String, Object> input) {\n        WorkflowDef workflowDef = metadataService.getWorkflowDef(name, version);\n        if (workflowDef == null) {\n            throw new NotFoundException(\n                    \"No such workflow found by name: %s, version: %d\", name, version);\n        }\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(workflowDef.getName());\n        startWorkflowInput.setVersion(workflowDef.getVersion());\n        startWorkflowInput.setCorrelationId(correlationId);\n        startWorkflowInput.setPriority(priority);\n        startWorkflowInput.setWorkflowInput(input);\n\n        return startWorkflowOperation.execute(startWorkflowInput);\n    }\n\n    /**\n     * Lists workflows for the given correlation id.\n     *\n     * @param name Name of the workflow.\n     * @param correlationId CorrelationID of the workflow you want to start.\n     * @param includeClosed IncludeClosed workflow which are not running.\n     * @param includeTasks Includes tasks associated with workflows.\n     * @return a list of {@link Workflow}\n     */\n    public List<Workflow> getWorkflows(\n            String name, String correlationId, boolean includeClosed, boolean includeTasks) {\n        return executionService.getWorkflowInstances(\n                name, correlationId, includeClosed, includeTasks);\n    }\n\n    /**\n     * Lists workflows for the given correlation id.\n     *\n     * @param name Name of the workflow.\n     * @param includeClosed CorrelationID of the workflow you want to start.\n     * @param includeTasks IncludeClosed workflow which are not running.\n     * @param correlationIds Includes tasks associated with workflows.\n     * @return a {@link Map} of {@link String} as key and a list of {@link Workflow} as value\n     */\n    public Map<String, List<Workflow>> getWorkflows(\n            String name, boolean includeClosed, boolean includeTasks, List<String> correlationIds) {\n        Map<String, List<Workflow>> workflowMap = new HashMap<>();\n        for (String correlationId : correlationIds) {\n            List<Workflow> workflows =\n                    executionService.getWorkflowInstances(\n                            name, correlationId, includeClosed, includeTasks);\n            workflowMap.put(correlationId, workflows);\n        }\n        return workflowMap;\n    }\n\n    /**\n     * Gets the workflow by workflow id.\n     *\n     * @param workflowId id of the workflow.\n     * @param includeTasks Includes tasks associated with workflow.\n     * @return an instance of {@link Workflow}\n     */\n    public Workflow getExecutionStatus(String workflowId, boolean includeTasks) {\n        Workflow workflow = executionService.getExecutionStatus(workflowId, includeTasks);\n        if (workflow == null) {\n            throw new NotFoundException(\"Workflow with id: %s not found.\", workflowId);\n        }\n        return workflow;\n    }\n\n    /**\n     * Removes the workflow from the system.\n     *\n     * @param workflowId WorkflowID of the workflow you want to remove from system.\n     * @param archiveWorkflow Archives the workflow and associated tasks instead of removing them.\n     */\n    public void deleteWorkflow(String workflowId, boolean archiveWorkflow) {\n        executionService.removeWorkflow(workflowId, archiveWorkflow);\n    }\n\n    /**\n     * Retrieves all the running workflows.\n     *\n     * @param workflowName Name of the workflow.\n     * @param version Version of the workflow.\n     * @param startTime start time of the workflow.\n     * @param endTime EndTime of the workflow\n     * @return a list of workflow Ids.\n     */\n    public List<String> getRunningWorkflows(\n            String workflowName, Integer version, Long startTime, Long endTime) {\n        if (Optional.ofNullable(startTime).orElse(0L) != 0\n                && Optional.ofNullable(endTime).orElse(0L) != 0) {\n            return workflowExecutor.getWorkflows(workflowName, version, startTime, endTime);\n        } else {\n            version =\n                    Optional.ofNullable(version)\n                            .orElseGet(\n                                    () -> {\n                                        WorkflowDef workflowDef =\n                                                metadataService.getWorkflowDef(workflowName, null);\n                                        return workflowDef.getVersion();\n                                    });\n            return workflowExecutor.getRunningWorkflowIds(workflowName, version);\n        }\n    }\n\n    /**\n     * Starts the decision task for a workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    public void decideWorkflow(String workflowId) {\n        workflowExecutor.decide(workflowId);\n    }\n\n    /**\n     * Pauses the workflow given a workflowId.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    public void pauseWorkflow(String workflowId) {\n        workflowExecutor.pauseWorkflow(workflowId);\n    }\n\n    /**\n     * Resumes the workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    public void resumeWorkflow(String workflowId) {\n        workflowExecutor.resumeWorkflow(workflowId);\n    }\n\n    /**\n     * Skips a given task from a current running workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     * @param taskReferenceName The task reference name.\n     * @param skipTaskRequest {@link SkipTaskRequest} for task you want to skip.\n     */\n    public void skipTaskFromWorkflow(\n            String workflowId, String taskReferenceName, SkipTaskRequest skipTaskRequest) {\n        workflowExecutor.skipTaskFromWorkflow(workflowId, taskReferenceName, skipTaskRequest);\n    }\n\n    /**\n     * Reruns the workflow from a specific task.\n     *\n     * @param workflowId WorkflowId of the workflow you want to rerun.\n     * @param request (@link RerunWorkflowRequest) for the workflow.\n     * @return WorkflowId of the rerun workflow.\n     */\n    public String rerunWorkflow(String workflowId, RerunWorkflowRequest request) {\n        request.setReRunFromWorkflowId(workflowId);\n        return workflowExecutor.rerun(request);\n    }\n\n    /**\n     * Restarts a completed workflow.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     * @param useLatestDefinitions if true, use the latest workflow and task definitions upon\n     *     restart\n     */\n    public void restartWorkflow(String workflowId, boolean useLatestDefinitions) {\n        workflowExecutor.restart(workflowId, useLatestDefinitions);\n    }\n\n    /**\n     * Retries the last failed task.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    public void retryWorkflow(String workflowId, boolean resumeSubworkflowTasks) {\n        workflowExecutor.retry(workflowId, resumeSubworkflowTasks);\n    }\n\n    /**\n     * Resets callback times of all non-terminal SIMPLE tasks to 0.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     */\n    public void resetWorkflow(String workflowId) {\n        workflowExecutor.resetCallbacksForWorkflow(workflowId);\n    }\n\n    /**\n     * Terminate workflow execution.\n     *\n     * @param workflowId WorkflowId of the workflow.\n     * @param reason Reason for terminating the workflow.\n     */\n    public void terminateWorkflow(String workflowId, String reason) {\n        workflowExecutor.terminateWorkflow(workflowId, reason);\n    }\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<WorkflowSummary> searchWorkflows(\n            int start, int size, String sort, String freeText, String query) {\n        return executionService.search(\n                query, freeText, start, size, Utils.convertStringToList(sort));\n    }\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<Workflow> searchWorkflowsV2(\n            int start, int size, String sort, String freeText, String query) {\n        return executionService.searchV2(\n                query, freeText, start, size, Utils.convertStringToList(sort));\n    }\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<WorkflowSummary> searchWorkflows(\n            int start, int size, List<String> sort, String freeText, String query) {\n        return executionService.search(query, freeText, start, size, sort);\n    }\n\n    /**\n     * Search for workflows based on payload and given parameters. Use sort options as sort ASCor\n     * DESC e.g. sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<Workflow> searchWorkflowsV2(\n            int start, int size, List<String> sort, String freeText, String query) {\n        return executionService.searchV2(query, freeText, start, size, sort);\n    }\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<WorkflowSummary> searchWorkflowsByTasks(\n            int start, int size, String sort, String freeText, String query) {\n        return executionService.searchWorkflowByTasks(\n                query, freeText, start, size, Utils.convertStringToList(sort));\n    }\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort Sorting type ASC|DESC\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<Workflow> searchWorkflowsByTasksV2(\n            int start, int size, String sort, String freeText, String query) {\n        return executionService.searchWorkflowByTasksV2(\n                query, freeText, start, size, Utils.convertStringToList(sort));\n    }\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<WorkflowSummary> searchWorkflowsByTasks(\n            int start, int size, List<String> sort, String freeText, String query) {\n        return executionService.searchWorkflowByTasks(query, freeText, start, size, sort);\n    }\n\n    /**\n     * Search for workflows based on task parameters. Use sort options as sort ASC or DESC e.g.\n     * sort=name or sort=workflowId:DESC. If order is not specified, defaults to ASC.\n     *\n     * @param start Start index of pagination\n     * @param size Number of entries\n     * @param sort list of sorting options, separated by \"|\" delimiter\n     * @param freeText Text you want to search\n     * @param query Query you want to search\n     * @return instance of {@link SearchResult}\n     */\n    public SearchResult<Workflow> searchWorkflowsByTasksV2(\n            int start, int size, List<String> sort, String freeText, String query) {\n        return executionService.searchWorkflowByTasksV2(query, freeText, start, size, sort);\n    }\n\n    /**\n     * Get the external storage location where the workflow input payload is stored/to be stored\n     *\n     * @param path the path for which the external storage location is to be populated\n     * @param operation the operation to be performed (read or write)\n     * @param type the type of payload (input or output)\n     * @return {@link ExternalStorageLocation} containing the uri and the path to the payload is\n     *     stored in external storage\n     */\n    public ExternalStorageLocation getExternalStorageLocation(\n            String path, String operation, String type) {\n        return executionService.getExternalStorageLocation(path, operation, type);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/service/WorkflowTestService.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowTestRequest;\nimport com.netflix.conductor.dao.ExecutionDAO;\nimport com.netflix.conductor.model.TaskModel;\n\n@Component\npublic class WorkflowTestService {\n\n    private static final int MAX_LOOPS = 20_000;\n\n    private static final Set<String> operators = new HashSet<>();\n\n    static {\n        operators.add(TaskType.TASK_TYPE_JOIN);\n        operators.add(TaskType.TASK_TYPE_DO_WHILE);\n        operators.add(TaskType.TASK_TYPE_SET_VARIABLE);\n        operators.add(TaskType.TASK_TYPE_FORK);\n        operators.add(TaskType.TASK_TYPE_INLINE);\n        operators.add(TaskType.TASK_TYPE_TERMINATE);\n        operators.add(TaskType.TASK_TYPE_DECISION);\n        operators.add(TaskType.TASK_TYPE_DYNAMIC);\n        operators.add(TaskType.TASK_TYPE_FORK_JOIN);\n        operators.add(TaskType.TASK_TYPE_FORK_JOIN_DYNAMIC);\n        operators.add(TaskType.TASK_TYPE_SWITCH);\n        operators.add(TaskType.TASK_TYPE_SUB_WORKFLOW);\n    }\n\n    private final WorkflowService workflowService;\n\n    private final ExecutionDAO executionDAO;\n\n    private final ExecutionService workflowExecutionService;\n\n    public WorkflowTestService(\n            WorkflowService workflowService,\n            ExecutionDAO executionDAO,\n            ExecutionService workflowExecutionService) {\n        this.workflowService = workflowService;\n        this.executionDAO = executionDAO;\n        this.workflowExecutionService = workflowExecutionService;\n    }\n\n    public Workflow testWorkflow(WorkflowTestRequest request) {\n        request.setName(request.getName());\n        request.setVersion(request.getVersion());\n        String domain = UUID.randomUUID().toString();\n        // Ensure the workflows started for the testing are not picked by any workers\n        request.getTaskToDomain().put(\"*\", domain);\n        String workflowId = workflowService.startWorkflow(request);\n        return testWorkflow(request, workflowId);\n    }\n\n    private Workflow testWorkflow(WorkflowTestRequest request, String workflowId) {\n\n        Map<String, List<WorkflowTestRequest.TaskMock>> mockData = request.getTaskRefToMockOutput();\n        Workflow workflow;\n        int loopCount = 0;\n        do {\n            loopCount++;\n            workflow = workflowService.getExecutionStatus(workflowId, true);\n\n            if (loopCount > MAX_LOOPS) {\n                // Short circuit to avoid large loops\n                return workflow;\n            }\n\n            List<String> runningTasksMissingInput =\n                    workflow.getTasks().stream()\n                            .filter(task -> !operators.contains(task.getTaskType()))\n                            .filter(t -> !t.getStatus().isTerminal())\n                            .filter(t2 -> !mockData.containsKey(t2.getReferenceTaskName()))\n                            .map(task -> task.getReferenceTaskName())\n                            .collect(Collectors.toList());\n\n            if (!runningTasksMissingInput.isEmpty()) {\n                break;\n            }\n            Stream<Task> runningTasks =\n                    workflow.getTasks().stream().filter(t -> !t.getStatus().isTerminal());\n            runningTasks.forEach(\n                    running -> {\n                        if (running.getTaskType().equals(TaskType.SUB_WORKFLOW.name())) {\n                            String subWorkflowId = running.getSubWorkflowId();\n                            WorkflowTestRequest subWorkflowTestRequest =\n                                    request.getSubWorkflowTestRequest()\n                                            .get(running.getReferenceTaskName());\n                            if (subWorkflowId != null && subWorkflowTestRequest != null) {\n                                testWorkflow(subWorkflowTestRequest, subWorkflowId);\n                            }\n                        }\n                        String refName = running.getReferenceTaskName();\n                        List<WorkflowTestRequest.TaskMock> taskMock = mockData.get(refName);\n                        if (taskMock == null\n                                || taskMock.isEmpty()\n                                || operators.contains(running.getTaskType())) {\n                            mockData.remove(refName);\n                            workflowService.decideWorkflow(workflowId);\n                        } else {\n                            WorkflowTestRequest.TaskMock task = taskMock.remove(0);\n                            if (task.getExecutionTime() > 0 || task.getQueueWaitTime() > 0) {\n                                TaskModel existing = executionDAO.getTask(running.getTaskId());\n                                existing.setScheduledTime(\n                                        System.currentTimeMillis()\n                                                - (task.getExecutionTime()\n                                                        + task.getQueueWaitTime()));\n                                existing.setStartTime(\n                                        System.currentTimeMillis() - task.getExecutionTime());\n                                existing.setStatus(\n                                        TaskModel.Status.valueOf(task.getStatus().name()));\n                                existing.getOutputData().putAll(task.getOutput());\n\n                                executionDAO.updateTask(existing);\n                                workflowService.decideWorkflow(workflowId);\n                            } else {\n                                TaskResult taskResult = new TaskResult(running);\n                                taskResult.setStatus(task.getStatus());\n                                taskResult.getOutputData().putAll(task.getOutput());\n                                workflowExecutionService.updateTask(taskResult);\n                            }\n                        }\n                    });\n        } while (!workflow.getStatus().isTerminal() && !mockData.isEmpty());\n\n        return workflow;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/validations/ValidationContext.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.validations;\n\nimport com.netflix.conductor.dao.MetadataDAO;\n\n/**\n * This context is defined to get access to {@link MetadataDAO} inside {@link\n * WorkflowTaskTypeConstraint} constraint validator to validate {@link\n * com.netflix.conductor.common.metadata.workflow.WorkflowTask}.\n */\npublic class ValidationContext {\n\n    private static MetadataDAO metadataDAO;\n\n    public static void initialize(MetadataDAO metadataDAO) {\n        ValidationContext.metadataDAO = metadataDAO;\n    }\n\n    public static MetadataDAO getMetadataDAO() {\n        return metadataDAO;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/com/netflix/conductor/validations/WorkflowTaskTypeConstraint.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.validations;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.text.ParseException;\nimport java.time.format.DateTimeParseException;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport javax.script.ScriptException;\nimport javax.validation.Constraint;\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport javax.validation.Payload;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.events.ScriptEvaluator;\nimport com.netflix.conductor.core.utils.DateTimeUtils;\n\nimport static com.netflix.conductor.core.execution.tasks.Terminate.getTerminationStatusParameter;\nimport static com.netflix.conductor.core.execution.tasks.Terminate.validateInputStatus;\nimport static com.netflix.conductor.core.execution.tasks.Wait.DURATION_INPUT;\nimport static com.netflix.conductor.core.execution.tasks.Wait.UNTIL_INPUT;\n\nimport static java.lang.annotation.ElementType.ANNOTATION_TYPE;\nimport static java.lang.annotation.ElementType.TYPE;\n\n/**\n * This constraint class validates following things. 1. Correct parameters are set depending on task\n * type.\n */\n@Documented\n@Constraint(validatedBy = WorkflowTaskTypeConstraint.WorkflowTaskValidator.class)\n@Target({TYPE, ANNOTATION_TYPE})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface WorkflowTaskTypeConstraint {\n\n    String message() default \"\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n    class WorkflowTaskValidator\n            implements ConstraintValidator<WorkflowTaskTypeConstraint, WorkflowTask> {\n\n        final String PARAM_REQUIRED_STRING_FORMAT =\n                \"%s field is required for taskType: %s taskName: %s\";\n\n        @Override\n        public void initialize(WorkflowTaskTypeConstraint constraintAnnotation) {}\n\n        @Override\n        public boolean isValid(WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            context.disableDefaultConstraintViolation();\n\n            boolean valid = true;\n\n            // depending on task type check if required parameters are set or not\n            switch (workflowTask.getType()) {\n                case TaskType.TASK_TYPE_EVENT:\n                    valid = isEventTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_DECISION:\n                    valid = isDecisionTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_SWITCH:\n                    valid = isSwitchTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_DYNAMIC:\n                    valid = isDynamicTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_FORK_JOIN_DYNAMIC:\n                    valid = isDynamicForkJoinValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_HTTP:\n                    valid = isHttpTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_FORK_JOIN:\n                    valid = isForkJoinTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_TERMINATE:\n                    valid = isTerminateTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_KAFKA_PUBLISH:\n                    valid = isKafkaPublishTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_DO_WHILE:\n                    valid = isDoWhileTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_SUB_WORKFLOW:\n                    valid = isSubWorkflowTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_JSON_JQ_TRANSFORM:\n                    valid = isJSONJQTransformTaskValid(workflowTask, context);\n                    break;\n                case TaskType.TASK_TYPE_WAIT:\n                    valid = isWaitTaskValid(workflowTask, context);\n                    break;\n            }\n\n            return valid;\n        }\n\n        private boolean isEventTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            if (workflowTask.getSink() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"sink\",\n                                TaskType.TASK_TYPE_EVENT,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            return valid;\n        }\n\n        private boolean isDecisionTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            if (workflowTask.getCaseValueParam() == null\n                    && workflowTask.getCaseExpression() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"caseValueParam or caseExpression\",\n                                TaskType.DECISION,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            if (workflowTask.getDecisionCases() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"decisionCases\",\n                                TaskType.DECISION,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            } else if ((workflowTask.getDecisionCases() != null\n                            || workflowTask.getCaseExpression() != null)\n                    && (workflowTask.getDecisionCases().size() == 0)) {\n                String message =\n                        String.format(\n                                \"decisionCases should have atleast one task for taskType: %s taskName: %s\",\n                                TaskType.DECISION, workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            if (workflowTask.getCaseExpression() != null) {\n                try {\n                    validateScriptExpression(\n                            workflowTask.getCaseExpression(), workflowTask.getInputParameters());\n                } catch (Exception ee) {\n                    String message =\n                            String.format(\n                                    ee.getMessage() + \", taskType: DECISION taskName %s\",\n                                    workflowTask.getName());\n                    context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                    valid = false;\n                }\n            }\n\n            return valid;\n        }\n\n        private void validateScriptExpression(\n                String expression, Map<String, Object> inputParameters) {\n            try {\n                Object returnValue = ScriptEvaluator.eval(expression, inputParameters);\n            } catch (ScriptException e) {\n                throw new IllegalArgumentException(\n                        String.format(\"Expression is not well formatted: %s\", e.getMessage()));\n            }\n        }\n\n        private boolean isSwitchTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            if (workflowTask.getEvaluatorType() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"evaluatorType\",\n                                TaskType.SWITCH,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            } else if (workflowTask.getExpression() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"expression\",\n                                TaskType.SWITCH,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            if (workflowTask.getDecisionCases() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"decisionCases\",\n                                TaskType.SWITCH,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            } else if (workflowTask.getDecisionCases() != null\n                    && workflowTask.getDecisionCases().size() == 0) {\n                String message =\n                        String.format(\n                                \"decisionCases should have atleast one task for taskType: %s taskName: %s\",\n                                TaskType.SWITCH, workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            if (\"javascript\".equals(workflowTask.getEvaluatorType())\n                    && workflowTask.getExpression() != null) {\n                try {\n                    validateScriptExpression(\n                            workflowTask.getExpression(), workflowTask.getInputParameters());\n                } catch (Exception ee) {\n                    String message =\n                            String.format(\n                                    ee.getMessage() + \", taskType: SWITCH taskName %s\",\n                                    workflowTask.getName());\n                    context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                    valid = false;\n                }\n            }\n            return valid;\n        }\n\n        private boolean isDoWhileTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            if (workflowTask.getLoopCondition() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"loopExpression\",\n                                TaskType.DO_WHILE,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            if (workflowTask.getLoopOver() == null || workflowTask.getLoopOver().size() == 0) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"loopover\",\n                                TaskType.DO_WHILE,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            return valid;\n        }\n\n        private boolean isDynamicTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            if (workflowTask.getDynamicTaskNameParam() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"dynamicTaskNameParam\",\n                                TaskType.DYNAMIC,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            return valid;\n        }\n\n        private boolean isWaitTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            String duration =\n                    Optional.ofNullable(workflowTask.getInputParameters().get(DURATION_INPUT))\n                            .orElse(\"\")\n                            .toString();\n            String until =\n                    Optional.ofNullable(workflowTask.getInputParameters().get(UNTIL_INPUT))\n                            .orElse(\"\")\n                            .toString();\n\n            if (StringUtils.isNotBlank(duration) && StringUtils.isNotBlank(until)) {\n                String message =\n                        \"Both 'duration' and 'until' specified. Please provide only one input\";\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            try {\n                if (StringUtils.isNotBlank(duration) && !(duration.startsWith(\"${\"))) {\n                    DateTimeUtils.parseDuration(duration);\n                } else if (StringUtils.isNotBlank(until) && !(until.startsWith(\"${\"))) {\n                    DateTimeUtils.parseDate(until);\n                }\n            } catch (DateTimeParseException e) {\n                String message = \"Unable to parse date \";\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            } catch (IllegalArgumentException e) {\n                String message = \"Either date or duration is passed as null \";\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            } catch (ParseException e) {\n                String message = \"Unable to parse date \";\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            } catch (Exception e) {\n                String message = \"Wait time specified is invalid.  The duration must be in \";\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            return valid;\n        }\n\n        private boolean isDynamicForkJoinValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n\n            // For DYNAMIC_FORK_JOIN_TASK support dynamicForkJoinTasksParam or combination of\n            // dynamicForkTasksParam and dynamicForkTasksInputParamName.\n            // Both are not allowed.\n            if (workflowTask.getDynamicForkJoinTasksParam() != null\n                    && (workflowTask.getDynamicForkTasksParam() != null\n                            || workflowTask.getDynamicForkTasksInputParamName() != null)) {\n                String message =\n                        String.format(\n                                \"dynamicForkJoinTasksParam or combination of dynamicForkTasksInputParamName and dynamicForkTasksParam cam be used for taskType: %s taskName: %s\",\n                                TaskType.FORK_JOIN_DYNAMIC, workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                return false;\n            }\n\n            if (workflowTask.getDynamicForkJoinTasksParam() != null) {\n                return valid;\n            } else {\n                if (workflowTask.getDynamicForkTasksParam() == null) {\n                    String message =\n                            String.format(\n                                    PARAM_REQUIRED_STRING_FORMAT,\n                                    \"dynamicForkTasksParam\",\n                                    TaskType.FORK_JOIN_DYNAMIC,\n                                    workflowTask.getName());\n                    context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                    valid = false;\n                }\n                if (workflowTask.getDynamicForkTasksInputParamName() == null) {\n                    String message =\n                            String.format(\n                                    PARAM_REQUIRED_STRING_FORMAT,\n                                    \"dynamicForkTasksInputParamName\",\n                                    TaskType.FORK_JOIN_DYNAMIC,\n                                    workflowTask.getName());\n                    context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                    valid = false;\n                }\n            }\n\n            return valid;\n        }\n\n        private boolean isHttpTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            boolean isInputParameterSet = false;\n            boolean isInputTemplateSet = false;\n\n            // Either http_request in WorkflowTask inputParam should be set or in inputTemplate\n            // Taskdef should be set\n            if (workflowTask.getInputParameters() != null\n                    && workflowTask.getInputParameters().containsKey(\"http_request\")) {\n                isInputParameterSet = true;\n            }\n\n            TaskDef taskDef =\n                    Optional.ofNullable(workflowTask.getTaskDefinition())\n                            .orElse(\n                                    ValidationContext.getMetadataDAO()\n                                            .getTaskDef(workflowTask.getName()));\n\n            if (taskDef != null\n                    && taskDef.getInputTemplate() != null\n                    && taskDef.getInputTemplate().containsKey(\"http_request\")) {\n                isInputTemplateSet = true;\n            }\n\n            if (!(isInputParameterSet || isInputTemplateSet)) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"inputParameters.http_request\",\n                                TaskType.HTTP,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            return valid;\n        }\n\n        private boolean isForkJoinTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n\n            if (workflowTask.getForkTasks() != null && (workflowTask.getForkTasks().size() == 0)) {\n                String message =\n                        String.format(\n                                \"forkTasks should have atleast one task for taskType: %s taskName: %s\",\n                                TaskType.FORK_JOIN, workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            return valid;\n        }\n\n        private boolean isTerminateTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            Object inputStatusParam =\n                    workflowTask.getInputParameters().get(getTerminationStatusParameter());\n            if (workflowTask.isOptional()) {\n                String message =\n                        String.format(\n                                \"terminate task cannot be optional, taskName: %s\",\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            if (inputStatusParam == null || !validateInputStatus(inputStatusParam.toString())) {\n                String message =\n                        String.format(\n                                \"terminate task must have an %s parameter and must be set to COMPLETED or FAILED, taskName: %s\",\n                                getTerminationStatusParameter(), workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            return valid;\n        }\n\n        private boolean isKafkaPublishTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            boolean isInputParameterSet = false;\n            boolean isInputTemplateSet = false;\n\n            // Either kafka_request in WorkflowTask inputParam should be set or in inputTemplate\n            // Taskdef should be set\n            if (workflowTask.getInputParameters() != null\n                    && workflowTask.getInputParameters().containsKey(\"kafka_request\")) {\n                isInputParameterSet = true;\n            }\n\n            TaskDef taskDef =\n                    Optional.ofNullable(workflowTask.getTaskDefinition())\n                            .orElse(\n                                    ValidationContext.getMetadataDAO()\n                                            .getTaskDef(workflowTask.getName()));\n\n            if (taskDef != null\n                    && taskDef.getInputTemplate() != null\n                    && taskDef.getInputTemplate().containsKey(\"kafka_request\")) {\n                isInputTemplateSet = true;\n            }\n\n            if (!(isInputParameterSet || isInputTemplateSet)) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"inputParameters.kafka_request\",\n                                TaskType.KAFKA_PUBLISH,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            return valid;\n        }\n\n        private boolean isSubWorkflowTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            if (workflowTask.getSubWorkflowParam() == null) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"subWorkflowParam\",\n                                TaskType.SUB_WORKFLOW,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n            return valid;\n        }\n\n        private boolean isJSONJQTransformTaskValid(\n                WorkflowTask workflowTask, ConstraintValidatorContext context) {\n            boolean valid = true;\n            boolean isInputParameterSet = false;\n            boolean isInputTemplateSet = false;\n\n            // Either queryExpression in WorkflowTask inputParam should be set or in inputTemplate\n            // Taskdef should be set\n            if (workflowTask.getInputParameters() != null\n                    && workflowTask.getInputParameters().containsKey(\"queryExpression\")) {\n                isInputParameterSet = true;\n            }\n\n            TaskDef taskDef =\n                    Optional.ofNullable(workflowTask.getTaskDefinition())\n                            .orElse(\n                                    ValidationContext.getMetadataDAO()\n                                            .getTaskDef(workflowTask.getName()));\n\n            if (taskDef != null\n                    && taskDef.getInputTemplate() != null\n                    && taskDef.getInputTemplate().containsKey(\"queryExpression\")) {\n                isInputTemplateSet = true;\n            }\n\n            if (!(isInputParameterSet || isInputTemplateSet)) {\n                String message =\n                        String.format(\n                                PARAM_REQUIRED_STRING_FORMAT,\n                                \"inputParameters.queryExpression\",\n                                TaskType.JSON_JQ_TRANSFORM,\n                                workflowTask.getName());\n                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();\n                valid = false;\n            }\n\n            return valid;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"properties\": [\n    {\n      \"name\": \"conductor.workflow-reconciler.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enables the workflow reconciliation mechanism.\",\n      \"sourceType\": \"com.netflix.conductor.core.reconciliation.WorkflowReconciler\",\n      \"defaultValue\": true\n    },\n    {\n      \"name\": \"conductor.sweep-frequency.millis\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"The frequency in milliseconds, at which the workflow sweeper should evaluate active workflows.\",\n      \"sourceType\": \"com.netflix.conductor.core.reconciliation.WorkflowReconciler\",\n      \"defaultValue\": 500\n    },\n    {\n      \"name\": \"conductor.workflow-repair-service.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Configuration to enable WorkflowRepairService, that tries to keep ExecutionDAO and QueueDAO in sync, based on the task or workflow state. This is disabled by default; To enable, the Queueing layer must implement QueueDAO.containsMessage method.\",\n      \"sourceType\": \"com.netflix.conductor.core.reconciliation.WorkflowRepairService\"\n    },\n    {\n      \"name\": \"conductor.system-task-workers.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Configuration to enable SystemTaskWorkerCoordinator, that polls and executes the asynchronous system tasks.\",\n      \"sourceType\": \"com.netflix.conductor.core.execution.tasks.SystemTaskWorkerCoordinator\",\n      \"defaultValue\": true\n    },\n    {\n      \"name\": \"conductor.app.isolated-system-task-enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Used to enable/disable use of isolation groups for system task workers.\"\n    },\n    {\n      \"name\": \"conductor.app.isolatedSystemTaskPollIntervalSecs\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"The time interval (in seconds) at which new isolated task queues will be polled and added to the system task queue repository.\"\n    },\n    {\n      \"name\": \"conductor.app.taskPendingTimeThresholdMins\",\n      \"type\": \"java.lang.Long\",\n      \"description\": \"The time threshold (in minutes) beyond which a warning log will be emitted for a task if it stays in the same state for this duration.\"\n    },\n    {\n      \"name\": \"conductor.workflow-monitor.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enables the workflow monitor that publishes workflow and task metrics.\",\n      \"defaultValue\": \"true\",\n      \"sourceType\": \"com.netflix.conductor.metrics.WorkflowMonitor\"\n    },\n    {\n      \"name\": \"conductor.workflow-monitor.stats.initial-delay\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"The initial delay (in milliseconds) at which the workflow monitor publishes workflow and task metrics.\"\n    },\n    {\n      \"name\": \"conductor.workflow-monitor.metadata-refresh-interval\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"The interval (counter) after which the workflow monitor refreshes the metadata definitions from the datastore.\",\n      \"defaultValue\": \"10\"\n    },\n    {\n      \"name\": \"conductor.workflow-monitor.stats.delay\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"The delay (in milliseconds) at which the workflow monitor publishes workflow and task metrics.\"\n    },\n    {\n      \"name\": \"conductor.external-payload-storage.type\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"The type of payload storage to be used for externalizing large payloads.\"\n    },\n    {\n      \"name\": \"conductor.default-event-processor.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enables the default event processor for handling events.\",\n      \"sourceType\": \"com.netflix.conductor.core.events.DefaultEventProcessor\",\n      \"defaultValue\": \"true\"\n    },\n    {\n      \"name\": \"conductor.event-queues.default.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enables the use of the underlying queue implementation to provide queues for consuming events.\",\n      \"sourceType\": \"com.netflix.conductor.core.events.queue.ConductorEventQueueProvider\",\n      \"defaultValue\": \"true\"\n    },\n    {\n      \"name\": \"conductor.default-event-queue-processor.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enables the processor for the default event queues that conductor is configured to listen on.\",\n      \"sourceType\": \"com.netflix.conductor.core.events.queue.DefaultEventQueueProcessor\",\n      \"defaultValue\": \"true\"\n    },\n    {\n      \"name\": \"conductor.workflow-status-listener.type\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"The implementation of the workflow status listener to be used.\"\n    },\n    {\n      \"name\": \"conductor.task-status-listener.type\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"The implementation of the task status listener to be used.\"\n    },\n    {\n      \"name\": \"conductor.workflow-execution-lock.type\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"The implementation of the workflow execution lock to be used.\",\n      \"defaultValue\": \"noop_lock\"\n    }\n  ],\n  \"hints\": [\n    {\n      \"name\": \"conductor.external-payload-storage.type\",\n      \"values\": [\n        {\n          \"value\": \"dummy\",\n          \"description\": \"Use the dummy no-op implementation as the external payload storage.\"\n        }\n      ]\n    },\n    {\n      \"name\": \"conductor.workflow-status-listener.type\",\n      \"values\": [\n        {\n          \"value\": \"stub\",\n          \"description\": \"Use the no-op implementation of the workflow status listener.\"\n        }\n      ]\n    },\n    {\n      \"name\": \"conductor.workflow-execution-lock.type\",\n      \"values\": [\n        {\n          \"value\": \"noop_lock\",\n          \"description\": \"Use the no-op implementation as the lock provider.\"\n        },\n        {\n          \"value\": \"local_only\",\n          \"description\": \"Use the local in-memory cache based implementation as the lock provider.\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "core/src/main/resources/META-INF/validation/constraints.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n\n    Copyright 2020 Netflix, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n-->\n<constraint-mappings\n  xmlns=\"http://xmlns.jcp.org/xml/ns/validation/mapping\"\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/validation/mapping\n            http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd\"\n  version=\"2.0\">\n  <default-package>com.netflix.conductor.common.metadata.workflow</default-package>\n\n  <bean class=\"WorkflowTask\" ignore-annotations=\"false\">\n    <class ignore-annotations=\"false\">\n      <constraint annotation=\"com.netflix.conductor.validations.WorkflowTaskTypeConstraint\"/>\n    </class>\n  </bean>\n</constraint-mappings>"
  },
  {
    "path": "core/src/main/resources/META-INF/validation.xml",
    "content": "<!--\n\n    Copyright 2020 Netflix, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n-->\n<validation-config\n  xmlns=\"http://xmlns.jcp.org/xml/ns/validation/configuration\"\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/validation/configuration\n            http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd\"\n  version=\"2.0\">\n\n  <constraint-mapping>META-INF/validation/constraints.xml</constraint-mapping>\n\n</validation-config>"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/core/execution/AsyncSystemTaskExecutorTest.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution\n\nimport java.time.Duration\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.core.config.ConductorProperties\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask\nimport com.netflix.conductor.core.operation.StartWorkflowOperation\nimport com.netflix.conductor.core.utils.IDGenerator\nimport com.netflix.conductor.core.utils.QueueUtils\nimport com.netflix.conductor.dao.MetadataDAO\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.model.TaskModel\nimport com.netflix.conductor.model.WorkflowModel\n\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.SUB_WORKFLOW\n\nclass AsyncSystemTaskExecutorTest extends Specification {\n\n    ExecutionDAOFacade executionDAOFacade\n    QueueDAO queueDAO\n    MetadataDAO metadataDAO\n    WorkflowExecutor workflowExecutor\n    StartWorkflowOperation startWorkflowOperation\n\n    @Subject\n    AsyncSystemTaskExecutor executor\n\n    WorkflowSystemTask workflowSystemTask\n    ConductorProperties properties = new ConductorProperties()\n\n    def setup() {\n        executionDAOFacade = Mock(ExecutionDAOFacade.class)\n        queueDAO = Mock(QueueDAO.class)\n        metadataDAO = Mock(MetadataDAO.class)\n        workflowExecutor = Mock(WorkflowExecutor.class)\n        startWorkflowOperation = Mock(StartWorkflowOperation.class)\n\n        workflowSystemTask = Mock(WorkflowSystemTask.class) {\n            isTaskRetrievalRequired() >> true\n        }\n\n        properties.taskExecutionPostponeDuration = Duration.ofSeconds(1)\n        properties.systemTaskWorkerCallbackDuration = Duration.ofSeconds(1)\n\n        executor = new AsyncSystemTaskExecutor(executionDAOFacade, queueDAO, metadataDAO, properties, workflowExecutor)\n    }\n\n    // this is not strictly a unit test, but its essential to test AsyncSystemTaskExecutor with SubWorkflow\n    def \"Execute SubWorkflow task\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String subWorkflowId = \"subWorkflowId\"\n        SubWorkflow subWorkflowTask = new SubWorkflow(new ObjectMapper(), startWorkflowOperation)\n\n        String task1Id = new IDGenerator().generate()\n        TaskModel task1 = new TaskModel()\n        task1.setTaskType(SUB_WORKFLOW.name())\n        task1.setReferenceTaskName(\"waitTask\")\n        task1.setWorkflowInstanceId(workflowId)\n        task1.setScheduledTime(System.currentTimeMillis())\n        task1.setTaskId(task1Id)\n        task1.getInputData().put(\"asyncComplete\", true)\n        task1.getInputData().put(\"subWorkflowName\", \"junit1\")\n        task1.getInputData().put(\"subWorkflowVersion\", 1)\n        task1.setStatus(TaskModel.Status.SCHEDULED)\n\n        String queueName = QueueUtils.getQueueName(task1)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.RUNNING)\n        WorkflowModel subWorkflow = new WorkflowModel(workflowId: subWorkflowId, status: WorkflowModel.Status.RUNNING)\n\n        when:\n        executor.execute(subWorkflowTask, task1Id)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(task1Id) >> task1\n        1 * executionDAOFacade.getWorkflowModel(workflowId, subWorkflowTask.isTaskRetrievalRequired()) >> workflow\n        1 * startWorkflowOperation.execute(*_) >> subWorkflowId\n        1 * workflowExecutor.getWorkflow(subWorkflowId, false) >> subWorkflow\n\n        // SUB_WORKFLOW is asyncComplete so its removed from the queue\n        1 * queueDAO.remove(queueName, task1Id)\n\n        task1.status == TaskModel.Status.IN_PROGRESS\n        task1.subWorkflowId == subWorkflowId\n        task1.startTime != 0\n    }\n\n    def \"Execute with a non-existing task id\"() {\n        given:\n        String taskId = \"taskId\"\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> null\n        0 * workflowSystemTask.start(*_)\n        0 * executionDAOFacade.updateTask(_)\n    }\n\n    def \"Execute with a task id that fails to load\"() {\n        given:\n        String taskId = \"taskId\"\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> { throw new RuntimeException(\"datastore unavailable\") }\n        0 * workflowSystemTask.start(*_)\n        0 * executionDAOFacade.updateTask(_)\n    }\n\n    def \"Execute with a task id that is in terminal state\"() {\n        given:\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.COMPLETED, taskId: taskId)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * queueDAO.remove(task.taskType, taskId)\n        0 * workflowSystemTask.start(*_)\n        0 * executionDAOFacade.updateTask(_)\n    }\n\n    def \"Execute with a task id that is part of a workflow in terminal state\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.COMPLETED)\n        String queueName = QueueUtils.getQueueName(task)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.getWorkflowModel(workflowId, true) >> workflow\n        1 * queueDAO.remove(queueName, taskId)\n\n        task.status == TaskModel.Status.CANCELED\n        task.startTime == 0\n    }\n\n    def \"Execute with a task id that exceeds in-progress limit\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId,\n                workflowPriority: 10)\n        String queueName = QueueUtils.getQueueName(task)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.exceedsInProgressLimit(task) >> true\n        1 * queueDAO.postpone(queueName, taskId, task.workflowPriority, properties.taskExecutionPostponeDuration.seconds)\n\n        task.status == TaskModel.Status.SCHEDULED\n        task.startTime == 0\n    }\n\n    def \"Execute with a task id that is rate limited\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId,\n                rateLimitPerFrequency: 1, taskDefName: \"taskDefName\", workflowPriority: 10)\n        String queueName = QueueUtils.getQueueName(task)\n        TaskDef taskDef = new TaskDef()\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * metadataDAO.getTaskDef(task.taskDefName) >> taskDef\n        1 * executionDAOFacade.exceedsRateLimitPerFrequency(task, taskDef) >> taskDef\n        1 * queueDAO.postpone(queueName, taskId, task.workflowPriority, properties.taskExecutionPostponeDuration.seconds)\n\n        task.status == TaskModel.Status.SCHEDULED\n        task.startTime == 0\n    }\n\n    def \"Execute with a task id that is rate limited but postpone fails\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId,\n                rateLimitPerFrequency: 1, taskDefName: \"taskDefName\", workflowPriority: 10)\n        String queueName = QueueUtils.getQueueName(task)\n        TaskDef taskDef = new TaskDef()\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * metadataDAO.getTaskDef(task.taskDefName) >> taskDef\n        1 * executionDAOFacade.exceedsRateLimitPerFrequency(task, taskDef) >> taskDef\n        1 * queueDAO.postpone(queueName, taskId, task.workflowPriority, properties.taskExecutionPostponeDuration.seconds) >> { throw new RuntimeException(\"queue unavailable\") }\n\n        task.status == TaskModel.Status.SCHEDULED\n        task.startTime == 0\n    }\n\n    def \"Execute with a task id that is in SCHEDULED state\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId,\n                taskDefName: \"taskDefName\", workflowPriority: 10)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.RUNNING)\n        String queueName = QueueUtils.getQueueName(task)\n        workflowSystemTask.getEvaluationOffset(task, 1) >> Optional.empty();\n\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.getWorkflowModel(workflowId, true) >> workflow\n        1 * executionDAOFacade.updateTask(task)\n        1 * queueDAO.postpone(queueName, taskId, task.workflowPriority, properties.systemTaskWorkerCallbackDuration.seconds)\n        1 * workflowSystemTask.start(workflow, task, workflowExecutor) >> { task.status = TaskModel.Status.IN_PROGRESS }\n\n        0 * workflowExecutor.decide(workflowId) // verify that workflow is NOT decided\n\n        task.status == TaskModel.Status.IN_PROGRESS\n        task.startTime != 0 // verify that startTime is set\n        task.endTime == 0 // verify that endTime is not set\n        task.pollCount == 1 // verify that poll count is incremented\n        task.callbackAfterSeconds == properties.systemTaskWorkerCallbackDuration.seconds\n    }\n\n    def \"Execute with a task id that is in SCHEDULED state and WorkflowSystemTask.start sets the task in a terminal state\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId,\n                taskDefName: \"taskDefName\", workflowPriority: 10)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.RUNNING)\n        String queueName = QueueUtils.getQueueName(task)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.getWorkflowModel(workflowId, true) >> workflow\n        1 * executionDAOFacade.updateTask(task)\n\n        1 * workflowSystemTask.start(workflow, task, workflowExecutor) >> { task.status = TaskModel.Status.COMPLETED }\n        1 * queueDAO.remove(queueName, taskId)\n        1 * workflowExecutor.decide(workflowId) // verify that workflow is decided\n\n        task.status == TaskModel.Status.COMPLETED\n        task.startTime != 0 // verify that startTime is set\n        task.endTime != 0 // verify that endTime is set\n        task.pollCount == 1 // verify that poll count is incremented\n    }\n\n    def \"Execute with a task id that is in SCHEDULED state but WorkflowSystemTask.start fails\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId,\n                taskDefName: \"taskDefName\", workflowPriority: 10)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.RUNNING)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.getWorkflowModel(workflowId, true) >> workflow\n        1 * executionDAOFacade.updateTask(task)\n\n        // simulating a \"start\" failure that happens after the Task object is modified\n        // the modification will be persisted\n        1 * workflowSystemTask.start(workflow, task, workflowExecutor) >> {\n            task.status = TaskModel.Status.IN_PROGRESS\n            throw new RuntimeException(\"unknown system task failure\")\n        }\n\n        0 * workflowExecutor.decide(workflowId) // verify that workflow is NOT decided\n\n        task.status == TaskModel.Status.IN_PROGRESS\n        task.startTime != 0 // verify that startTime is set\n        task.endTime == 0 // verify that endTime is not set\n        task.pollCount == 1 // verify that poll count is incremented\n    }\n\n    def \"Execute with a task id that is in SCHEDULED state and is set to asyncComplete\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.SCHEDULED, taskId: taskId, workflowInstanceId: workflowId,\n                taskDefName: \"taskDefName\", workflowPriority: 10)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.RUNNING)\n        String queueName = QueueUtils.getQueueName(task)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.getWorkflowModel(workflowId, true) >> workflow\n        1 * executionDAOFacade.updateTask(task) // 1st call for pollCount, 2nd call for status update\n\n        1 * workflowSystemTask.isAsyncComplete(task) >> true\n        1 * workflowSystemTask.start(workflow, task, workflowExecutor) >> { task.status = TaskModel.Status.IN_PROGRESS }\n        1 * queueDAO.remove(queueName, taskId)\n\n        1 * workflowExecutor.decide(workflowId) // verify that workflow is decided\n\n        task.status == TaskModel.Status.IN_PROGRESS\n        task.startTime != 0 // verify that startTime is set\n        task.endTime == 0 // verify that endTime is not set\n        task.pollCount == 1 // verify that poll count is incremented\n    }\n\n    def \"Execute with a task id that is in IN_PROGRESS state\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.IN_PROGRESS, taskId: taskId, workflowInstanceId: workflowId,\n                rateLimitPerFrequency: 1, taskDefName: \"taskDefName\", workflowPriority: 10, pollCount: 1)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.RUNNING)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.getWorkflowModel(workflowId, true) >> workflow\n        1 * executionDAOFacade.updateTask(task) // 1st call for pollCount, 2nd call for status update\n\n        0 * workflowSystemTask.start(workflow, task, workflowExecutor)\n        1 * workflowSystemTask.execute(workflow, task, workflowExecutor)\n\n        task.status == TaskModel.Status.IN_PROGRESS\n        task.endTime == 0 // verify that endTime is not set\n        task.pollCount == 2 // verify that poll count is incremented\n    }\n\n    def \"Execute with a task id that is in IN_PROGRESS state and is set to asyncComplete\"() {\n        given:\n        String workflowId = \"workflowId\"\n        String taskId = \"taskId\"\n        TaskModel task = new TaskModel(taskType: \"type1\", status: TaskModel.Status.IN_PROGRESS, taskId: taskId, workflowInstanceId: workflowId,\n                rateLimitPerFrequency: 1, taskDefName: \"taskDefName\", workflowPriority: 10, pollCount: 1)\n        WorkflowModel workflow = new WorkflowModel(workflowId: workflowId, status: WorkflowModel.Status.RUNNING)\n\n        when:\n        executor.execute(workflowSystemTask, taskId)\n\n        then:\n        1 * executionDAOFacade.getTaskModel(taskId) >> task\n        1 * executionDAOFacade.getWorkflowModel(workflowId, true) >> workflow\n        1 * executionDAOFacade.updateTask(task) // only one call since pollCount is not incremented\n\n        1 * workflowSystemTask.isAsyncComplete(task) >> true\n        0 * workflowSystemTask.start(workflow, task, workflowExecutor)\n        1 * workflowSystemTask.execute(workflow, task, workflowExecutor)\n\n        task.status == TaskModel.Status.IN_PROGRESS\n        task.endTime == 0 // verify that endTime is not set\n        task.pollCount == 1 // verify that poll count is NOT incremented\n    }\n\n}\n"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/core/execution/tasks/DoWhileSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask\nimport com.netflix.conductor.common.utils.TaskUtils\nimport com.netflix.conductor.core.exception.TerminateWorkflowException\nimport com.netflix.conductor.core.execution.WorkflowExecutor\nimport com.netflix.conductor.core.utils.ParametersUtils\nimport com.netflix.conductor.model.TaskModel\nimport com.netflix.conductor.model.WorkflowModel\n\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_DO_WHILE\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_HTTP\n\nclass DoWhileSpec extends Specification {\n\n    @Subject\n    DoWhile doWhile\n\n    WorkflowExecutor workflowExecutor\n    ObjectMapper objectMapper\n    ParametersUtils parametersUtils\n    TaskModel doWhileTaskModel\n\n    WorkflowTask task1, task2\n    TaskModel taskModel1, taskModel2\n\n    def setup() {\n        objectMapper = new ObjectMapper();\n        workflowExecutor = Mock(WorkflowExecutor.class)\n        parametersUtils = new ParametersUtils(objectMapper)\n\n        task1 = new WorkflowTask(name: 'task1', taskReferenceName: 'task1')\n        task2 = new WorkflowTask(name: 'task2', taskReferenceName: 'task2')\n\n        doWhile = new DoWhile(parametersUtils)\n    }\n\n    def \"first iteration\"() {\n        given:\n        WorkflowTask doWhileWorkflowTask = new WorkflowTask(taskReferenceName: 'doWhileTask', type: TASK_TYPE_DO_WHILE)\n        doWhileWorkflowTask.loopCondition = \"if (\\$.doWhileTask['iteration'] < 1) { true; } else { false; }\"\n        doWhileWorkflowTask.loopOver = [task1, task2]\n        doWhileTaskModel = new TaskModel(workflowTask: doWhileWorkflowTask, taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE, referenceTaskName: doWhileWorkflowTask.taskReferenceName)\n\n        def workflowModel = new WorkflowModel()\n        workflowModel.tasks = [doWhileTaskModel]\n\n        when:\n        def retVal = doWhile.execute(workflowModel, doWhileTaskModel, workflowExecutor)\n\n        then: \"verify that return value is true, iteration value is updated in DO_WHILE TaskModel\"\n        retVal\n\n        and: \"verify the iteration value\"\n        doWhileTaskModel.iteration == 1\n        doWhileTaskModel.outputData['iteration'] == 1\n\n        and: \"verify whether the first task is scheduled\"\n        1 * workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflowModel)\n    }\n\n    def \"an iteration - one task is complete and other is not scheduled\"() {\n        given: \"WorkflowModel consists of one iteration of one task inside DO_WHILE already completed\"\n        taskModel1 = createTaskModel(task1)\n\n        and: \"loop over contains two tasks\"\n        WorkflowTask doWhileWorkflowTask = new WorkflowTask(taskReferenceName: 'doWhileTask', type: TASK_TYPE_DO_WHILE)\n        doWhileWorkflowTask.loopCondition = \"if (\\$.doWhileTask['iteration'] < 2) { true; } else { false; }\"\n        doWhileWorkflowTask.loopOver = [task1, task2] // two tasks\n\n        doWhileTaskModel = new TaskModel(workflowTask: doWhileWorkflowTask, taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE, referenceTaskName: doWhileWorkflowTask.taskReferenceName)\n        doWhileTaskModel.iteration = 1\n        doWhileTaskModel.outputData['iteration'] = 1\n        doWhileTaskModel.status = TaskModel.Status.IN_PROGRESS\n\n        def workflowModel = new WorkflowModel(workflowDefinition: new WorkflowDef(name: 'test_workflow'))\n        // setup the WorkflowModel\n        workflowModel.tasks = [doWhileTaskModel, taskModel1]\n\n        // this is the expected format of iteration 1's output data\n        def iteration1OutputData = [:]\n        iteration1OutputData[task1.taskReferenceName] = taskModel1.outputData\n\n        when:\n        def retVal = doWhile.execute(workflowModel, doWhileTaskModel, workflowExecutor)\n\n        then: \"verify that the return value is false, since the iteration is not complete\"\n        !retVal\n\n        and: \"verify that the next iteration is NOT scheduled\"\n        0 * workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflowModel)\n    }\n\n    def \"next iteration - one iteration of all tasks inside DO_WHILE are complete\"() {\n        given: \"WorkflowModel consists of one iteration of tasks inside DO_WHILE already completed\"\n        taskModel1 = createTaskModel(task1)\n        taskModel2 = createTaskModel(task2)\n\n        WorkflowTask doWhileWorkflowTask = new WorkflowTask(taskReferenceName: 'doWhileTask', type: TASK_TYPE_DO_WHILE)\n        doWhileWorkflowTask.loopCondition = \"if (\\$.doWhileTask['iteration'] < 2) { true; } else { false; }\"\n        doWhileWorkflowTask.loopOver = [task1, task2]\n\n        doWhileTaskModel = new TaskModel(workflowTask: doWhileWorkflowTask, taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE, referenceTaskName: doWhileWorkflowTask.taskReferenceName)\n        doWhileTaskModel.iteration = 1\n        doWhileTaskModel.outputData['iteration'] = 1\n        doWhileTaskModel.status = TaskModel.Status.IN_PROGRESS\n\n        def workflowModel = new WorkflowModel(workflowDefinition: new WorkflowDef(name: 'test_workflow'))\n        // setup the WorkflowModel\n        workflowModel.tasks = [doWhileTaskModel, taskModel1, taskModel2]\n\n        // this is the expected format of iteration 1's output data\n        def iteration1OutputData = [:]\n        iteration1OutputData[task1.taskReferenceName] = taskModel1.outputData\n        iteration1OutputData[task2.taskReferenceName] = taskModel2.outputData\n\n        when:\n        def retVal = doWhile.execute(workflowModel, doWhileTaskModel, workflowExecutor)\n\n        then: \"verify that the return value is true, since the iteration is updated\"\n        retVal\n\n        and: \"verify that the DO_WHILE TaskModel is correct\"\n        doWhileTaskModel.iteration == 2\n        doWhileTaskModel.outputData['iteration'] == 2\n        doWhileTaskModel.outputData['1'] == iteration1OutputData\n        doWhileTaskModel.status == TaskModel.Status.IN_PROGRESS\n\n        and: \"verify whether the first task in the next iteration is scheduled\"\n        1 * workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflowModel)\n    }\n\n    def \"next iteration - a task failed in the previous iteration\"() {\n        given: \"WorkflowModel consists of one iteration of tasks one of which is FAILED\"\n        taskModel1 = createTaskModel(task1)\n\n        taskModel2 = createTaskModel(task2, TaskModel.Status.FAILED)\n        taskModel2.reasonForIncompletion = 'no specific reason, i am tired of success'\n\n        WorkflowTask doWhileWorkflowTask = new WorkflowTask(taskReferenceName: 'doWhileTask', type: TASK_TYPE_DO_WHILE)\n        doWhileWorkflowTask.loopCondition = \"if (\\$.doWhileTask['iteration'] < 2) { true; } else { false; }\"\n        doWhileWorkflowTask.loopOver = [task1, task2]\n\n        doWhileTaskModel = new TaskModel(workflowTask: doWhileWorkflowTask, taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE, referenceTaskName: doWhileWorkflowTask.taskReferenceName)\n        doWhileTaskModel.iteration = 1\n        doWhileTaskModel.outputData['iteration'] = 1\n        doWhileTaskModel.status = TaskModel.Status.IN_PROGRESS\n\n        def workflowModel = new WorkflowModel(workflowDefinition: new WorkflowDef(name: 'test_workflow'))\n        // setup the WorkflowModel\n        workflowModel.tasks = [doWhileTaskModel, taskModel1, taskModel2]\n\n        // this is the expected format of iteration 1's output data\n        def iteration1OutputData = [:]\n        iteration1OutputData[task1.taskReferenceName] = taskModel1.outputData\n        iteration1OutputData[task2.taskReferenceName] = taskModel2.outputData\n\n        when:\n        def retVal = doWhile.execute(workflowModel, doWhileTaskModel, workflowExecutor)\n\n        then: \"verify that return value is true, status is updated\"\n        retVal\n\n        and: \"verify the status and reasonForIncompletion fields\"\n        doWhileTaskModel.iteration == 1\n        doWhileTaskModel.outputData['iteration'] == 1\n        doWhileTaskModel.outputData['1'] == iteration1OutputData\n        doWhileTaskModel.status == TaskModel.Status.FAILED\n        doWhileTaskModel.reasonForIncompletion && doWhileTaskModel.reasonForIncompletion.contains(taskModel2.reasonForIncompletion)\n\n        and: \"verify that next iteration is NOT scheduled\"\n        0 * workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflowModel)\n    }\n\n    def \"next iteration - a task is in progress in the previous iteration\"() {\n        given: \"WorkflowModel consists of one iteration of tasks inside DO_WHILE already completed\"\n        taskModel1 = createTaskModel(task1)\n        taskModel2 = createTaskModel(task2, TaskModel.Status.IN_PROGRESS)\n        taskModel2.outputData = [:] // no output data, task is in progress\n\n        WorkflowTask doWhileWorkflowTask = new WorkflowTask(taskReferenceName: 'doWhileTask', type: TASK_TYPE_DO_WHILE)\n        doWhileWorkflowTask.loopCondition = \"if (\\$.doWhileTask['iteration'] < 2) { true; } else { false; }\"\n        doWhileWorkflowTask.loopOver = [task1, task2]\n\n        doWhileTaskModel = new TaskModel(workflowTask: doWhileWorkflowTask, taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE, referenceTaskName: doWhileWorkflowTask.taskReferenceName)\n        doWhileTaskModel.iteration = 1\n        doWhileTaskModel.outputData['iteration'] = 1\n        doWhileTaskModel.status = TaskModel.Status.IN_PROGRESS\n\n        def workflowModel = new WorkflowModel(workflowDefinition: new WorkflowDef(name: 'test_workflow'))\n        // setup the WorkflowModel\n        workflowModel.tasks = [doWhileTaskModel, taskModel1, taskModel2]\n\n        // this is the expected format of iteration 1's output data\n        def iteration1OutputData = [:]\n        iteration1OutputData[task1.taskReferenceName] = taskModel1.outputData\n        iteration1OutputData[task2.taskReferenceName] = [:]\n\n        when:\n        def retVal = doWhile.execute(workflowModel, doWhileTaskModel, workflowExecutor)\n\n        then: \"verify that return value is false, since the DO_WHILE task model is not updated\"\n        !retVal\n\n        and: \"verify that DO_WHILE task model is not modified\"\n        doWhileTaskModel.iteration == 1\n        doWhileTaskModel.outputData['iteration'] == 1\n        doWhileTaskModel.outputData['1'] == iteration1OutputData\n        doWhileTaskModel.status == TaskModel.Status.IN_PROGRESS\n\n        and: \"verify that next iteration is NOT scheduled\"\n        0 * workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflowModel)\n    }\n\n    def \"final step - all iterations are complete and all tasks in them are successful\"() {\n        given: \"WorkflowModel consists of one iteration of tasks inside DO_WHILE already completed\"\n        taskModel1 = createTaskModel(task1)\n        taskModel2 = createTaskModel(task2)\n\n        WorkflowTask doWhileWorkflowTask = new WorkflowTask(taskReferenceName: 'doWhileTask', type: TASK_TYPE_DO_WHILE)\n        doWhileWorkflowTask.loopCondition = \"if (\\$.doWhileTask['iteration'] < 1) { true; } else { false; }\"\n        doWhileWorkflowTask.loopOver = [task1, task2]\n\n        doWhileTaskModel = new TaskModel(workflowTask: doWhileWorkflowTask, taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE, referenceTaskName: doWhileWorkflowTask.taskReferenceName)\n        doWhileTaskModel.iteration = 1\n        doWhileTaskModel.outputData['iteration'] = 1\n        doWhileTaskModel.status = TaskModel.Status.IN_PROGRESS\n\n        def workflowModel = new WorkflowModel(workflowDefinition: new WorkflowDef(name: 'test_workflow'))\n        // setup the WorkflowModel\n        workflowModel.tasks = [doWhileTaskModel, taskModel1, taskModel2]\n\n        // this is the expected format of iteration 1's output data\n        def iteration1OutputData = [:]\n        iteration1OutputData[task1.taskReferenceName] = taskModel1.outputData\n        iteration1OutputData[task2.taskReferenceName] = taskModel2.outputData\n\n        when:\n        def retVal = doWhile.execute(workflowModel, doWhileTaskModel, workflowExecutor)\n\n        then: \"verify that the return value is true, DO_WHILE TaskModel is updated\"\n        retVal\n\n        and: \"verify the status and other fields are set correctly\"\n        doWhileTaskModel.iteration == 1\n        doWhileTaskModel.outputData['iteration'] == 1\n        doWhileTaskModel.outputData['1'] == iteration1OutputData\n        doWhileTaskModel.status == TaskModel.Status.COMPLETED\n\n        and: \"verify that next iteration is not scheduled\"\n        0 * workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflowModel)\n    }\n\n    def \"next iteration - one iteration of all tasks inside DO_WHILE are complete, but the condition is incorrect\"() {\n        given: \"WorkflowModel consists of one iteration of tasks inside DO_WHILE already completed\"\n        taskModel1 = createTaskModel(task1)\n        taskModel2 = createTaskModel(task2)\n\n        WorkflowTask doWhileWorkflowTask = new WorkflowTask(taskReferenceName: 'doWhileTask', type: TASK_TYPE_DO_WHILE)\n        // condition will produce a ScriptException\n        doWhileWorkflowTask.loopCondition = \"if (dollar_sign_goes_here.doWhileTask['iteration'] < 2) { true; } else { false; }\"\n        doWhileWorkflowTask.loopOver = [task1, task2]\n\n        doWhileTaskModel = new TaskModel(workflowTask: doWhileWorkflowTask, taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE, referenceTaskName: doWhileWorkflowTask.taskReferenceName)\n        doWhileTaskModel.iteration = 1\n        doWhileTaskModel.outputData['iteration'] = 1\n        doWhileTaskModel.status = TaskModel.Status.IN_PROGRESS\n\n        def workflowModel = new WorkflowModel(workflowDefinition: new WorkflowDef(name: 'test_workflow'))\n        // setup the WorkflowModel\n        workflowModel.tasks = [doWhileTaskModel, taskModel1, taskModel2]\n\n        // this is the expected format of iteration 1's output data\n        def iteration1OutputData = [:]\n        iteration1OutputData[task1.taskReferenceName] = taskModel1.outputData\n        iteration1OutputData[task2.taskReferenceName] = taskModel2.outputData\n\n        when:\n        def retVal = doWhile.execute(workflowModel, doWhileTaskModel, workflowExecutor)\n\n        then: \"verify that the return value is true since DO_WHILE TaskModel is updated\"\n        retVal\n\n        and: \"verify the status of DO_WHILE TaskModel\"\n        doWhileTaskModel.iteration == 1\n        doWhileTaskModel.outputData['iteration'] == 1\n        doWhileTaskModel.outputData['1'] == iteration1OutputData\n        doWhileTaskModel.status == TaskModel.Status.FAILED_WITH_TERMINAL_ERROR\n        doWhileTaskModel.reasonForIncompletion != null\n\n        and: \"verify that next iteration is not scheduled\"\n        0 * workflowExecutor.scheduleNextIteration(doWhileTaskModel, workflowModel)\n    }\n\n    def \"cancel sets the status as CANCELED\"() {\n        given:\n        doWhileTaskModel = new TaskModel(taskId: UUID.randomUUID().toString(),\n                taskType: TASK_TYPE_DO_WHILE)\n        doWhileTaskModel.iteration = 1\n        doWhileTaskModel.outputData['iteration'] = 1\n        doWhileTaskModel.status = TaskModel.Status.IN_PROGRESS\n\n        when: \"cancel is called with null for WorkflowModel and WorkflowExecutor\"\n        // null is used to note that those arguments are not intended to be used by this method\n        doWhile.cancel(null, doWhileTaskModel, null)\n\n        then:\n        doWhileTaskModel.status == TaskModel.Status.CANCELED\n    }\n\n    private static createTaskModel(WorkflowTask workflowTask, TaskModel.Status status = TaskModel.Status.COMPLETED, int iteration = 1) {\n        TaskModel taskModel1 = new TaskModel(workflowTask: workflowTask, taskType: TASK_TYPE_HTTP)\n\n        taskModel1.status = status\n        taskModel1.outputData = ['k1': 'v1']\n        taskModel1.iteration = iteration\n        taskModel1.referenceTaskName = TaskUtils.appendIteration(workflowTask.taskReferenceName, iteration)\n\n        return taskModel1\n    }\n}\n"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/core/execution/tasks/EventSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.core.events.EventQueues\nimport com.netflix.conductor.core.events.queue.Message\nimport com.netflix.conductor.core.events.queue.ObservableQueue\nimport com.netflix.conductor.core.exception.NonTransientException\nimport com.netflix.conductor.core.exception.TransientException\nimport com.netflix.conductor.core.utils.ParametersUtils\nimport com.netflix.conductor.model.TaskModel\nimport com.netflix.conductor.model.WorkflowModel\n\nimport com.fasterxml.jackson.core.JsonParseException\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nclass EventSpec extends Specification {\n\n    EventQueues eventQueues\n    ParametersUtils parametersUtils\n    ObjectMapper objectMapper\n    ObservableQueue observableQueue\n\n    String payloadJSON = \"payloadJSON\"\n    WorkflowDef testWorkflowDefinition\n    WorkflowModel workflow\n\n    @Subject\n    Event event\n\n    def setup() {\n        parametersUtils = Mock(ParametersUtils.class)\n        eventQueues = Mock(EventQueues.class)\n        observableQueue = Mock(ObservableQueue.class)\n        objectMapper = Mock(ObjectMapper.class) {\n            writeValueAsString(_) >> payloadJSON\n        }\n\n        testWorkflowDefinition = new WorkflowDef(name: \"testWorkflow\", version: 2)\n        workflow = new WorkflowModel(workflowDefinition: testWorkflowDefinition, workflowId: 'workflowId', correlationId: 'corrId')\n\n        event = new Event(eventQueues, parametersUtils, objectMapper)\n    }\n\n    def \"verify that event task is NOT async\"() {\n        when:\n        def async = event.isAsync()\n\n        then:\n        !async\n    }\n\n    def \"event cancel calls ack on the queue\"() {\n        given:\n        // status is intentionally left as null\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', inputData: ['sink': 'conductor'])\n\n        String queueName = \"conductor:${workflow.workflowName}:${task.referenceTaskName}\"\n\n        when:\n        event.cancel(workflow, task, null)\n\n        then:\n        task.status == null // task status is NOT updated by the cancel method\n\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': 'conductor']\n        1 * eventQueues.getQueue(queueName) >> observableQueue\n        // Event.cancel sends a list with one Message object to ack\n        1 * observableQueue.ack({it.size() == 1})\n    }\n\n    def \"event task with 'conductor' as sink\"() {\n        given:\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', inputData: ['sink': 'conductor'])\n\n        String queueName = \"conductor:${workflow.workflowName}:${task.referenceTaskName}\"\n        Message expectedMessage\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        verifyOutputData(task, queueName)\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': 'conductor']\n\n        when:\n        event.execute(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.COMPLETED\n        verifyOutputData(task, queueName)\n        1 * eventQueues.getQueue(queueName) >> observableQueue\n        // capture the Message object sent to the publish method. Event.start sends a list with one Message object\n        1 * observableQueue.publish({ it.size() == 1 }) >> { it -> expectedMessage = it[0][0] as Message }\n        verifyMessage(expectedMessage, task)\n    }\n\n    def \"event task with 'conductor:<eventname>' as sink\"() {\n        given:\n        String eventName = 'testEvent'\n        String sinkValue = \"conductor:$eventName\".toString()\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', inputData: ['sink': sinkValue])\n\n        String queueName = \"conductor:${workflow.workflowName}:$eventName\"\n        Message expectedMessage\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        verifyOutputData(task, queueName)\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n\n        when:\n        event.execute(workflow, task,  null)\n\n        then:\n        task.status == TaskModel.Status.COMPLETED\n        verifyOutputData(task, queueName)\n        1 * eventQueues.getQueue(queueName) >> observableQueue\n        // capture the Message object sent to the publish method. Event.start sends a list with one Message object\n        1 * observableQueue.publish({ it.size() == 1 }) >> { it -> expectedMessage = it[0][0] as Message }\n        verifyMessage(expectedMessage, task)\n    }\n\n    def \"event task with 'sqs' as sink\"() {\n        given:\n        String eventName = 'testEvent'\n        String sinkValue = \"sqs:$eventName\".toString()\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', inputData: ['sink': sinkValue])\n\n        // for non conductor queues, queueName is the same as the value of the 'sink' field in the inputData\n        String queueName = sinkValue\n        Message expectedMessage\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        verifyOutputData(task, queueName)\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n\n        when:\n        event.execute(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.COMPLETED\n        verifyOutputData(task, queueName)\n        1 * eventQueues.getQueue(queueName) >> observableQueue\n        // capture the Message object sent to the publish method. Event.start sends a list with one Message object\n        1 * observableQueue.publish({ it.size() == 1 }) >> { it -> expectedMessage = it[0][0] as Message }\n        verifyMessage(expectedMessage, task)\n    }\n\n    def \"event task with 'conductor' as sink and async complete\"() {\n        given:\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', inputData: ['sink': 'conductor', 'asyncComplete': true])\n\n        String queueName = \"conductor:${workflow.workflowName}:${task.referenceTaskName}\"\n        Message expectedMessage\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        verifyOutputData(task, queueName)\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': 'conductor']\n\n        when:\n        boolean isTaskUpdateRequired = event.execute(workflow, task, null)\n\n        then:\n        !isTaskUpdateRequired\n        task.status == TaskModel.Status.IN_PROGRESS\n        verifyOutputData(task, queueName)\n        1 * eventQueues.getQueue(queueName) >> observableQueue\n        // capture the Message object sent to the publish method. Event.start sends a list with one Message object\n        1 * observableQueue.publish({ it.size() == 1 }) >> { args -> expectedMessage = args[0][0] as Message }\n        verifyMessage(expectedMessage, task)\n    }\n\n    def \"event task with incorrect 'conductor' sink value\"() {\n        given:\n        String sinkValue = 'conductorinvalidsink'\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', inputData: ['sink': sinkValue])\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.FAILED\n        task.reasonForIncompletion != null\n        task.reasonForIncompletion.contains('Invalid / Unsupported sink specified:')\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n    }\n\n    def \"event task with sink value that does not resolve to a queue\"() {\n        given:\n        String sinkValue = 'rabbitmq:abc_123'\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', inputData: ['sink': sinkValue])\n\n        // for non conductor queues, queueName is the same as the value of the 'sink' field in the inputData\n        String queueName = sinkValue\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n\n        when:\n        event.execute(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.FAILED\n        task.reasonForIncompletion != null\n        1 * eventQueues.getQueue(queueName) >> {throw new IllegalArgumentException() }\n    }\n\n    def \"publishing to a queue throws a TransientException\"() {\n        given:\n        String sinkValue = 'conductor'\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', status: TaskModel.Status.SCHEDULED, inputData: ['sink': sinkValue])\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n\n        when:\n        event.execute(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.FAILED\n        1 * eventQueues.getQueue(_) >> observableQueue\n        // capture the Message object sent to the publish method. Event.start sends a list with one Message object\n        1 * observableQueue.publish(_) >> { throw new TransientException(\"transient error\") }\n    }\n\n    def \"publishing to a queue throws a NonTransientException\"() {\n        given:\n        String sinkValue = 'conductor'\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', status: TaskModel.Status.SCHEDULED, inputData: ['sink': sinkValue])\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n\n        when:\n        event.execute(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.FAILED\n        task.reasonForIncompletion != null\n        1 * eventQueues.getQueue(_) >> observableQueue\n        // capture the Message object sent to the publish method. Event.start sends a list with one Message object\n        1 * observableQueue.publish(_) >> { throw new NonTransientException(\"fatal error\") }\n    }\n\n    def \"event task fails to convert the payload to json\"() {\n        given:\n        String sinkValue = 'conductor'\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', status: TaskModel.Status.SCHEDULED, inputData: ['sink': sinkValue])\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n\n        when:\n        event.execute(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.FAILED\n        task.reasonForIncompletion != null\n        1 * objectMapper.writeValueAsString(_ as Map) >> { throw new JsonParseException(null, \"invalid json\") }\n    }\n\n    def \"event task fails with an unexpected exception\"() {\n        given:\n        String sinkValue = 'conductor'\n\n        TaskModel task = new TaskModel(referenceTaskName: 'task0', taskId: 'task_id_0', status: TaskModel.Status.SCHEDULED, inputData: ['sink': sinkValue])\n\n        when:\n        event.start(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.IN_PROGRESS\n        1 * parametersUtils.getTaskInputV2(_, workflow, task.taskId, _) >> ['sink': sinkValue]\n\n        when:\n        event.execute(workflow, task, null)\n\n        then:\n        task.status == TaskModel.Status.FAILED\n        task.reasonForIncompletion != null\n        1 * eventQueues.getQueue(_) >> { throw new NullPointerException(\"some object is null\") }\n    }\n\n    private void verifyOutputData(TaskModel task, String queueName) {\n        assert task.outputData != null\n        assert task.outputData['event_produced'] == queueName\n        assert task.outputData['workflowInstanceId'] == workflow.workflowId\n        assert task.outputData['workflowVersion'] == workflow.workflowVersion\n        assert task.outputData['workflowType'] == workflow.workflowName\n        assert task.outputData['correlationId'] == workflow.correlationId\n    }\n\n    private void verifyMessage(Message expectedMessage, TaskModel task) {\n        assert expectedMessage != null\n        assert expectedMessage.id == task.taskId\n        assert expectedMessage.receipt == task.taskId\n        assert expectedMessage.payload == payloadJSON\n    }\n}\n"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/core/execution/tasks/IsolatedTaskQueueProducerSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks\n\nimport java.time.Duration\n\nimport org.junit.Test\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.service.MetadataService\n\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nclass IsolatedTaskQueueProducerSpec extends Specification {\n\n    SystemTaskWorker systemTaskWorker\n    MetadataService metadataService\n\n    @Subject\n    IsolatedTaskQueueProducer isolatedTaskQueueProducer\n\n    def asyncSystemTask = new WorkflowSystemTask(\"asyncTask\") {\n        @Override\n        boolean isAsync() {\n            return true\n        }\n    }\n\n    def setup() {\n        systemTaskWorker = Mock(SystemTaskWorker.class)\n        metadataService = Mock(MetadataService.class)\n\n        isolatedTaskQueueProducer = new IsolatedTaskQueueProducer(metadataService, [asyncSystemTask] as Set, systemTaskWorker, false,\n                Duration.ofSeconds(10))\n    }\n\n    def \"addTaskQueuesAddsElementToQueue\"() {\n        given:\n        TaskDef taskDef = new TaskDef(isolationGroupId: \"isolated\")\n\n        when:\n        isolatedTaskQueueProducer.addTaskQueues()\n\n        then:\n        1 * systemTaskWorker.startPolling(asyncSystemTask, \"${asyncSystemTask.taskType}-${taskDef.isolationGroupId}\")\n        1 * metadataService.getTaskDefs() >> Collections.singletonList(taskDef)\n    }\n}\n"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/core/execution/tasks/StartWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks\n\nimport javax.validation.ConstraintViolation\nimport javax.validation.Validator\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider\nimport com.netflix.conductor.core.exception.NotFoundException\nimport com.netflix.conductor.core.exception.TransientException\nimport com.netflix.conductor.core.execution.WorkflowExecutor\nimport com.netflix.conductor.core.operation.StartWorkflowOperation\nimport com.netflix.conductor.model.TaskModel\nimport com.netflix.conductor.model.WorkflowModel\n\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nimport static com.netflix.conductor.core.execution.tasks.StartWorkflow.START_WORKFLOW_PARAMETER\nimport static com.netflix.conductor.model.TaskModel.Status.FAILED\nimport static com.netflix.conductor.model.TaskModel.Status.SCHEDULED\n\n/**\n * Unit test for StartWorkflow. Success and Javax validation cases are covered by the StartWorkflowSpec in test-harness module.\n */\nclass StartWorkflowSpec extends Specification {\n\n    @Subject\n    StartWorkflow startWorkflow\n\n    WorkflowExecutor workflowExecutor\n    Validator validator\n    WorkflowModel workflowModel\n    TaskModel taskModel\n    StartWorkflowOperation startWorkflowOperation\n\n    def setup() {\n        workflowExecutor = Mock(WorkflowExecutor.class)\n        validator = Mock(Validator.class) {\n            validate(_) >> new HashSet<ConstraintViolation<Object>>()\n        }\n        startWorkflowOperation = Mock(StartWorkflowOperation.class)\n\n        def inputData = [:]\n        inputData[START_WORKFLOW_PARAMETER] = ['name': 'some_workflow']\n        taskModel = new TaskModel(status: SCHEDULED, inputData: inputData)\n        workflowModel = new WorkflowModel()\n\n        startWorkflow = new StartWorkflow(new ObjectMapperProvider().getObjectMapper(), validator, startWorkflowOperation)\n    }\n\n    def \"StartWorkflow task is asynchronous\"() {\n        expect:\n        startWorkflow.isAsync()\n    }\n\n    def \"startWorkflow parameter is missing\"() {\n        given: \"a task with no start_workflow in input\"\n        taskModel.inputData = [:]\n\n        when:\n        startWorkflow.start(workflowModel, taskModel, workflowExecutor)\n\n        then:\n        taskModel.status == FAILED\n        taskModel.reasonForIncompletion != null\n    }\n\n    def \"ObjectMapper throws an IllegalArgumentException\"() {\n        given: \"a task with no start_workflow in input\"\n        taskModel.inputData[START_WORKFLOW_PARAMETER] = \"I can't be converted to StartWorkflowRequest\"\n\n        when:\n        startWorkflow.start(workflowModel, taskModel, workflowExecutor)\n\n        then:\n        taskModel.status == FAILED\n        taskModel.reasonForIncompletion != null\n    }\n\n    def \"WorkflowExecutor throws a retryable exception\"() {\n        when:\n        startWorkflow.start(workflowModel, taskModel, workflowExecutor)\n\n        then:\n        taskModel.status == SCHEDULED\n        1 * startWorkflowOperation.execute(*_) >> { throw new TransientException(\"\") }\n    }\n\n    def \"WorkflowExecutor throws a NotFoundException\"() {\n        when:\n        startWorkflow.start(workflowModel, taskModel, workflowExecutor)\n\n        then:\n        taskModel.status == FAILED\n        taskModel.reasonForIncompletion != null\n        1 * startWorkflowOperation.execute(*_) >> { throw new NotFoundException(\"\") }\n    }\n\n    def \"WorkflowExecutor throws a RuntimeException\"() {\n        when:\n        startWorkflow.start(workflowModel, taskModel, workflowExecutor)\n\n        then:\n        taskModel.status == FAILED\n        taskModel.reasonForIncompletion != null\n        1 * startWorkflowOperation.execute(*_) >> { throw new RuntimeException(\"I am an unexpected exception\") }\n    }\n}\n"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/core/operation/StartWorkflowOperationSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.operation\n\nimport org.springframework.context.ApplicationEventPublisher\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade\nimport com.netflix.conductor.core.execution.StartWorkflowInput\nimport com.netflix.conductor.core.metadata.MetadataMapperService\nimport com.netflix.conductor.core.utils.IDGenerator\nimport com.netflix.conductor.core.utils.ParametersUtils\nimport com.netflix.conductor.service.ExecutionLockService\n\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nclass StartWorkflowOperationSpec extends Specification {\n\n    @Subject\n    StartWorkflowOperation startWorkflowOperation\n\n    MetadataMapperService metadataMapperService\n    IDGenerator idGenerator\n    ParametersUtils parametersUtils\n    ExecutionDAOFacade executionDAOFacade\n    ExecutionLockService executionLockService\n    ApplicationEventPublisher eventPublisher\n\n    def setup() {\n        metadataMapperService = Mock(MetadataMapperService.class)\n        idGenerator = Mock(IDGenerator.class)\n        parametersUtils = Mock(ParametersUtils.class)\n        executionDAOFacade = Mock(ExecutionDAOFacade.class)\n        executionLockService = Mock(ExecutionLockService.class)\n        eventPublisher = Mock(ApplicationEventPublisher.class)\n\n        startWorkflowOperation = new StartWorkflowOperation(metadataMapperService, idGenerator, parametersUtils, executionDAOFacade, executionLockService, eventPublisher)\n    }\n\n    def \"simple start workflow\"() {\n        given:\n        def workflowDef = new WorkflowDef(name: 'test')\n        def generatedWorkflowId = UUID.randomUUID().toString()\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput(workflowDefinition: workflowDef, workflowInput: [:])\n\n        when:\n        def workflowId = startWorkflowOperation.execute(startWorkflowInput)\n\n        then:\n        workflowId == generatedWorkflowId\n        1 * idGenerator.generate() >> generatedWorkflowId\n        1 * metadataMapperService.populateTaskDefinitions(workflowDef) >> workflowDef\n        1 * executionLockService.acquireLock(generatedWorkflowId) >> true\n        1 * executionDAOFacade.createWorkflow(_)\n        1 * eventPublisher.publishEvent(_)\n    }\n}\n"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/model/TaskModelSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.model\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider\n\nimport com.fasterxml.jackson.databind.JsonNode\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nclass TaskModelSpec extends Specification {\n\n    @Subject\n    TaskModel taskModel\n\n    private static final ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper()\n\n    def setup() {\n        taskModel = new TaskModel()\n    }\n\n    def \"check inputData serialization\"() {\n        given:\n        String path = \"task/input/${UUID.randomUUID()}.json\"\n        taskModel.addInput(['key1': 'value1', 'key2': 'value2'])\n        taskModel.externalizeInput(path)\n\n        when:\n        def json = objectMapper.writeValueAsString(taskModel)\n        println(json)\n\n        then:\n        json != null\n        JsonNode node = objectMapper.readTree(json)\n        node.path(\"inputData\").isEmpty()\n        node.path(\"externalInputPayloadStoragePath\").isTextual()\n    }\n\n    def \"check outputData serialization\"() {\n        given:\n        String path = \"task/output/${UUID.randomUUID()}.json\"\n        taskModel.addOutput(['key1': 'value1', 'key2': 'value2'])\n        taskModel.externalizeOutput(path)\n\n        when:\n        def json = objectMapper.writeValueAsString(taskModel)\n        println(json)\n\n        then:\n        json != null\n        JsonNode node = objectMapper.readTree(json)\n        node.path(\"outputData\").isEmpty()\n        node.path(\"externalOutputPayloadStoragePath\").isTextual()\n    }\n}\n"
  },
  {
    "path": "core/src/test/groovy/com/netflix/conductor/model/WorkflowModelSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.model\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\n\nimport com.fasterxml.jackson.databind.JsonNode\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport spock.lang.Specification\nimport spock.lang.Subject\n\nclass WorkflowModelSpec extends Specification {\n\n    @Subject\n    WorkflowModel workflowModel\n\n    private static final ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper()\n\n    def setup() {\n        def workflowDef = new WorkflowDef(name: \"test def name\", version: 1)\n        workflowModel = new WorkflowModel(workflowDefinition: workflowDef)\n    }\n\n    def \"check input serialization\"() {\n        given:\n        String path = \"task/input/${UUID.randomUUID()}.json\"\n        workflowModel.input = ['key1': 'value1', 'key2': 'value2']\n        workflowModel.externalizeInput(path)\n\n        when:\n        def json = objectMapper.writeValueAsString(workflowModel)\n        println(json)\n\n        then:\n        json != null\n        JsonNode node = objectMapper.readTree(json)\n        node.path(\"input\").isEmpty()\n        node.path(\"externalInputPayloadStoragePath\").isTextual()\n    }\n\n    def \"check output serialization\"() {\n        given:\n        String path = \"task/output/${UUID.randomUUID()}.json\"\n        workflowModel.output = ['key1': 'value1', 'key2': 'value2']\n        workflowModel.externalizeOutput(path)\n\n        when:\n        def json = objectMapper.writeValueAsString(workflowModel)\n        println(json)\n\n        then:\n        json != null\n        JsonNode node = objectMapper.readTree(json)\n        node.path(\"output\").isEmpty()\n        node.path(\"externalOutputPayloadStoragePath\").isTextual()\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/TestUtils.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport javax.validation.ConstraintViolation;\n\npublic class TestUtils {\n\n    public static Set<String> getConstraintViolationMessages(\n            Set<ConstraintViolation<?>> constraintViolations) {\n        Set<String> messages = new HashSet<>(constraintViolations.size());\n        messages.addAll(\n                constraintViolations.stream()\n                        .map(ConstraintViolation::getMessage)\n                        .collect(Collectors.toList()));\n        return messages;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/dal/ExecutionDAOFacadeTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.dal;\n\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.UUID;\n\nimport org.apache.commons.io.IOUtils;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.TestDeciderService;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.dao.*;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class ExecutionDAOFacadeTest {\n\n    private ExecutionDAO executionDAO;\n    private IndexDAO indexDAO;\n    private ExecutionDAOFacade executionDAOFacade;\n    private ExternalPayloadStorageUtils externalPayloadStorageUtils;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void setUp() {\n        executionDAO = mock(ExecutionDAO.class);\n        QueueDAO queueDAO = mock(QueueDAO.class);\n        indexDAO = mock(IndexDAO.class);\n        externalPayloadStorageUtils = mock(ExternalPayloadStorageUtils.class);\n        RateLimitingDAO rateLimitingDao = mock(RateLimitingDAO.class);\n        ConcurrentExecutionLimitDAO concurrentExecutionLimitDAO =\n                mock(ConcurrentExecutionLimitDAO.class);\n        PollDataDAO pollDataDAO = mock(PollDataDAO.class);\n        ConductorProperties properties = mock(ConductorProperties.class);\n        when(properties.isEventExecutionIndexingEnabled()).thenReturn(true);\n        when(properties.isAsyncIndexingEnabled()).thenReturn(true);\n        executionDAOFacade =\n                new ExecutionDAOFacade(\n                        executionDAO,\n                        queueDAO,\n                        indexDAO,\n                        rateLimitingDao,\n                        concurrentExecutionLimitDAO,\n                        pollDataDAO,\n                        objectMapper,\n                        properties,\n                        externalPayloadStorageUtils);\n    }\n\n    @Test\n    public void testGetWorkflow() throws Exception {\n        when(executionDAO.getWorkflow(any(), anyBoolean())).thenReturn(new WorkflowModel());\n        Workflow workflow = executionDAOFacade.getWorkflow(\"workflowId\", true);\n        assertNotNull(workflow);\n        verify(indexDAO, never()).get(any(), any());\n    }\n\n    @Test\n    public void testGetWorkflowModel() throws Exception {\n        when(executionDAO.getWorkflow(any(), anyBoolean())).thenReturn(new WorkflowModel());\n        WorkflowModel workflowModel = executionDAOFacade.getWorkflowModel(\"workflowId\", true);\n        assertNotNull(workflowModel);\n        verify(indexDAO, never()).get(any(), any());\n\n        when(executionDAO.getWorkflow(any(), anyBoolean())).thenReturn(null);\n        InputStream stream = ExecutionDAOFacadeTest.class.getResourceAsStream(\"/test.json\");\n        byte[] bytes = IOUtils.toByteArray(stream);\n        String jsonString = new String(bytes);\n        when(indexDAO.get(any(), any())).thenReturn(jsonString);\n        workflowModel = executionDAOFacade.getWorkflowModel(\"wokflowId\", true);\n        assertNotNull(workflowModel);\n        verify(indexDAO, times(1)).get(any(), any());\n    }\n\n    @Test\n    public void testGetWorkflowsByCorrelationId() {\n        when(executionDAO.canSearchAcrossWorkflows()).thenReturn(true);\n        when(executionDAO.getWorkflowsByCorrelationId(any(), any(), anyBoolean()))\n                .thenReturn(Collections.singletonList(new WorkflowModel()));\n        List<Workflow> workflows =\n                executionDAOFacade.getWorkflowsByCorrelationId(\n                        \"workflowName\", \"correlationId\", true);\n\n        assertNotNull(workflows);\n        assertEquals(1, workflows.size());\n        verify(indexDAO, never())\n                .searchWorkflows(anyString(), anyString(), anyInt(), anyInt(), any());\n\n        when(executionDAO.canSearchAcrossWorkflows()).thenReturn(false);\n        List<String> workflowIds = new ArrayList<>();\n        workflowIds.add(\"workflowId\");\n        SearchResult<String> searchResult = new SearchResult<>();\n        searchResult.setResults(workflowIds);\n        when(indexDAO.searchWorkflows(anyString(), anyString(), anyInt(), anyInt(), any()))\n                .thenReturn(searchResult);\n        when(executionDAO.getWorkflow(\"workflowId\", true)).thenReturn(new WorkflowModel());\n        workflows =\n                executionDAOFacade.getWorkflowsByCorrelationId(\n                        \"workflowName\", \"correlationId\", true);\n        assertNotNull(workflows);\n        assertEquals(1, workflows.size());\n    }\n\n    @Test\n    public void testRemoveWorkflow() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"workflowId\");\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n\n        TaskModel task = new TaskModel();\n        task.setTaskId(\"taskId\");\n        workflow.setTasks(Collections.singletonList(task));\n\n        when(executionDAO.getWorkflow(anyString(), anyBoolean())).thenReturn(workflow);\n        executionDAOFacade.removeWorkflow(\"workflowId\", false);\n        verify(executionDAO, times(1)).removeWorkflow(anyString());\n        verify(executionDAO, never()).removeTask(anyString());\n        verify(indexDAO, never()).updateWorkflow(anyString(), any(), any());\n        verify(indexDAO, never()).updateTask(anyString(), anyString(), any(), any());\n        verify(indexDAO, times(1)).asyncRemoveWorkflow(anyString());\n        verify(indexDAO, times(1)).asyncRemoveTask(anyString(), anyString());\n    }\n\n    @Test\n    public void testArchiveWorkflow() throws Exception {\n        InputStream stream = TestDeciderService.class.getResourceAsStream(\"/completed.json\");\n        WorkflowModel workflow = objectMapper.readValue(stream, WorkflowModel.class);\n\n        when(executionDAO.getWorkflow(anyString(), anyBoolean())).thenReturn(workflow);\n        executionDAOFacade.removeWorkflow(\"workflowId\", true);\n        verify(executionDAO, times(1)).removeWorkflow(anyString());\n        verify(executionDAO, never()).removeTask(anyString());\n        verify(indexDAO, times(1)).updateWorkflow(anyString(), any(), any());\n        verify(indexDAO, times(15)).updateTask(anyString(), anyString(), any(), any());\n        verify(indexDAO, never()).removeWorkflow(anyString());\n        verify(indexDAO, never()).removeTask(anyString(), anyString());\n    }\n\n    @Test\n    public void testAddEventExecution() {\n        when(executionDAO.addEventExecution(any())).thenReturn(false);\n        boolean added = executionDAOFacade.addEventExecution(new EventExecution());\n        assertFalse(added);\n        verify(indexDAO, never()).addEventExecution(any());\n\n        when(executionDAO.addEventExecution(any())).thenReturn(true);\n        added = executionDAOFacade.addEventExecution(new EventExecution());\n        assertTrue(added);\n        verify(indexDAO, times(1)).asyncAddEventExecution(any());\n    }\n\n    @Test(expected = TerminateWorkflowException.class)\n    public void testUpdateTaskThrowsTerminateWorkflowException() {\n        TaskModel task = new TaskModel();\n        task.setScheduledTime(1L);\n        task.setSeq(1);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setTaskDefName(\"task1\");\n\n        doThrow(new TerminateWorkflowException(\"failed\"))\n                .when(externalPayloadStorageUtils)\n                .verifyAndUpload(task, ExternalPayloadStorage.PayloadType.TASK_OUTPUT);\n\n        executionDAOFacade.updateTask(task);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/events/MockObservableQueue.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.stream.Collectors;\n\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\n\nimport rx.Observable;\n\npublic class MockObservableQueue implements ObservableQueue {\n\n    private final String uri;\n    private final String name;\n    private final String type;\n    private final Set<Message> messages = new TreeSet<>(Comparator.comparing(Message::getId));\n\n    public MockObservableQueue(String uri, String name, String type) {\n        this.uri = uri;\n        this.name = name;\n        this.type = type;\n    }\n\n    @Override\n    public Observable<Message> observe() {\n        return Observable.from(messages);\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    @Override\n    public String getName() {\n        return name;\n    }\n\n    @Override\n    public String getURI() {\n        return uri;\n    }\n\n    @Override\n    public List<String> ack(List<Message> msgs) {\n        messages.removeAll(msgs);\n        return msgs.stream().map(Message::getId).collect(Collectors.toList());\n    }\n\n    @Override\n    public void publish(List<Message> messages) {\n        this.messages.addAll(messages);\n    }\n\n    @Override\n    public void setUnackTimeout(Message message, long unackTimeout) {}\n\n    @Override\n    public long size() {\n        return messages.size();\n    }\n\n    @Override\n    public String toString() {\n        return \"MockObservableQueue [uri=\" + uri + \", name=\" + name + \", type=\" + type + \"]\";\n    }\n\n    @Override\n    public void start() {}\n\n    @Override\n    public void stop() {}\n\n    @Override\n    public boolean isRunning() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/events/MockQueueProvider.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport org.springframework.lang.NonNull;\n\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\n\npublic class MockQueueProvider implements EventQueueProvider {\n\n    private final String type;\n\n    public MockQueueProvider(String type) {\n        this.type = type;\n    }\n\n    @Override\n    public String getQueueType() {\n        return \"mock\";\n    }\n\n    @Override\n    @NonNull\n    public ObservableQueue getQueue(String queueURI) {\n        return new MockObservableQueue(queueURI, queueURI, type);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/events/TestDefaultEventProcessor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.stubbing.Answer;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.retry.support.RetryTemplate;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action.Type;\nimport com.netflix.conductor.common.metadata.events.EventHandler.StartWorkflow;\nimport com.netflix.conductor.common.metadata.events.EventHandler.TaskDetails;\nimport com.netflix.conductor.core.config.ConductorCoreConfiguration;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.core.execution.evaluators.JavascriptEvaluator;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.JsonUtils;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.conductor.service.ExecutionService;\nimport com.netflix.conductor.service.MetadataService;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@ContextConfiguration(\n        classes = {\n            TestObjectMapperConfiguration.class,\n            TestDefaultEventProcessor.TestConfiguration.class,\n            ConductorCoreConfiguration.class\n        })\n@RunWith(SpringRunner.class)\npublic class TestDefaultEventProcessor {\n\n    private String event;\n    private ObservableQueue queue;\n    private MetadataService metadataService;\n    private ExecutionService executionService;\n    private WorkflowExecutor workflowExecutor;\n    private StartWorkflowOperation startWorkflowOperation;\n    private ExternalPayloadStorageUtils externalPayloadStorageUtils;\n    private SimpleActionProcessor actionProcessor;\n    private ParametersUtils parametersUtils;\n    private JsonUtils jsonUtils;\n    private ConductorProperties properties;\n    private Message message;\n\n    @Autowired private Map<String, Evaluator> evaluators;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Autowired\n    private @Qualifier(\"onTransientErrorRetryTemplate\") RetryTemplate retryTemplate;\n\n    @Configuration\n    @ComponentScan(basePackageClasses = {Evaluator.class}) // load all Evaluator beans\n    public static class TestConfiguration {}\n\n    @Before\n    public void setup() {\n        event = \"sqs:arn:account090:sqstest1\";\n        String queueURI = \"arn:account090:sqstest1\";\n\n        metadataService = mock(MetadataService.class);\n        executionService = mock(ExecutionService.class);\n        workflowExecutor = mock(WorkflowExecutor.class);\n        startWorkflowOperation = mock(StartWorkflowOperation.class);\n        externalPayloadStorageUtils = mock(ExternalPayloadStorageUtils.class);\n        actionProcessor = mock(SimpleActionProcessor.class);\n        parametersUtils = new ParametersUtils(objectMapper);\n        jsonUtils = new JsonUtils(objectMapper);\n\n        queue = mock(ObservableQueue.class);\n        message =\n                new Message(\n                        \"t0\",\n                        \"{\\\"Type\\\":\\\"Notification\\\",\\\"MessageId\\\":\\\"7e4e6415-01e9-5caf-abaa-37fd05d446ff\\\",\\\"Message\\\":\\\"{\\\\n    \\\\\\\"testKey1\\\\\\\": \\\\\\\"level1\\\\\\\",\\\\n    \\\\\\\"metadata\\\\\\\": {\\\\n      \\\\\\\"testKey2\\\\\\\": 123456 }\\\\n  }\\\",\\\"Timestamp\\\":\\\"2018-08-10T21:22:05.029Z\\\",\\\"SignatureVersion\\\":\\\"1\\\"}\",\n                        \"t0\");\n\n        when(queue.getURI()).thenReturn(queueURI);\n        when(queue.getName()).thenReturn(queueURI);\n        when(queue.getType()).thenReturn(\"sqs\");\n\n        properties = mock(ConductorProperties.class);\n        when(properties.isEventMessageIndexingEnabled()).thenReturn(true);\n        when(properties.getEventProcessorThreadCount()).thenReturn(2);\n    }\n\n    @Test\n    public void testEventProcessor() {\n        // setup event handler\n        EventHandler eventHandler = new EventHandler();\n        eventHandler.setName(UUID.randomUUID().toString());\n        eventHandler.setActive(true);\n\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(\"*\", \"dev\");\n\n        Action startWorkflowAction = new Action();\n        startWorkflowAction.setAction(Type.start_workflow);\n        startWorkflowAction.setStart_workflow(new StartWorkflow());\n        startWorkflowAction.getStart_workflow().setName(\"workflow_x\");\n        startWorkflowAction.getStart_workflow().setVersion(1);\n        startWorkflowAction.getStart_workflow().setTaskToDomain(taskToDomain);\n        eventHandler.getActions().add(startWorkflowAction);\n\n        Action completeTaskAction = new Action();\n        completeTaskAction.setAction(Type.complete_task);\n        completeTaskAction.setComplete_task(new TaskDetails());\n        completeTaskAction.getComplete_task().setTaskRefName(\"task_x\");\n        completeTaskAction.getComplete_task().setWorkflowId(UUID.randomUUID().toString());\n        completeTaskAction.getComplete_task().setOutput(new HashMap<>());\n        eventHandler.getActions().add(completeTaskAction);\n\n        eventHandler.setEvent(event);\n\n        when(metadataService.getEventHandlersForEvent(event, true))\n                .thenReturn(Collections.singletonList(eventHandler));\n        when(executionService.addEventExecution(any())).thenReturn(true);\n        when(queue.rePublishIfNoAck()).thenReturn(false);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(startWorkflowAction.getStart_workflow().getName());\n        startWorkflowInput.setVersion(startWorkflowAction.getStart_workflow().getVersion());\n        startWorkflowInput.setCorrelationId(\n                startWorkflowAction.getStart_workflow().getCorrelationId());\n        startWorkflowInput.setEvent(event);\n\n        String id = UUID.randomUUID().toString();\n        AtomicBoolean started = new AtomicBoolean(false);\n        doAnswer(\n                        (Answer<String>)\n                                invocation -> {\n                                    started.set(true);\n                                    return id;\n                                })\n                .when(startWorkflowOperation)\n                .execute(\n                        argThat(\n                                argument ->\n                                        startWorkflowAction\n                                                        .getStart_workflow()\n                                                        .getName()\n                                                        .equals(argument.getName())\n                                                && startWorkflowAction\n                                                        .getStart_workflow()\n                                                        .getVersion()\n                                                        .equals(argument.getVersion())\n                                                && event.equals(argument.getEvent())));\n\n        AtomicBoolean completed = new AtomicBoolean(false);\n        doAnswer(\n                        (Answer<String>)\n                                invocation -> {\n                                    completed.set(true);\n                                    return null;\n                                })\n                .when(workflowExecutor)\n                .updateTask(any());\n\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(completeTaskAction.getComplete_task().getTaskRefName());\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setTasks(Collections.singletonList(task));\n        when(workflowExecutor.getWorkflow(\n                        completeTaskAction.getComplete_task().getWorkflowId(), true))\n                .thenReturn(workflow);\n        doNothing().when(externalPayloadStorageUtils).verifyAndUpload(any(), any());\n\n        SimpleActionProcessor actionProcessor =\n                new SimpleActionProcessor(\n                        workflowExecutor, parametersUtils, jsonUtils, startWorkflowOperation);\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        eventProcessor.handle(queue, message);\n        assertTrue(started.get());\n        assertTrue(completed.get());\n        verify(queue, atMost(1)).ack(any());\n        verify(queue, never()).nack(any());\n        verify(queue, never()).publish(any());\n    }\n\n    @Test\n    public void testEventHandlerWithCondition() {\n        EventHandler eventHandler = new EventHandler();\n        eventHandler.setName(\"cms_intermediate_video_ingest_handler\");\n        eventHandler.setActive(true);\n        eventHandler.setEvent(\"sqs:dev_cms_asset_ingest_queue\");\n        eventHandler.setCondition(\n                \"$.Message.testKey1 == 'level1' && $.Message.metadata.testKey2 == 123456\");\n\n        Map<String, Object> workflowInput = new LinkedHashMap<>();\n        workflowInput.put(\"param1\", \"${Message.metadata.testKey2}\");\n        workflowInput.put(\"param2\", \"SQS-${MessageId}\");\n\n        Action startWorkflowAction = new Action();\n        startWorkflowAction.setAction(Type.start_workflow);\n        startWorkflowAction.setStart_workflow(new StartWorkflow());\n        startWorkflowAction.getStart_workflow().setName(\"cms_artwork_automation\");\n        startWorkflowAction.getStart_workflow().setVersion(1);\n        startWorkflowAction.getStart_workflow().setInput(workflowInput);\n        startWorkflowAction.setExpandInlineJSON(true);\n        eventHandler.getActions().add(startWorkflowAction);\n\n        eventHandler.setEvent(event);\n\n        when(metadataService.getEventHandlersForEvent(event, true))\n                .thenReturn(Collections.singletonList(eventHandler));\n        when(executionService.addEventExecution(any())).thenReturn(true);\n        when(queue.rePublishIfNoAck()).thenReturn(false);\n\n        String id = UUID.randomUUID().toString();\n        AtomicBoolean started = new AtomicBoolean(false);\n        doAnswer(\n                        (Answer<String>)\n                                invocation -> {\n                                    started.set(true);\n                                    return id;\n                                })\n                .when(startWorkflowOperation)\n                .execute(\n                        argThat(\n                                argument ->\n                                        startWorkflowAction\n                                                        .getStart_workflow()\n                                                        .getName()\n                                                        .equals(argument.getName())\n                                                && startWorkflowAction\n                                                        .getStart_workflow()\n                                                        .getVersion()\n                                                        .equals(argument.getVersion())\n                                                && event.equals(argument.getEvent())));\n\n        SimpleActionProcessor actionProcessor =\n                new SimpleActionProcessor(\n                        workflowExecutor, parametersUtils, jsonUtils, startWorkflowOperation);\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        eventProcessor.handle(queue, message);\n        assertTrue(started.get());\n    }\n\n    @Test\n    public void testEventHandlerWithConditionEvaluator() {\n        EventHandler eventHandler = new EventHandler();\n        eventHandler.setName(\"cms_intermediate_video_ingest_handler\");\n        eventHandler.setActive(true);\n        eventHandler.setEvent(\"sqs:dev_cms_asset_ingest_queue\");\n        eventHandler.setEvaluatorType(JavascriptEvaluator.NAME);\n        eventHandler.setCondition(\n                \"$.Message.testKey1 == 'level1' && $.Message.metadata.testKey2 == 123456\");\n\n        Map<String, Object> workflowInput = new LinkedHashMap<>();\n        workflowInput.put(\"param1\", \"${Message.metadata.testKey2}\");\n        workflowInput.put(\"param2\", \"SQS-${MessageId}\");\n\n        Action startWorkflowAction = new Action();\n        startWorkflowAction.setAction(Type.start_workflow);\n        startWorkflowAction.setStart_workflow(new StartWorkflow());\n        startWorkflowAction.getStart_workflow().setName(\"cms_artwork_automation\");\n        startWorkflowAction.getStart_workflow().setVersion(1);\n        startWorkflowAction.getStart_workflow().setInput(workflowInput);\n        startWorkflowAction.setExpandInlineJSON(true);\n        eventHandler.getActions().add(startWorkflowAction);\n\n        eventHandler.setEvent(event);\n\n        when(metadataService.getEventHandlersForEvent(event, true))\n                .thenReturn(Collections.singletonList(eventHandler));\n        when(executionService.addEventExecution(any())).thenReturn(true);\n        when(queue.rePublishIfNoAck()).thenReturn(false);\n\n        String id = UUID.randomUUID().toString();\n        AtomicBoolean started = new AtomicBoolean(false);\n        doAnswer(\n                        (Answer<String>)\n                                invocation -> {\n                                    started.set(true);\n                                    return id;\n                                })\n                .when(startWorkflowOperation)\n                .execute(\n                        argThat(\n                                argument ->\n                                        startWorkflowAction\n                                                        .getStart_workflow()\n                                                        .getName()\n                                                        .equals(argument.getName())\n                                                && startWorkflowAction\n                                                        .getStart_workflow()\n                                                        .getVersion()\n                                                        .equals(argument.getVersion())\n                                                && event.equals(argument.getEvent())));\n\n        SimpleActionProcessor actionProcessor =\n                new SimpleActionProcessor(\n                        workflowExecutor, parametersUtils, jsonUtils, startWorkflowOperation);\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        eventProcessor.handle(queue, message);\n        assertTrue(started.get());\n    }\n\n    @Test\n    public void testEventProcessorWithRetriableError() {\n        EventHandler eventHandler = new EventHandler();\n        eventHandler.setName(UUID.randomUUID().toString());\n        eventHandler.setActive(true);\n        eventHandler.setEvent(event);\n\n        Action completeTaskAction = new Action();\n        completeTaskAction.setAction(Type.complete_task);\n        completeTaskAction.setComplete_task(new TaskDetails());\n        completeTaskAction.getComplete_task().setTaskRefName(\"task_x\");\n        completeTaskAction.getComplete_task().setWorkflowId(UUID.randomUUID().toString());\n        completeTaskAction.getComplete_task().setOutput(new HashMap<>());\n        eventHandler.getActions().add(completeTaskAction);\n\n        when(queue.rePublishIfNoAck()).thenReturn(false);\n        when(metadataService.getEventHandlersForEvent(event, true))\n                .thenReturn(Collections.singletonList(eventHandler));\n        when(executionService.addEventExecution(any())).thenReturn(true);\n        when(actionProcessor.execute(any(), any(), any(), any()))\n                .thenThrow(new TransientException(\"some retriable error\"));\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        eventProcessor.handle(queue, message);\n        verify(queue, never()).ack(any());\n        verify(queue, never()).nack(any());\n        verify(queue, atLeastOnce()).publish(any());\n    }\n\n    @Test\n    public void testEventProcessorWithNonRetriableError() {\n        EventHandler eventHandler = new EventHandler();\n        eventHandler.setName(UUID.randomUUID().toString());\n        eventHandler.setActive(true);\n        eventHandler.setEvent(event);\n\n        Action completeTaskAction = new Action();\n        completeTaskAction.setAction(Type.complete_task);\n        completeTaskAction.setComplete_task(new TaskDetails());\n        completeTaskAction.getComplete_task().setTaskRefName(\"task_x\");\n        completeTaskAction.getComplete_task().setWorkflowId(UUID.randomUUID().toString());\n        completeTaskAction.getComplete_task().setOutput(new HashMap<>());\n        eventHandler.getActions().add(completeTaskAction);\n\n        when(metadataService.getEventHandlersForEvent(event, true))\n                .thenReturn(Collections.singletonList(eventHandler));\n        when(executionService.addEventExecution(any())).thenReturn(true);\n\n        when(actionProcessor.execute(any(), any(), any(), any()))\n                .thenThrow(new IllegalArgumentException(\"some non-retriable error\"));\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        eventProcessor.handle(queue, message);\n        verify(queue, atMost(1)).ack(any());\n        verify(queue, never()).publish(any());\n    }\n\n    @Test\n    public void testExecuteInvalidAction() {\n        AtomicInteger executeInvoked = new AtomicInteger(0);\n        doAnswer(\n                        (Answer<Map<String, Object>>)\n                                invocation -> {\n                                    executeInvoked.incrementAndGet();\n                                    throw new UnsupportedOperationException(\"error\");\n                                })\n                .when(actionProcessor)\n                .execute(any(), any(), any(), any());\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        EventExecution eventExecution = new EventExecution(\"id\", \"messageId\");\n        eventExecution.setName(\"handler\");\n        eventExecution.setStatus(EventExecution.Status.IN_PROGRESS);\n        eventExecution.setEvent(\"event\");\n        Action action = new Action();\n        eventExecution.setAction(Type.start_workflow);\n\n        eventProcessor.execute(eventExecution, action, \"payload\");\n        assertEquals(1, executeInvoked.get());\n        assertEquals(EventExecution.Status.FAILED, eventExecution.getStatus());\n        assertNotNull(eventExecution.getOutput().get(\"exception\"));\n    }\n\n    @Test\n    public void testExecuteNonRetriableException() {\n        AtomicInteger executeInvoked = new AtomicInteger(0);\n        doAnswer(\n                        (Answer<Map<String, Object>>)\n                                invocation -> {\n                                    executeInvoked.incrementAndGet();\n                                    throw new IllegalArgumentException(\"some non-retriable error\");\n                                })\n                .when(actionProcessor)\n                .execute(any(), any(), any(), any());\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        EventExecution eventExecution = new EventExecution(\"id\", \"messageId\");\n        eventExecution.setStatus(EventExecution.Status.IN_PROGRESS);\n        eventExecution.setEvent(\"event\");\n        eventExecution.setName(\"handler\");\n        Action action = new Action();\n        action.setAction(Type.start_workflow);\n        eventExecution.setAction(Type.start_workflow);\n\n        eventProcessor.execute(eventExecution, action, \"payload\");\n        assertEquals(1, executeInvoked.get());\n        assertEquals(EventExecution.Status.FAILED, eventExecution.getStatus());\n        assertNotNull(eventExecution.getOutput().get(\"exception\"));\n    }\n\n    @Test\n    public void testExecuteTransientException() {\n        AtomicInteger executeInvoked = new AtomicInteger(0);\n        doAnswer(\n                        (Answer<Map<String, Object>>)\n                                invocation -> {\n                                    executeInvoked.incrementAndGet();\n                                    throw new TransientException(\"some retriable error\");\n                                })\n                .when(actionProcessor)\n                .execute(any(), any(), any(), any());\n\n        DefaultEventProcessor eventProcessor =\n                new DefaultEventProcessor(\n                        executionService,\n                        metadataService,\n                        actionProcessor,\n                        jsonUtils,\n                        properties,\n                        objectMapper,\n                        evaluators,\n                        retryTemplate);\n        EventExecution eventExecution = new EventExecution(\"id\", \"messageId\");\n        eventExecution.setStatus(EventExecution.Status.IN_PROGRESS);\n        eventExecution.setEvent(\"event\");\n        Action action = new Action();\n        action.setAction(Type.start_workflow);\n\n        eventProcessor.execute(eventExecution, action, \"payload\");\n        assertEquals(3, executeInvoked.get());\n        assertNull(eventExecution.getOutput().get(\"exception\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/events/TestScriptEval.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\npublic class TestScriptEval {\n\n    @Test\n    public void testScript() throws Exception {\n        Map<String, Object> payload = new HashMap<>();\n        Map<String, Object> app = new HashMap<>();\n        app.put(\"name\", \"conductor\");\n        app.put(\"version\", 2.0);\n        app.put(\"license\", \"Apache 2.0\");\n\n        payload.put(\"app\", app);\n        payload.put(\"author\", \"Netflix\");\n        payload.put(\"oss\", true);\n\n        String script1 = \"$.app.name == 'conductor'\"; // true\n        String script2 = \"$.version > 3\"; // false\n        String script3 = \"$.oss\"; // true\n        String script4 = \"$.author == 'me'\"; // false\n\n        assertTrue(ScriptEvaluator.evalBool(script1, payload));\n        assertFalse(ScriptEvaluator.evalBool(script2, payload));\n        assertTrue(ScriptEvaluator.evalBool(script3, payload));\n        assertFalse(ScriptEvaluator.evalBool(script4, payload));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/events/TestSimpleActionProcessor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.events;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action.Type;\nimport com.netflix.conductor.common.metadata.events.EventHandler.StartWorkflow;\nimport com.netflix.conductor.common.metadata.events.EventHandler.TaskDetails;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult.Status;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.JsonUtils;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class TestSimpleActionProcessor {\n\n    private WorkflowExecutor workflowExecutor;\n    private ExternalPayloadStorageUtils externalPayloadStorageUtils;\n    private SimpleActionProcessor actionProcessor;\n    private StartWorkflowOperation startWorkflowOperation;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void setup() {\n        externalPayloadStorageUtils = mock(ExternalPayloadStorageUtils.class);\n\n        workflowExecutor = mock(WorkflowExecutor.class);\n        startWorkflowOperation = mock(StartWorkflowOperation.class);\n\n        actionProcessor =\n                new SimpleActionProcessor(\n                        workflowExecutor,\n                        new ParametersUtils(objectMapper),\n                        new JsonUtils(objectMapper),\n                        startWorkflowOperation);\n    }\n\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n    @Test\n    public void testStartWorkflow_correlationId() throws Exception {\n        StartWorkflow startWorkflow = new StartWorkflow();\n        startWorkflow.setName(\"testWorkflow\");\n        startWorkflow.getInput().put(\"testInput\", \"${testId}\");\n        startWorkflow.setCorrelationId(\"${correlationId}\");\n\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(\"*\", \"dev\");\n        startWorkflow.setTaskToDomain(taskToDomain);\n\n        Action action = new Action();\n        action.setAction(Type.start_workflow);\n        action.setStart_workflow(startWorkflow);\n\n        Object payload =\n                objectMapper.readValue(\n                        \"{\\\"correlationId\\\":\\\"test-id\\\", \\\"testId\\\":\\\"test_1\\\"}\", Object.class);\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testWorkflow\");\n        workflowDef.setVersion(1);\n\n        when(startWorkflowOperation.execute(any())).thenReturn(\"workflow_1\");\n\n        Map<String, Object> output =\n                actionProcessor.execute(action, payload, \"testEvent\", \"testMessage\");\n\n        assertNotNull(output);\n        assertEquals(\"workflow_1\", output.get(\"workflowId\"));\n\n        ArgumentCaptor<StartWorkflowInput> startWorkflowInputArgumentCaptor =\n                ArgumentCaptor.forClass(StartWorkflowInput.class);\n\n        verify(startWorkflowOperation).execute(startWorkflowInputArgumentCaptor.capture());\n        StartWorkflowInput capturedValue = startWorkflowInputArgumentCaptor.getValue();\n\n        assertEquals(\"test_1\", capturedValue.getWorkflowInput().get(\"testInput\"));\n        assertEquals(\"test-id\", capturedValue.getCorrelationId());\n        assertEquals(\n                \"testMessage\", capturedValue.getWorkflowInput().get(\"conductor.event.messageId\"));\n        assertEquals(\"testEvent\", capturedValue.getWorkflowInput().get(\"conductor.event.name\"));\n        assertEquals(taskToDomain, capturedValue.getTaskToDomain());\n    }\n\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n    @Test\n    public void testStartWorkflow() throws Exception {\n        StartWorkflow startWorkflow = new StartWorkflow();\n        startWorkflow.setName(\"testWorkflow\");\n        startWorkflow.getInput().put(\"testInput\", \"${testId}\");\n\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(\"*\", \"dev\");\n        startWorkflow.setTaskToDomain(taskToDomain);\n\n        Action action = new Action();\n        action.setAction(Type.start_workflow);\n        action.setStart_workflow(startWorkflow);\n\n        Object payload = objectMapper.readValue(\"{\\\"testId\\\":\\\"test_1\\\"}\", Object.class);\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testWorkflow\");\n        workflowDef.setVersion(1);\n\n        when(startWorkflowOperation.execute(any())).thenReturn(\"workflow_1\");\n\n        Map<String, Object> output =\n                actionProcessor.execute(action, payload, \"testEvent\", \"testMessage\");\n\n        assertNotNull(output);\n        assertEquals(\"workflow_1\", output.get(\"workflowId\"));\n\n        ArgumentCaptor<StartWorkflowInput> startWorkflowInputArgumentCaptor =\n                ArgumentCaptor.forClass(StartWorkflowInput.class);\n\n        verify(startWorkflowOperation).execute(startWorkflowInputArgumentCaptor.capture());\n        StartWorkflowInput capturedArgument = startWorkflowInputArgumentCaptor.getValue();\n        assertEquals(\"test_1\", capturedArgument.getWorkflowInput().get(\"testInput\"));\n        assertNull(capturedArgument.getCorrelationId());\n        assertEquals(\n                \"testMessage\",\n                capturedArgument.getWorkflowInput().get(\"conductor.event.messageId\"));\n        assertEquals(\"testEvent\", capturedArgument.getWorkflowInput().get(\"conductor.event.name\"));\n        assertEquals(taskToDomain, capturedArgument.getTaskToDomain());\n    }\n\n    @Test\n    public void testCompleteTask() throws Exception {\n        TaskDetails taskDetails = new TaskDetails();\n        taskDetails.setWorkflowId(\"${workflowId}\");\n        taskDetails.setTaskRefName(\"testTask\");\n        taskDetails.getOutput().put(\"someNEKey\", \"${Message.someNEKey}\");\n        taskDetails.getOutput().put(\"someKey\", \"${Message.someKey}\");\n        taskDetails.getOutput().put(\"someNullKey\", \"${Message.someNullKey}\");\n\n        Action action = new Action();\n        action.setAction(Type.complete_task);\n        action.setComplete_task(taskDetails);\n\n        String payloadJson =\n                \"{\\\"workflowId\\\":\\\"workflow_1\\\",\\\"Message\\\":{\\\"someKey\\\":\\\"someData\\\",\\\"someNullKey\\\":null}}\";\n        Object payload = objectMapper.readValue(payloadJson, Object.class);\n\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(\"testTask\");\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.getTasks().add(task);\n\n        when(workflowExecutor.getWorkflow(eq(\"workflow_1\"), anyBoolean())).thenReturn(workflow);\n        doNothing().when(externalPayloadStorageUtils).verifyAndUpload(any(), any());\n\n        actionProcessor.execute(action, payload, \"testEvent\", \"testMessage\");\n\n        ArgumentCaptor<TaskResult> argumentCaptor = ArgumentCaptor.forClass(TaskResult.class);\n        verify(workflowExecutor).updateTask(argumentCaptor.capture());\n        assertEquals(Status.COMPLETED, argumentCaptor.getValue().getStatus());\n        assertEquals(\n                \"testMessage\",\n                argumentCaptor.getValue().getOutputData().get(\"conductor.event.messageId\"));\n        assertEquals(\n                \"testEvent\", argumentCaptor.getValue().getOutputData().get(\"conductor.event.name\"));\n        assertEquals(\"workflow_1\", argumentCaptor.getValue().getOutputData().get(\"workflowId\"));\n        assertEquals(\"testTask\", argumentCaptor.getValue().getOutputData().get(\"taskRefName\"));\n        assertEquals(\"someData\", argumentCaptor.getValue().getOutputData().get(\"someKey\"));\n        // Assert values not in message are evaluated to null\n        assertTrue(\"testTask\", argumentCaptor.getValue().getOutputData().containsKey(\"someNEKey\"));\n        // Assert null values from message are kept\n        assertTrue(\n                \"testTask\", argumentCaptor.getValue().getOutputData().containsKey(\"someNullKey\"));\n        assertNull(\"testTask\", argumentCaptor.getValue().getOutputData().get(\"someNullKey\"));\n    }\n\n    @Test\n    public void testCompleteLoopOverTask() throws Exception {\n        TaskDetails taskDetails = new TaskDetails();\n        taskDetails.setWorkflowId(\"${workflowId}\");\n        taskDetails.setTaskRefName(\"testTask\");\n        taskDetails.getOutput().put(\"someNEKey\", \"${Message.someNEKey}\");\n        taskDetails.getOutput().put(\"someKey\", \"${Message.someKey}\");\n        taskDetails.getOutput().put(\"someNullKey\", \"${Message.someNullKey}\");\n\n        Action action = new Action();\n        action.setAction(Type.complete_task);\n        action.setComplete_task(taskDetails);\n\n        String payloadJson =\n                \"{\\\"workflowId\\\":\\\"workflow_1\\\",  \\\"taskRefName\\\":\\\"testTask\\\", \\\"Message\\\":{\\\"someKey\\\":\\\"someData\\\",\\\"someNullKey\\\":null}}\";\n        Object payload = objectMapper.readValue(payloadJson, Object.class);\n\n        TaskModel task = new TaskModel();\n        task.setIteration(1);\n        task.setReferenceTaskName(\"testTask__1\");\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.getTasks().add(task);\n\n        when(workflowExecutor.getWorkflow(eq(\"workflow_1\"), anyBoolean())).thenReturn(workflow);\n        doNothing().when(externalPayloadStorageUtils).verifyAndUpload(any(), any());\n\n        actionProcessor.execute(action, payload, \"testEvent\", \"testMessage\");\n\n        ArgumentCaptor<TaskResult> argumentCaptor = ArgumentCaptor.forClass(TaskResult.class);\n        verify(workflowExecutor).updateTask(argumentCaptor.capture());\n        assertEquals(Status.COMPLETED, argumentCaptor.getValue().getStatus());\n        assertEquals(\n                \"testMessage\",\n                argumentCaptor.getValue().getOutputData().get(\"conductor.event.messageId\"));\n        assertEquals(\n                \"testEvent\", argumentCaptor.getValue().getOutputData().get(\"conductor.event.name\"));\n        assertEquals(\"workflow_1\", argumentCaptor.getValue().getOutputData().get(\"workflowId\"));\n        assertEquals(\"testTask\", argumentCaptor.getValue().getOutputData().get(\"taskRefName\"));\n        assertEquals(\"someData\", argumentCaptor.getValue().getOutputData().get(\"someKey\"));\n        // Assert values not in message are evaluated to null\n        assertTrue(\"testTask\", argumentCaptor.getValue().getOutputData().containsKey(\"someNEKey\"));\n        // Assert null values from message are kept\n        assertTrue(\n                \"testTask\", argumentCaptor.getValue().getOutputData().containsKey(\"someNullKey\"));\n        assertNull(\"testTask\", argumentCaptor.getValue().getOutputData().get(\"someNullKey\"));\n    }\n\n    @Test\n    public void testCompleteTaskByTaskId() throws Exception {\n        TaskDetails taskDetails = new TaskDetails();\n        taskDetails.setWorkflowId(\"${workflowId}\");\n        taskDetails.setTaskId(\"${taskId}\");\n\n        Action action = new Action();\n        action.setAction(Type.complete_task);\n        action.setComplete_task(taskDetails);\n\n        Object payload =\n                objectMapper.readValue(\n                        \"{\\\"workflowId\\\":\\\"workflow_1\\\", \\\"taskId\\\":\\\"task_1\\\"}\", Object.class);\n\n        TaskModel task = new TaskModel();\n        task.setTaskId(\"task_1\");\n        task.setReferenceTaskName(\"testTask\");\n\n        when(workflowExecutor.getTask(eq(\"task_1\"))).thenReturn(task);\n        doNothing().when(externalPayloadStorageUtils).verifyAndUpload(any(), any());\n\n        actionProcessor.execute(action, payload, \"testEvent\", \"testMessage\");\n\n        ArgumentCaptor<TaskResult> argumentCaptor = ArgumentCaptor.forClass(TaskResult.class);\n        verify(workflowExecutor).updateTask(argumentCaptor.capture());\n        assertEquals(Status.COMPLETED, argumentCaptor.getValue().getStatus());\n        assertEquals(\n                \"testMessage\",\n                argumentCaptor.getValue().getOutputData().get(\"conductor.event.messageId\"));\n        assertEquals(\n                \"testEvent\", argumentCaptor.getValue().getOutputData().get(\"conductor.event.name\"));\n        assertEquals(\"workflow_1\", argumentCaptor.getValue().getOutputData().get(\"workflowId\"));\n        assertEquals(\"task_1\", argumentCaptor.getValue().getOutputData().get(\"taskId\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/TestDeciderOutcomes.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport java.io.InputStream;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.util.unit.DataSize;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.execution.DeciderService.DeciderOutcome;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.core.execution.mapper.DecisionTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.DynamicTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.EventTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.ForkJoinDynamicTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.ForkJoinTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.HTTPTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.JoinTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.SimpleTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.SubWorkflowTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.SwitchTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.TaskMapper;\nimport com.netflix.conductor.core.execution.mapper.UserDefinedTaskMapper;\nimport com.netflix.conductor.core.execution.mapper.WaitTaskMapper;\nimport com.netflix.conductor.core.execution.tasks.Decision;\nimport com.netflix.conductor.core.execution.tasks.Join;\nimport com.netflix.conductor.core.execution.tasks.Switch;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.DECISION;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.DYNAMIC;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.EVENT;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.FORK_JOIN;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.FORK_JOIN_DYNAMIC;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.HTTP;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.JOIN;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.SIMPLE;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.SUB_WORKFLOW;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.SWITCH;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_DECISION;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SWITCH;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.USER_DEFINED;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.WAIT;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNotSame;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@ContextConfiguration(\n        classes = {\n            TestObjectMapperConfiguration.class,\n            TestDeciderOutcomes.TestConfiguration.class\n        })\n@RunWith(SpringRunner.class)\npublic class TestDeciderOutcomes {\n\n    private DeciderService deciderService;\n\n    @Autowired private Map<String, Evaluator> evaluators;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Autowired private SystemTaskRegistry systemTaskRegistry;\n\n    @Configuration\n    @ComponentScan(basePackageClasses = {Evaluator.class}) // load all Evaluator beans.\n    public static class TestConfiguration {\n\n        @Bean(TASK_TYPE_DECISION)\n        public Decision decision() {\n            return new Decision();\n        }\n\n        @Bean(TASK_TYPE_SWITCH)\n        public Switch switchTask() {\n            return new Switch();\n        }\n\n        @Bean(TASK_TYPE_JOIN)\n        public Join join() {\n            return new Join();\n        }\n\n        @Bean\n        public SystemTaskRegistry systemTaskRegistry(Set<WorkflowSystemTask> tasks) {\n            return new SystemTaskRegistry(tasks);\n        }\n    }\n\n    @Before\n    public void init() {\n        MetadataDAO metadataDAO = mock(MetadataDAO.class);\n\n        ExternalPayloadStorageUtils externalPayloadStorageUtils =\n                mock(ExternalPayloadStorageUtils.class);\n        ConductorProperties properties = mock(ConductorProperties.class);\n        when(properties.getTaskInputPayloadSizeThreshold()).thenReturn(DataSize.ofKilobytes(10L));\n        when(properties.getMaxTaskInputPayloadSizeThreshold())\n                .thenReturn(DataSize.ofKilobytes(10240L));\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setRetryCount(1);\n        taskDef.setName(\"mockTaskDef\");\n        taskDef.setResponseTimeoutSeconds(60 * 60);\n        when(metadataDAO.getTaskDef(anyString())).thenReturn(taskDef);\n        ParametersUtils parametersUtils = new ParametersUtils(objectMapper);\n        Map<String, TaskMapper> taskMappers = new HashMap<>();\n        taskMappers.put(DECISION.name(), new DecisionTaskMapper());\n        taskMappers.put(SWITCH.name(), new SwitchTaskMapper(evaluators));\n        taskMappers.put(DYNAMIC.name(), new DynamicTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(FORK_JOIN.name(), new ForkJoinTaskMapper());\n        taskMappers.put(JOIN.name(), new JoinTaskMapper());\n        taskMappers.put(\n                FORK_JOIN_DYNAMIC.name(),\n                new ForkJoinDynamicTaskMapper(\n                        new IDGenerator(), parametersUtils, objectMapper, metadataDAO));\n        taskMappers.put(\n                USER_DEFINED.name(), new UserDefinedTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(SIMPLE.name(), new SimpleTaskMapper(parametersUtils));\n        taskMappers.put(\n                SUB_WORKFLOW.name(), new SubWorkflowTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(EVENT.name(), new EventTaskMapper(parametersUtils));\n        taskMappers.put(WAIT.name(), new WaitTaskMapper(parametersUtils));\n        taskMappers.put(HTTP.name(), new HTTPTaskMapper(parametersUtils, metadataDAO));\n\n        this.deciderService =\n                new DeciderService(\n                        new IDGenerator(),\n                        parametersUtils,\n                        metadataDAO,\n                        externalPayloadStorageUtils,\n                        systemTaskRegistry,\n                        taskMappers,\n                        Duration.ofMinutes(60));\n    }\n\n    @Test\n    public void testWorkflowWithNoTasks() throws Exception {\n        InputStream stream = new ClassPathResource(\"./conditional_flow.json\").getInputStream();\n        WorkflowDef def = objectMapper.readValue(stream, WorkflowDef.class);\n        assertNotNull(def);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setCreateTime(0L);\n        workflow.getInput().put(\"param1\", \"nested\");\n        workflow.getInput().put(\"param2\", \"one\");\n\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n        assertFalse(outcome.isComplete);\n        assertTrue(outcome.tasksToBeUpdated.isEmpty());\n        assertEquals(3, outcome.tasksToBeScheduled.size());\n\n        outcome.tasksToBeScheduled.forEach(t -> t.setStatus(TaskModel.Status.COMPLETED));\n        workflow.getTasks().addAll(outcome.tasksToBeScheduled);\n        outcome = deciderService.decide(workflow);\n        assertFalse(outcome.isComplete);\n        assertEquals(outcome.tasksToBeUpdated.toString(), 3, outcome.tasksToBeUpdated.size());\n        assertEquals(1, outcome.tasksToBeScheduled.size());\n        assertEquals(\"junit_task_3\", outcome.tasksToBeScheduled.get(0).getTaskDefName());\n    }\n\n    @Test\n    public void testWorkflowWithNoTasksWithSwitch() throws Exception {\n        InputStream stream =\n                new ClassPathResource(\"./conditional_flow_with_switch.json\").getInputStream();\n        WorkflowDef def = objectMapper.readValue(stream, WorkflowDef.class);\n        assertNotNull(def);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setCreateTime(0L);\n        workflow.getInput().put(\"param1\", \"nested\");\n        workflow.getInput().put(\"param2\", \"one\");\n\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n        assertFalse(outcome.isComplete);\n        assertTrue(outcome.tasksToBeUpdated.isEmpty());\n        assertEquals(3, outcome.tasksToBeScheduled.size());\n\n        outcome.tasksToBeScheduled.forEach(t -> t.setStatus(TaskModel.Status.COMPLETED));\n        workflow.getTasks().addAll(outcome.tasksToBeScheduled);\n        outcome = deciderService.decide(workflow);\n        assertFalse(outcome.isComplete);\n        assertEquals(outcome.tasksToBeUpdated.toString(), 3, outcome.tasksToBeUpdated.size());\n        assertEquals(1, outcome.tasksToBeScheduled.size());\n        assertEquals(\"junit_task_3\", outcome.tasksToBeScheduled.get(0).getTaskDefName());\n    }\n\n    @Test\n    public void testRetries() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"test_task\");\n        workflowTask.setType(\"USER_TASK\");\n        workflowTask.setTaskReferenceName(\"t0\");\n        workflowTask.getInputParameters().put(\"taskId\", \"${CPEWF_TASK_ID}\");\n        workflowTask.getInputParameters().put(\"requestId\", \"${workflow.input.requestId}\");\n        workflowTask.setTaskDefinition(new TaskDef(\"test_task\"));\n\n        def.getTasks().add(workflowTask);\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.getInput().put(\"requestId\", 123);\n        workflow.setCreateTime(System.currentTimeMillis());\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n\n        assertEquals(1, outcome.tasksToBeScheduled.size());\n        assertEquals(\n                workflowTask.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n\n        String task1Id = outcome.tasksToBeScheduled.get(0).getTaskId();\n        assertEquals(task1Id, outcome.tasksToBeScheduled.get(0).getInputData().get(\"taskId\"));\n        assertEquals(123, outcome.tasksToBeScheduled.get(0).getInputData().get(\"requestId\"));\n\n        outcome.tasksToBeScheduled.get(0).setStatus(TaskModel.Status.FAILED);\n        workflow.getTasks().addAll(outcome.tasksToBeScheduled);\n\n        outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n\n        assertEquals(1, outcome.tasksToBeUpdated.size());\n        assertEquals(1, outcome.tasksToBeScheduled.size());\n        assertEquals(task1Id, outcome.tasksToBeUpdated.get(0).getTaskId());\n        assertNotSame(task1Id, outcome.tasksToBeScheduled.get(0).getTaskId());\n        assertEquals(\n                outcome.tasksToBeScheduled.get(0).getTaskId(),\n                outcome.tasksToBeScheduled.get(0).getInputData().get(\"taskId\"));\n        assertEquals(task1Id, outcome.tasksToBeScheduled.get(0).getRetriedTaskId());\n        assertEquals(123, outcome.tasksToBeScheduled.get(0).getInputData().get(\"requestId\"));\n\n        WorkflowTask fork = new WorkflowTask();\n        fork.setName(\"fork0\");\n        fork.setWorkflowTaskType(TaskType.FORK_JOIN_DYNAMIC);\n        fork.setTaskReferenceName(\"fork0\");\n        fork.setDynamicForkTasksInputParamName(\"forkedInputs\");\n        fork.setDynamicForkTasksParam(\"forks\");\n        fork.getInputParameters().put(\"forks\", \"${workflow.input.forks}\");\n        fork.getInputParameters().put(\"forkedInputs\", \"${workflow.input.forkedInputs}\");\n\n        WorkflowTask join = new WorkflowTask();\n        join.setName(\"join0\");\n        join.setType(\"JOIN\");\n        join.setTaskReferenceName(\"join0\");\n\n        def.getTasks().clear();\n        def.getTasks().add(fork);\n        def.getTasks().add(join);\n\n        List<WorkflowTask> forks = new LinkedList<>();\n        Map<String, Map<String, Object>> forkedInputs = new HashMap<>();\n\n        for (int i = 0; i < 1; i++) {\n            WorkflowTask wft = new WorkflowTask();\n            wft.setName(\"f\" + i);\n            wft.setTaskReferenceName(\"f\" + i);\n            wft.setWorkflowTaskType(TaskType.SIMPLE);\n            wft.getInputParameters().put(\"requestId\", \"${workflow.input.requestId}\");\n            wft.getInputParameters().put(\"taskId\", \"${CPEWF_TASK_ID}\");\n            wft.setTaskDefinition(new TaskDef(\"f\" + i));\n            forks.add(wft);\n            Map<String, Object> input = new HashMap<>();\n            input.put(\"k\", \"v\");\n            input.put(\"k1\", 1);\n            forkedInputs.put(wft.getTaskReferenceName(), input);\n        }\n        workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.getInput().put(\"requestId\", 123);\n        workflow.setCreateTime(System.currentTimeMillis());\n\n        workflow.getInput().put(\"forks\", forks);\n        workflow.getInput().put(\"forkedInputs\", forkedInputs);\n\n        outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n        assertEquals(3, outcome.tasksToBeScheduled.size());\n        assertEquals(0, outcome.tasksToBeUpdated.size());\n\n        assertEquals(\"v\", outcome.tasksToBeScheduled.get(1).getInputData().get(\"k\"));\n        assertEquals(1, outcome.tasksToBeScheduled.get(1).getInputData().get(\"k1\"));\n        assertEquals(\n                outcome.tasksToBeScheduled.get(1).getTaskId(),\n                outcome.tasksToBeScheduled.get(1).getInputData().get(\"taskId\"));\n        task1Id = outcome.tasksToBeScheduled.get(1).getTaskId();\n\n        outcome.tasksToBeScheduled.get(1).setStatus(TaskModel.Status.FAILED);\n        for (TaskModel taskToBeScheduled : outcome.tasksToBeScheduled) {\n            taskToBeScheduled.setUpdateTime(System.currentTimeMillis());\n        }\n        workflow.getTasks().addAll(outcome.tasksToBeScheduled);\n\n        outcome = deciderService.decide(workflow);\n        assertTrue(\n                outcome.tasksToBeScheduled.stream()\n                        .anyMatch(task1 -> task1.getReferenceTaskName().equals(\"f0\")));\n\n        Optional<TaskModel> optionalTask =\n                outcome.tasksToBeScheduled.stream()\n                        .filter(t -> t.getReferenceTaskName().equals(\"f0\"))\n                        .findFirst();\n        assertTrue(optionalTask.isPresent());\n        TaskModel task = optionalTask.get();\n        assertEquals(\"v\", task.getInputData().get(\"k\"));\n        assertEquals(1, task.getInputData().get(\"k1\"));\n        assertEquals(task.getTaskId(), task.getInputData().get(\"taskId\"));\n        assertNotSame(task1Id, task.getTaskId());\n        assertEquals(task1Id, task.getRetriedTaskId());\n    }\n\n    @Test\n    public void testOptional() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n\n        WorkflowTask task1 = new WorkflowTask();\n        task1.setName(\"task0\");\n        task1.setType(\"SIMPLE\");\n        task1.setTaskReferenceName(\"t0\");\n        task1.getInputParameters().put(\"taskId\", \"${CPEWF_TASK_ID}\");\n        task1.setOptional(true);\n        task1.setTaskDefinition(new TaskDef(\"task0\"));\n\n        WorkflowTask task2 = new WorkflowTask();\n        task2.setName(\"task1\");\n        task2.setType(\"SIMPLE\");\n        task2.setTaskReferenceName(\"t1\");\n        task2.setTaskDefinition(new TaskDef(\"task1\"));\n\n        def.getTasks().add(task1);\n        def.getTasks().add(task2);\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setCreateTime(System.currentTimeMillis());\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n        assertEquals(1, outcome.tasksToBeScheduled.size());\n        assertEquals(\n                task1.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n\n        for (int i = 0; i < 3; i++) {\n            String task1Id = outcome.tasksToBeScheduled.get(0).getTaskId();\n            assertEquals(task1Id, outcome.tasksToBeScheduled.get(0).getInputData().get(\"taskId\"));\n\n            workflow.getTasks().clear();\n            workflow.getTasks().addAll(outcome.tasksToBeScheduled);\n            workflow.getTasks().get(0).setStatus(TaskModel.Status.FAILED);\n\n            outcome = deciderService.decide(workflow);\n\n            assertNotNull(outcome);\n            assertEquals(1, outcome.tasksToBeUpdated.size());\n            assertEquals(1, outcome.tasksToBeScheduled.size());\n\n            assertEquals(TaskModel.Status.FAILED, workflow.getTasks().get(0).getStatus());\n            assertEquals(task1Id, outcome.tasksToBeUpdated.get(0).getTaskId());\n            assertEquals(\n                    task1.getTaskReferenceName(),\n                    outcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n            assertEquals(i + 1, outcome.tasksToBeScheduled.get(0).getRetryCount());\n        }\n\n        String task1Id = outcome.tasksToBeScheduled.get(0).getTaskId();\n\n        workflow.getTasks().clear();\n        workflow.getTasks().addAll(outcome.tasksToBeScheduled);\n        workflow.getTasks().get(0).setStatus(TaskModel.Status.FAILED);\n\n        outcome = deciderService.decide(workflow);\n\n        assertNotNull(outcome);\n        assertEquals(1, outcome.tasksToBeUpdated.size());\n        assertEquals(1, outcome.tasksToBeScheduled.size());\n\n        assertEquals(\n                TaskModel.Status.COMPLETED_WITH_ERRORS, workflow.getTasks().get(0).getStatus());\n        assertEquals(task1Id, outcome.tasksToBeUpdated.get(0).getTaskId());\n        assertEquals(\n                task2.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n    }\n\n    @Test\n    public void testOptionalWithDynamicFork() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n\n        WorkflowTask task1 = new WorkflowTask();\n        task1.setName(\"fork0\");\n        task1.setWorkflowTaskType(TaskType.FORK_JOIN_DYNAMIC);\n        task1.setTaskReferenceName(\"fork0\");\n        task1.setDynamicForkTasksInputParamName(\"forkedInputs\");\n        task1.setDynamicForkTasksParam(\"forks\");\n        task1.getInputParameters().put(\"forks\", \"${workflow.input.forks}\");\n        task1.getInputParameters().put(\"forkedInputs\", \"${workflow.input.forkedInputs}\");\n\n        WorkflowTask task2 = new WorkflowTask();\n        task2.setName(\"join0\");\n        task2.setType(\"JOIN\");\n        task2.setTaskReferenceName(\"join0\");\n\n        def.getTasks().add(task1);\n        def.getTasks().add(task2);\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        List<WorkflowTask> forks = new LinkedList<>();\n        Map<String, Map<String, Object>> forkedInputs = new HashMap<>();\n\n        for (int i = 0; i < 3; i++) {\n            WorkflowTask workflowTask = new WorkflowTask();\n            workflowTask.setName(\"f\" + i);\n            workflowTask.setTaskReferenceName(\"f\" + i);\n            workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n            workflowTask.setOptional(true);\n            workflowTask.setTaskDefinition(new TaskDef(\"f\" + i));\n            forks.add(workflowTask);\n\n            forkedInputs.put(workflowTask.getTaskReferenceName(), new HashMap<>());\n        }\n        workflow.getInput().put(\"forks\", forks);\n        workflow.getInput().put(\"forkedInputs\", forkedInputs);\n\n        workflow.setCreateTime(System.currentTimeMillis());\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n        assertEquals(5, outcome.tasksToBeScheduled.size());\n        assertEquals(0, outcome.tasksToBeUpdated.size());\n        assertEquals(TASK_TYPE_FORK, outcome.tasksToBeScheduled.get(0).getTaskType());\n        assertEquals(TaskModel.Status.COMPLETED, outcome.tasksToBeScheduled.get(0).getStatus());\n\n        for (int retryCount = 0; retryCount < 4; retryCount++) {\n\n            for (TaskModel taskToBeScheduled : outcome.tasksToBeScheduled) {\n                if (taskToBeScheduled.getTaskDefName().equals(\"join0\")) {\n                    assertEquals(TaskModel.Status.IN_PROGRESS, taskToBeScheduled.getStatus());\n                } else if (taskToBeScheduled.getTaskType().matches(\"(f0|f1|f2)\")) {\n                    assertEquals(TaskModel.Status.SCHEDULED, taskToBeScheduled.getStatus());\n                    taskToBeScheduled.setStatus(TaskModel.Status.FAILED);\n                }\n\n                taskToBeScheduled.setUpdateTime(System.currentTimeMillis());\n            }\n            workflow.getTasks().addAll(outcome.tasksToBeScheduled);\n            outcome = deciderService.decide(workflow);\n            assertNotNull(outcome);\n        }\n        assertEquals(TASK_TYPE_JOIN, outcome.tasksToBeScheduled.get(0).getTaskType());\n\n        for (int i = 0; i < 3; i++) {\n            assertEquals(\n                    TaskModel.Status.COMPLETED_WITH_ERRORS,\n                    outcome.tasksToBeUpdated.get(i).getStatus());\n            assertEquals(\"f\" + (i), outcome.tasksToBeUpdated.get(i).getTaskDefName());\n        }\n\n        assertEquals(TaskModel.Status.IN_PROGRESS, outcome.tasksToBeScheduled.get(0).getStatus());\n        new Join().execute(workflow, outcome.tasksToBeScheduled.get(0), null);\n        assertEquals(\n                TaskModel.Status.COMPLETED_WITH_ERRORS,\n                outcome.tasksToBeScheduled.get(0).getStatus());\n    }\n\n    @Test\n    public void testDecisionCases() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n\n        WorkflowTask even = new WorkflowTask();\n        even.setName(\"even\");\n        even.setType(\"SIMPLE\");\n        even.setTaskReferenceName(\"even\");\n        even.setTaskDefinition(new TaskDef(\"even\"));\n\n        WorkflowTask odd = new WorkflowTask();\n        odd.setName(\"odd\");\n        odd.setType(\"SIMPLE\");\n        odd.setTaskReferenceName(\"odd\");\n        odd.setTaskDefinition(new TaskDef(\"odd\"));\n\n        WorkflowTask defaultt = new WorkflowTask();\n        defaultt.setName(\"defaultt\");\n        defaultt.setType(\"SIMPLE\");\n        defaultt.setTaskReferenceName(\"defaultt\");\n        defaultt.setTaskDefinition(new TaskDef(\"defaultt\"));\n\n        WorkflowTask decide = new WorkflowTask();\n        decide.setName(\"decide\");\n        decide.setWorkflowTaskType(TaskType.DECISION);\n        decide.setTaskReferenceName(\"d0\");\n        decide.getInputParameters().put(\"Id\", \"${workflow.input.Id}\");\n        decide.getInputParameters().put(\"location\", \"${workflow.input.location}\");\n        decide.setCaseExpression(\n                \"if ($.Id == null) 'bad input'; else if ( ($.Id != null && $.Id % 2 == 0) || $.location == 'usa') 'even'; else 'odd'; \");\n\n        decide.getDecisionCases().put(\"even\", Collections.singletonList(even));\n        decide.getDecisionCases().put(\"odd\", Collections.singletonList(odd));\n        decide.setDefaultCase(Collections.singletonList(defaultt));\n\n        def.getTasks().add(decide);\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setCreateTime(System.currentTimeMillis());\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        assertNotNull(outcome);\n        assertEquals(2, outcome.tasksToBeScheduled.size());\n        assertEquals(\n                decide.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n        assertEquals(\n                defaultt.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(1).getReferenceTaskName()); // default\n        assertEquals(\n                Collections.singletonList(\"bad input\"),\n                outcome.tasksToBeScheduled.get(0).getOutputData().get(\"caseOutput\"));\n\n        workflow.getInput().put(\"Id\", 9);\n        workflow.getInput().put(\"location\", \"usa\");\n        outcome = deciderService.decide(workflow);\n        assertEquals(2, outcome.tasksToBeScheduled.size());\n        assertEquals(\n                decide.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n        assertEquals(\n                even.getTaskReferenceName(),\n                outcome.tasksToBeScheduled\n                        .get(1)\n                        .getReferenceTaskName()); // even because of location == usa\n        assertEquals(\n                Collections.singletonList(\"even\"),\n                outcome.tasksToBeScheduled.get(0).getOutputData().get(\"caseOutput\"));\n\n        workflow.getInput().put(\"Id\", 9);\n        workflow.getInput().put(\"location\", \"canada\");\n        outcome = deciderService.decide(workflow);\n        assertEquals(2, outcome.tasksToBeScheduled.size());\n        assertEquals(\n                decide.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n        assertEquals(\n                odd.getTaskReferenceName(),\n                outcome.tasksToBeScheduled.get(1).getReferenceTaskName()); // odd\n        assertEquals(\n                Collections.singletonList(\"odd\"),\n                outcome.tasksToBeScheduled.get(0).getOutputData().get(\"caseOutput\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/TestDeciderService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.time.Duration;\nimport java.util.*;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef.TimeoutPolicy;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.DeciderService.DeciderOutcome;\nimport com.netflix.conductor.core.execution.mapper.TaskMapper;\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.*;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@ContextConfiguration(\n        classes = {TestObjectMapperConfiguration.class, TestDeciderService.TestConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class TestDeciderService {\n\n    @Configuration\n    @ComponentScan(basePackageClasses = TaskMapper.class) // loads all TaskMapper beans\n    public static class TestConfiguration {\n\n        @Bean(TASK_TYPE_SUB_WORKFLOW)\n        public SubWorkflow subWorkflow(ObjectMapper objectMapper) {\n            return new SubWorkflow(objectMapper, mock(StartWorkflowOperation.class));\n        }\n\n        @Bean(\"asyncCompleteSystemTask\")\n        public WorkflowSystemTaskStub asyncCompleteSystemTask() {\n            return new WorkflowSystemTaskStub(\"asyncCompleteSystemTask\") {\n                @Override\n                public boolean isAsyncComplete(TaskModel task) {\n                    return true;\n                }\n            };\n        }\n\n        @Bean\n        public SystemTaskRegistry systemTaskRegistry(Set<WorkflowSystemTask> tasks) {\n            return new SystemTaskRegistry(tasks);\n        }\n\n        @Bean\n        public MetadataDAO mockMetadataDAO() {\n            return mock(MetadataDAO.class);\n        }\n\n        @Bean\n        public Map<String, TaskMapper> taskMapperMap(Collection<TaskMapper> taskMappers) {\n            return taskMappers.stream()\n                    .collect(Collectors.toMap(TaskMapper::getTaskType, Function.identity()));\n        }\n\n        @Bean\n        public ParametersUtils parametersUtils(ObjectMapper mapper) {\n            return new ParametersUtils(mapper);\n        }\n\n        @Bean\n        public IDGenerator idGenerator() {\n            return new IDGenerator();\n        }\n    }\n\n    private DeciderService deciderService;\n\n    private ExternalPayloadStorageUtils externalPayloadStorageUtils;\n    private static Registry registry;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Autowired private SystemTaskRegistry systemTaskRegistry;\n\n    @Autowired\n    @Qualifier(\"taskMapperMap\")\n    private Map<String, TaskMapper> taskMappers;\n\n    @Autowired private ParametersUtils parametersUtils;\n\n    @Autowired private MetadataDAO metadataDAO;\n\n    @Rule public ExpectedException exception = ExpectedException.none();\n\n    @BeforeClass\n    public static void init() {\n        registry = new DefaultRegistry();\n        Spectator.globalRegistry().add(registry);\n    }\n\n    @Before\n    public void setup() {\n        externalPayloadStorageUtils = mock(ExternalPayloadStorageUtils.class);\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"TestDeciderService\");\n        workflowDef.setVersion(1);\n        TaskDef taskDef = new TaskDef();\n        when(metadataDAO.getTaskDef(any())).thenReturn(taskDef);\n        when(metadataDAO.getLatestWorkflowDef(any())).thenReturn(Optional.of(workflowDef));\n\n        deciderService =\n                new DeciderService(\n                        new IDGenerator(),\n                        parametersUtils,\n                        metadataDAO,\n                        externalPayloadStorageUtils,\n                        systemTaskRegistry,\n                        taskMappers,\n                        Duration.ofMinutes(60));\n    }\n\n    @Test\n    public void testGetTaskInputV2() {\n        WorkflowModel workflow = createDefaultWorkflow();\n\n        workflow.getWorkflowDefinition().setSchemaVersion(2);\n\n        Map<String, Object> inputParams = new HashMap<>();\n        inputParams.put(\"workflowInputParam\", \"${workflow.input.requestId}\");\n        inputParams.put(\"taskOutputParam\", \"${task2.output.location}\");\n        inputParams.put(\"taskOutputParam2\", \"${task2.output.locationBad}\");\n        inputParams.put(\"taskOutputParam3\", \"${task3.output.location}\");\n        inputParams.put(\"constParam\", \"Some String value\");\n        inputParams.put(\"nullValue\", null);\n        inputParams.put(\"task2Status\", \"${task2.status}\");\n        inputParams.put(\"channelMap\", \"${workflow.input.channelMapping}\");\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInput(inputParams, workflow, null, null);\n\n        assertNotNull(taskInput);\n        assertTrue(taskInput.containsKey(\"workflowInputParam\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam2\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam3\"));\n        assertNull(taskInput.get(\"taskOutputParam2\"));\n\n        assertNotNull(taskInput.get(\"channelMap\"));\n        assertEquals(5, taskInput.get(\"channelMap\"));\n\n        assertEquals(\"request id 001\", taskInput.get(\"workflowInputParam\"));\n        assertEquals(\"http://location\", taskInput.get(\"taskOutputParam\"));\n        assertNull(taskInput.get(\"taskOutputParam3\"));\n        assertNull(taskInput.get(\"nullValue\"));\n        assertEquals(\n                workflow.getTasks().get(0).getStatus().name(),\n                taskInput.get(\"task2Status\")); // task2 and task3 are the tasks respectively\n    }\n\n    @Test\n    public void testGetTaskInputV2Partial() {\n        WorkflowModel workflow = createDefaultWorkflow();\n        System.setProperty(\"EC2_INSTANCE\", \"i-123abcdef990\");\n        workflow.getWorkflowDefinition().setSchemaVersion(2);\n\n        Map<String, Object> inputParams = new HashMap<>();\n        inputParams.put(\"workflowInputParam\", \"${workflow.input.requestId}\");\n        inputParams.put(\"workfowOutputParam\", \"${workflow.output.name}\");\n        inputParams.put(\"taskOutputParam\", \"${task2.output.location}\");\n        inputParams.put(\"taskOutputParam2\", \"${task2.output.locationBad}\");\n        inputParams.put(\"taskOutputParam3\", \"${task3.output.location}\");\n        inputParams.put(\"constParam\", \"Some String value   &\");\n        inputParams.put(\"partial\", \"${task2.output.location}/something?host=${EC2_INSTANCE}\");\n        inputParams.put(\"jsonPathExtracted\", \"${workflow.output.names[*].year}\");\n        inputParams.put(\"secondName\", \"${workflow.output.names[1].name}\");\n        inputParams.put(\n                \"concatenatedName\",\n                \"The Band is: ${workflow.output.names[1].name}-\\t${EC2_INSTANCE}\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.getInputTemplate().put(\"opname\", \"${workflow.output.name}\");\n        List<Object> listParams = new LinkedList<>();\n        List<Object> listParams2 = new LinkedList<>();\n        listParams2.add(\"${workflow.input.requestId}-10-${EC2_INSTANCE}\");\n        listParams.add(listParams2);\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"name\", \"${workflow.output.names[0].name}\");\n        map.put(\"hasAwards\", \"${workflow.input.hasAwards}\");\n        listParams.add(map);\n        taskDef.getInputTemplate().put(\"listValues\", listParams);\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInput(inputParams, workflow, taskDef, null);\n\n        assertNotNull(taskInput);\n        assertTrue(taskInput.containsKey(\"workflowInputParam\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam2\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam3\"));\n        assertNull(taskInput.get(\"taskOutputParam2\"));\n        assertNotNull(taskInput.get(\"jsonPathExtracted\"));\n        assertTrue(taskInput.get(\"jsonPathExtracted\") instanceof List);\n        assertNotNull(taskInput.get(\"secondName\"));\n        assertTrue(taskInput.get(\"secondName\") instanceof String);\n        assertEquals(\"The Doors\", taskInput.get(\"secondName\"));\n        assertEquals(\"The Band is: The Doors-\\ti-123abcdef990\", taskInput.get(\"concatenatedName\"));\n\n        assertEquals(\"request id 001\", taskInput.get(\"workflowInputParam\"));\n        assertEquals(\"http://location\", taskInput.get(\"taskOutputParam\"));\n        assertNull(taskInput.get(\"taskOutputParam3\"));\n        assertNotNull(taskInput.get(\"partial\"));\n        assertEquals(\"http://location/something?host=i-123abcdef990\", taskInput.get(\"partial\"));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testGetTaskInput() {\n        Map<String, Object> ip = new HashMap<>();\n        ip.put(\"workflowInputParam\", \"${workflow.input.requestId}\");\n        ip.put(\"taskOutputParam\", \"${task2.output.location}\");\n        List<Map<String, Object>> json = new LinkedList<>();\n        Map<String, Object> m1 = new HashMap<>();\n        m1.put(\"name\", \"person name\");\n        m1.put(\"city\", \"New York\");\n        m1.put(\"phone\", 2120001234);\n        m1.put(\"status\", \"${task2.output.isPersonActive}\");\n\n        Map<String, Object> m2 = new HashMap<>();\n        m2.put(\"employer\", \"City Of New York\");\n        m2.put(\"color\", \"purple\");\n        m2.put(\"requestId\", \"${workflow.input.requestId}\");\n\n        json.add(m1);\n        json.add(m2);\n        ip.put(\"complexJson\", json);\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"testGetTaskInput\");\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.getInput().put(\"requestId\", \"request id 001\");\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(\"task2\");\n        task.addOutput(\"location\", \"http://location\");\n        task.addOutput(\"isPersonActive\", true);\n        workflow.getTasks().add(task);\n        Map<String, Object> taskInput = parametersUtils.getTaskInput(ip, workflow, null, null);\n\n        assertNotNull(taskInput);\n        assertTrue(taskInput.containsKey(\"workflowInputParam\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam\"));\n        assertEquals(\"request id 001\", taskInput.get(\"workflowInputParam\"));\n        assertEquals(\"http://location\", taskInput.get(\"taskOutputParam\"));\n        assertNotNull(taskInput.get(\"complexJson\"));\n        assertTrue(taskInput.get(\"complexJson\") instanceof List);\n\n        List<Map<String, Object>> resolvedInput =\n                (List<Map<String, Object>>) taskInput.get(\"complexJson\");\n        assertEquals(2, resolvedInput.size());\n    }\n\n    @Test\n    public void testGetTaskInputV1() {\n        Map<String, Object> ip = new HashMap<>();\n        ip.put(\"workflowInputParam\", \"workflow.input.requestId\");\n        ip.put(\"taskOutputParam\", \"task2.output.location\");\n\n        WorkflowDef def = new WorkflowDef();\n        def.setSchemaVersion(1);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n\n        workflow.getInput().put(\"requestId\", \"request id 001\");\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(\"task2\");\n        task.addOutput(\"location\", \"http://location\");\n        task.addOutput(\"isPersonActive\", true);\n        workflow.getTasks().add(task);\n        Map<String, Object> taskInput = parametersUtils.getTaskInput(ip, workflow, null, null);\n\n        assertNotNull(taskInput);\n        assertTrue(taskInput.containsKey(\"workflowInputParam\"));\n        assertTrue(taskInput.containsKey(\"taskOutputParam\"));\n        assertEquals(\"request id 001\", taskInput.get(\"workflowInputParam\"));\n        assertEquals(\"http://location\", taskInput.get(\"taskOutputParam\"));\n    }\n\n    @Test\n    public void testGetTaskInputV2WithInputTemplate() {\n        TaskDef def = new TaskDef();\n        Map<String, Object> inputTemplate = new HashMap<>();\n        inputTemplate.put(\"url\", \"https://some_url:7004\");\n        inputTemplate.put(\"default_url\", \"https://default_url:7004\");\n        inputTemplate.put(\"someKey\", \"someValue\");\n\n        def.getInputTemplate().putAll(inputTemplate);\n\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\"some_new_url\", \"https://some_new_url:7004\");\n        workflowInput.put(\"workflow_input_url\", \"https://workflow_input_url:7004\");\n        workflowInput.put(\"some_other_key\", \"some_other_value\");\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testGetTaskInputV2WithInputTemplate\");\n        workflowDef.setVersion(1);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setInput(workflowInput);\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.getInputParameters().put(\"url\", \"${workflow.input.some_new_url}\");\n        workflowTask\n                .getInputParameters()\n                .put(\"workflow_input_url\", \"${workflow.input.workflow_input_url}\");\n        workflowTask.getInputParameters().put(\"someKey\", \"${workflow.input.someKey}\");\n        workflowTask.getInputParameters().put(\"someOtherKey\", \"${workflow.input.some_other_key}\");\n        workflowTask\n                .getInputParameters()\n                .put(\"someNowhereToBeFoundKey\", \"${workflow.input.some_ne_key}\");\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInputV2(\n                        workflowTask.getInputParameters(), workflow, null, def);\n        assertTrue(taskInput.containsKey(\"url\"));\n        assertTrue(taskInput.containsKey(\"default_url\"));\n        assertEquals(taskInput.get(\"url\"), \"https://some_new_url:7004\");\n        assertEquals(taskInput.get(\"default_url\"), \"https://default_url:7004\");\n        assertEquals(taskInput.get(\"workflow_input_url\"), \"https://workflow_input_url:7004\");\n        assertEquals(\"some_other_value\", taskInput.get(\"someOtherKey\"));\n        assertEquals(\"someValue\", taskInput.get(\"someKey\"));\n        assertNull(taskInput.get(\"someNowhereToBeFoundKey\"));\n    }\n\n    @Test\n    public void testGetNextTask() {\n\n        WorkflowDef def = createNestedWorkflow();\n        WorkflowTask firstTask = def.getTasks().get(0);\n        assertNotNull(firstTask);\n        assertEquals(\"fork1\", firstTask.getTaskReferenceName());\n        WorkflowTask nextAfterFirst = def.getNextTask(firstTask.getTaskReferenceName());\n        assertNotNull(nextAfterFirst);\n        assertEquals(\"join1\", nextAfterFirst.getTaskReferenceName());\n\n        WorkflowTask fork2 = def.getTaskByRefName(\"fork2\");\n        assertNotNull(fork2);\n        assertEquals(\"fork2\", fork2.getTaskReferenceName());\n\n        WorkflowTask taskAfterFork2 = def.getNextTask(\"fork2\");\n        assertNotNull(taskAfterFork2);\n        assertEquals(\"join2\", taskAfterFork2.getTaskReferenceName());\n\n        WorkflowTask t2 = def.getTaskByRefName(\"t2\");\n        assertNotNull(t2);\n        assertEquals(\"t2\", t2.getTaskReferenceName());\n\n        WorkflowTask taskAfterT2 = def.getNextTask(\"t2\");\n        assertNotNull(taskAfterT2);\n        assertEquals(\"t4\", taskAfterT2.getTaskReferenceName());\n\n        WorkflowTask taskAfterT3 = def.getNextTask(\"t3\");\n        assertNotNull(taskAfterT3);\n        assertEquals(DECISION.name(), taskAfterT3.getType());\n        assertEquals(\"d1\", taskAfterT3.getTaskReferenceName());\n\n        WorkflowTask taskAfterT4 = def.getNextTask(\"t4\");\n        assertNotNull(taskAfterT4);\n        assertEquals(\"join2\", taskAfterT4.getTaskReferenceName());\n\n        WorkflowTask taskAfterT6 = def.getNextTask(\"t6\");\n        assertNotNull(taskAfterT6);\n        assertEquals(\"t9\", taskAfterT6.getTaskReferenceName());\n\n        WorkflowTask taskAfterJoin2 = def.getNextTask(\"join2\");\n        assertNotNull(taskAfterJoin2);\n        assertEquals(\"join1\", taskAfterJoin2.getTaskReferenceName());\n\n        WorkflowTask taskAfterJoin1 = def.getNextTask(\"join1\");\n        assertNotNull(taskAfterJoin1);\n        assertEquals(\"t5\", taskAfterJoin1.getTaskReferenceName());\n\n        WorkflowTask taskAfterSubWF = def.getNextTask(\"sw1\");\n        assertNotNull(taskAfterSubWF);\n        assertEquals(\"join1\", taskAfterSubWF.getTaskReferenceName());\n\n        WorkflowTask taskAfterT9 = def.getNextTask(\"t9\");\n        assertNotNull(taskAfterT9);\n        assertEquals(\"join2\", taskAfterT9.getTaskReferenceName());\n    }\n\n    @Test\n    public void testCaseStatement() {\n\n        WorkflowDef def = createConditionalWF();\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setCreateTime(0L);\n        workflow.setWorkflowId(\"a\");\n        workflow.setCorrelationId(\"b\");\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        List<TaskModel> scheduledTasks = outcome.tasksToBeScheduled;\n        assertNotNull(scheduledTasks);\n        assertEquals(2, scheduledTasks.size());\n        assertEquals(TaskModel.Status.IN_PROGRESS, scheduledTasks.get(0).getStatus());\n        assertEquals(TaskModel.Status.SCHEDULED, scheduledTasks.get(1).getStatus());\n    }\n\n    @Test\n    public void testGetTaskByRef() {\n        WorkflowModel workflow = new WorkflowModel();\n        TaskModel t1 = new TaskModel();\n        t1.setReferenceTaskName(\"ref\");\n        t1.setSeq(0);\n        t1.setStatus(TaskModel.Status.TIMED_OUT);\n\n        TaskModel t2 = new TaskModel();\n        t2.setReferenceTaskName(\"ref\");\n        t2.setSeq(1);\n        t2.setStatus(TaskModel.Status.FAILED);\n\n        TaskModel t3 = new TaskModel();\n        t3.setReferenceTaskName(\"ref\");\n        t3.setSeq(2);\n        t3.setStatus(TaskModel.Status.COMPLETED);\n\n        workflow.getTasks().add(t1);\n        workflow.getTasks().add(t2);\n        workflow.getTasks().add(t3);\n\n        TaskModel task = workflow.getTaskByRefName(\"ref\");\n        assertNotNull(task);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertEquals(t3.getSeq(), task.getSeq());\n    }\n\n    @Test\n    public void testTaskTimeout() {\n        Counter counter =\n                registry.counter(\"task_timeout\", \"class\", \"WorkflowMonitor\", \"taskType\", \"test\");\n        long counterCount = counter.count();\n\n        TaskDef taskType = new TaskDef();\n        taskType.setName(\"test\");\n        taskType.setTimeoutPolicy(TimeoutPolicy.RETRY);\n        taskType.setTimeoutSeconds(1);\n\n        TaskModel task = new TaskModel();\n        task.setTaskType(taskType.getName());\n        task.setStartTime(System.currentTimeMillis() - 2_000); // 2 seconds ago!\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        deciderService.checkTaskTimeout(taskType, task);\n\n        // Task should be marked as timed out\n        assertEquals(TaskModel.Status.TIMED_OUT, task.getStatus());\n        assertNotNull(task.getReasonForIncompletion());\n        assertEquals(++counterCount, counter.count());\n\n        taskType.setTimeoutPolicy(TimeoutPolicy.ALERT_ONLY);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.setReasonForIncompletion(null);\n        deciderService.checkTaskTimeout(taskType, task);\n\n        // Nothing will happen\n        assertEquals(TaskModel.Status.IN_PROGRESS, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n        assertEquals(++counterCount, counter.count());\n\n        boolean exception = false;\n        taskType.setTimeoutPolicy(TimeoutPolicy.TIME_OUT_WF);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.setReasonForIncompletion(null);\n\n        try {\n            deciderService.checkTaskTimeout(taskType, task);\n        } catch (TerminateWorkflowException tw) {\n            exception = true;\n        }\n        assertTrue(exception);\n        assertEquals(TaskModel.Status.TIMED_OUT, task.getStatus());\n        assertNotNull(task.getReasonForIncompletion());\n        assertEquals(++counterCount, counter.count());\n\n        taskType.setTimeoutPolicy(TimeoutPolicy.TIME_OUT_WF);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.setReasonForIncompletion(null);\n        deciderService.checkTaskTimeout(null, task); // this will be a no-op\n\n        assertEquals(TaskModel.Status.IN_PROGRESS, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n        assertEquals(counterCount, counter.count());\n    }\n\n    @Test\n    public void testCheckTaskPollTimeout() {\n        Counter counter =\n                registry.counter(\"task_timeout\", \"class\", \"WorkflowMonitor\", \"taskType\", \"test\");\n        long counterCount = counter.count();\n\n        TaskDef taskType = new TaskDef();\n        taskType.setName(\"test\");\n        taskType.setTimeoutPolicy(TimeoutPolicy.RETRY);\n        taskType.setPollTimeoutSeconds(1);\n\n        TaskModel task = new TaskModel();\n        task.setTaskType(taskType.getName());\n        task.setScheduledTime(System.currentTimeMillis() - 2_000);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        deciderService.checkTaskPollTimeout(taskType, task);\n\n        assertEquals(++counterCount, counter.count());\n        assertEquals(TaskModel.Status.TIMED_OUT, task.getStatus());\n        assertNotNull(task.getReasonForIncompletion());\n\n        task.setScheduledTime(System.currentTimeMillis());\n        task.setReasonForIncompletion(null);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        deciderService.checkTaskPollTimeout(taskType, task);\n\n        assertEquals(counterCount, counter.count());\n        assertEquals(TaskModel.Status.SCHEDULED, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testConcurrentTaskInputCalc() throws InterruptedException {\n        TaskDef def = new TaskDef();\n\n        Map<String, Object> inputMap = new HashMap<>();\n        inputMap.put(\"path\", \"${workflow.input.inputLocation}\");\n        inputMap.put(\"type\", \"${workflow.input.sourceType}\");\n        inputMap.put(\"channelMapping\", \"${workflow.input.channelMapping}\");\n\n        List<Map<String, Object>> input = new LinkedList<>();\n        input.add(inputMap);\n\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input\", input);\n\n        def.getInputTemplate().putAll(body);\n\n        ExecutorService executorService = Executors.newFixedThreadPool(10);\n        final int[] result = new int[10];\n        CountDownLatch latch = new CountDownLatch(10);\n\n        for (int i = 0; i < 10; i++) {\n            final int x = i;\n            executorService.submit(\n                    () -> {\n                        try {\n                            Map<String, Object> workflowInput = new HashMap<>();\n                            workflowInput.put(\"outputLocation\", \"baggins://outputlocation/\" + x);\n                            workflowInput.put(\"inputLocation\", \"baggins://inputlocation/\" + x);\n                            workflowInput.put(\"sourceType\", \"MuxedSource\");\n                            workflowInput.put(\"channelMapping\", x);\n\n                            WorkflowDef workflowDef = new WorkflowDef();\n                            workflowDef.setName(\"testConcurrentTaskInputCalc\");\n                            workflowDef.setVersion(1);\n\n                            WorkflowModel workflow = new WorkflowModel();\n                            workflow.setWorkflowDefinition(workflowDef);\n                            workflow.setInput(workflowInput);\n\n                            Map<String, Object> taskInput =\n                                    parametersUtils.getTaskInputV2(\n                                            new HashMap<>(), workflow, null, def);\n\n                            Object reqInputObj = taskInput.get(\"input\");\n                            assertNotNull(reqInputObj);\n                            assertTrue(reqInputObj instanceof List);\n                            List<Map<String, Object>> reqInput =\n                                    (List<Map<String, Object>>) reqInputObj;\n\n                            Object cmObj = reqInput.get(0).get(\"channelMapping\");\n                            assertNotNull(cmObj);\n                            if (!(cmObj instanceof Number)) {\n                                result[x] = -1;\n                            } else {\n                                Number channelMapping = (Number) cmObj;\n                                result[x] = channelMapping.intValue();\n                            }\n\n                            latch.countDown();\n                        } catch (Exception e) {\n                            e.printStackTrace();\n                        }\n                    });\n        }\n        latch.await(1, TimeUnit.MINUTES);\n        if (latch.getCount() > 0) {\n            fail(\n                    \"Executions did not complete in a minute.  Something wrong with the build server?\");\n        }\n        executorService.shutdownNow();\n        for (int i = 0; i < result.length; i++) {\n            assertEquals(i, result[i]);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testTaskRetry() {\n        WorkflowModel workflow = createDefaultWorkflow();\n\n        workflow.getWorkflowDefinition().setSchemaVersion(2);\n\n        Map<String, Object> inputParams = new HashMap<>();\n        inputParams.put(\"workflowInputParam\", \"${workflow.input.requestId}\");\n        inputParams.put(\"taskOutputParam\", \"${task2.output.location}\");\n        inputParams.put(\"constParam\", \"Some String value\");\n        inputParams.put(\"nullValue\", null);\n        inputParams.put(\"task2Status\", \"${task2.status}\");\n        inputParams.put(\"null\", null);\n        inputParams.put(\"task_id\", \"${CPEWF_TASK_ID}\");\n\n        Map<String, Object> env = new HashMap<>();\n        env.put(\"env_task_id\", \"${CPEWF_TASK_ID}\");\n        inputParams.put(\"env\", env);\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInput(inputParams, workflow, null, \"t1\");\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(taskInput);\n        task.setStatus(TaskModel.Status.FAILED);\n        task.setTaskId(\"t1\");\n\n        TaskDef taskDef = new TaskDef();\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.getInputParameters().put(\"task_id\", \"${CPEWF_TASK_ID}\");\n        workflowTask.getInputParameters().put(\"env\", env);\n\n        Optional<TaskModel> task2 = deciderService.retry(taskDef, workflowTask, task, workflow);\n        assertEquals(\"t1\", task.getInputData().get(\"task_id\"));\n        assertEquals(\n                \"t1\", ((Map<String, Object>) task.getInputData().get(\"env\")).get(\"env_task_id\"));\n\n        assertNotSame(task.getTaskId(), task2.get().getTaskId());\n        assertEquals(task2.get().getTaskId(), task2.get().getInputData().get(\"task_id\"));\n        assertEquals(\n                task2.get().getTaskId(),\n                ((Map<String, Object>) task2.get().getInputData().get(\"env\")).get(\"env_task_id\"));\n\n        TaskModel task3 = new TaskModel();\n        task3.getInputData().putAll(taskInput);\n        task3.setStatus(TaskModel.Status.FAILED_WITH_TERMINAL_ERROR);\n        task3.setTaskId(\"t1\");\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n        exception.expect(TerminateWorkflowException.class);\n        deciderService.retry(taskDef, workflowTask, task3, workflow);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testWorkflowTaskRetry() {\n        WorkflowModel workflow = createDefaultWorkflow();\n\n        workflow.getWorkflowDefinition().setSchemaVersion(2);\n\n        Map<String, Object> inputParams = new HashMap<>();\n        inputParams.put(\"workflowInputParam\", \"${workflow.input.requestId}\");\n        inputParams.put(\"taskOutputParam\", \"${task2.output.location}\");\n        inputParams.put(\"constParam\", \"Some String value\");\n        inputParams.put(\"nullValue\", null);\n        inputParams.put(\"task2Status\", \"${task2.status}\");\n        inputParams.put(\"null\", null);\n        inputParams.put(\"task_id\", \"${CPEWF_TASK_ID}\");\n\n        Map<String, Object> env = new HashMap<>();\n        env.put(\"env_task_id\", \"${CPEWF_TASK_ID}\");\n        inputParams.put(\"env\", env);\n\n        Map<String, Object> taskInput =\n                parametersUtils.getTaskInput(inputParams, workflow, null, \"t1\");\n\n        // Create a first failed task\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(taskInput);\n        task.setStatus(TaskModel.Status.FAILED);\n        task.setTaskId(\"t1\");\n\n        TaskDef taskDef = new TaskDef();\n        assertEquals(3, taskDef.getRetryCount());\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.getInputParameters().put(\"task_id\", \"${CPEWF_TASK_ID}\");\n        workflowTask.getInputParameters().put(\"env\", env);\n        workflowTask.setRetryCount(1);\n\n        // Retry the failed task and assert that a new one has been created\n        Optional<TaskModel> task2 = deciderService.retry(taskDef, workflowTask, task, workflow);\n        assertEquals(\"t1\", task.getInputData().get(\"task_id\"));\n        assertEquals(\n                \"t1\", ((Map<String, Object>) task.getInputData().get(\"env\")).get(\"env_task_id\"));\n\n        assertNotSame(task.getTaskId(), task2.get().getTaskId());\n        assertEquals(task2.get().getTaskId(), task2.get().getInputData().get(\"task_id\"));\n        assertEquals(\n                task2.get().getTaskId(),\n                ((Map<String, Object>) task2.get().getInputData().get(\"env\")).get(\"env_task_id\"));\n\n        // Set the retried task to FAILED, retry it again and assert that the workflow failed\n        task2.get().setStatus(TaskModel.Status.FAILED);\n        exception.expect(TerminateWorkflowException.class);\n        final Optional<TaskModel> task3 =\n                deciderService.retry(taskDef, workflowTask, task2.get(), workflow);\n\n        assertFalse(task3.isPresent());\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getStatus());\n    }\n\n    @Test\n    public void testLinearBackoff() {\n        WorkflowModel workflow = createDefaultWorkflow();\n\n        TaskModel task = new TaskModel();\n        task.setStatus(TaskModel.Status.FAILED);\n        task.setTaskId(\"t1\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setRetryDelaySeconds(60);\n        taskDef.setRetryLogic(TaskDef.RetryLogic.LINEAR_BACKOFF);\n        taskDef.setBackoffScaleFactor(2);\n        WorkflowTask workflowTask = new WorkflowTask();\n\n        Optional<TaskModel> task2 = deciderService.retry(taskDef, workflowTask, task, workflow);\n        assertEquals(120, task2.get().getCallbackAfterSeconds()); // 60*2*1\n\n        Optional<TaskModel> task3 =\n                deciderService.retry(taskDef, workflowTask, task2.get(), workflow);\n        assertEquals(240, task3.get().getCallbackAfterSeconds()); // 60*2*2\n\n        Optional<TaskModel> task4 =\n                deciderService.retry(taskDef, workflowTask, task3.get(), workflow);\n        // // 60*2*3\n        assertEquals(360, task4.get().getCallbackAfterSeconds()); // 60*2*3\n\n        taskDef.setRetryCount(Integer.MAX_VALUE);\n        task4.get().setRetryCount(Integer.MAX_VALUE - 100);\n        Optional<TaskModel> task5 =\n                deciderService.retry(taskDef, workflowTask, task4.get(), workflow);\n        assertEquals(Integer.MAX_VALUE, task5.get().getCallbackAfterSeconds());\n    }\n\n    @Test\n    public void testExponentialBackoff() {\n        WorkflowModel workflow = createDefaultWorkflow();\n\n        TaskModel task = new TaskModel();\n        task.setStatus(TaskModel.Status.FAILED);\n        task.setTaskId(\"t1\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setRetryDelaySeconds(60);\n        taskDef.setRetryLogic(TaskDef.RetryLogic.EXPONENTIAL_BACKOFF);\n        WorkflowTask workflowTask = new WorkflowTask();\n\n        Optional<TaskModel> task2 = deciderService.retry(taskDef, workflowTask, task, workflow);\n        assertEquals(60, task2.get().getCallbackAfterSeconds());\n\n        Optional<TaskModel> task3 =\n                deciderService.retry(taskDef, workflowTask, task2.get(), workflow);\n        assertEquals(120, task3.get().getCallbackAfterSeconds());\n\n        Optional<TaskModel> task4 =\n                deciderService.retry(taskDef, workflowTask, task3.get(), workflow);\n        assertEquals(240, task4.get().getCallbackAfterSeconds());\n\n        taskDef.setRetryCount(Integer.MAX_VALUE);\n        task4.get().setRetryCount(Integer.MAX_VALUE - 100);\n        Optional<TaskModel> task5 =\n                deciderService.retry(taskDef, workflowTask, task4.get(), workflow);\n        assertEquals(Integer.MAX_VALUE, task5.get().getCallbackAfterSeconds());\n    }\n\n    @Test\n    public void testFork() throws IOException {\n        InputStream stream = TestDeciderService.class.getResourceAsStream(\"/test.json\");\n        WorkflowModel workflow = objectMapper.readValue(stream, WorkflowModel.class);\n\n        DeciderOutcome outcome = deciderService.decide(workflow);\n        assertFalse(outcome.isComplete);\n        assertEquals(5, outcome.tasksToBeScheduled.size());\n        assertEquals(1, outcome.tasksToBeUpdated.size());\n    }\n\n    @Test\n    public void testDecideSuccessfulWorkflow() {\n        WorkflowDef workflowDef = createLinearWorkflow();\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(\"junit_task_l1\");\n        task1.setReferenceTaskName(\"s1\");\n        task1.setSeq(1);\n        task1.setRetried(false);\n        task1.setExecuted(false);\n        task1.setStatus(TaskModel.Status.COMPLETED);\n\n        workflow.getTasks().add(task1);\n\n        DeciderOutcome deciderOutcome = deciderService.decide(workflow);\n        assertNotNull(deciderOutcome);\n\n        assertFalse(workflow.getTaskByRefName(\"s1\").isRetried());\n        assertEquals(1, deciderOutcome.tasksToBeUpdated.size());\n        assertEquals(\"s1\", deciderOutcome.tasksToBeUpdated.get(0).getReferenceTaskName());\n        assertEquals(1, deciderOutcome.tasksToBeScheduled.size());\n        assertEquals(\"s2\", deciderOutcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n        assertFalse(deciderOutcome.isComplete);\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(\"junit_task_l2\");\n        task2.setReferenceTaskName(\"s2\");\n        task2.setSeq(2);\n        task2.setRetried(false);\n        task2.setExecuted(false);\n        task2.setStatus(TaskModel.Status.COMPLETED);\n        workflow.getTasks().add(task2);\n\n        deciderOutcome = deciderService.decide(workflow);\n        assertNotNull(deciderOutcome);\n        assertTrue(workflow.getTaskByRefName(\"s2\").isExecuted());\n        assertFalse(workflow.getTaskByRefName(\"s2\").isRetried());\n        assertEquals(1, deciderOutcome.tasksToBeUpdated.size());\n        assertEquals(\"s2\", deciderOutcome.tasksToBeUpdated.get(0).getReferenceTaskName());\n        assertEquals(0, deciderOutcome.tasksToBeScheduled.size());\n        assertTrue(deciderOutcome.isComplete);\n    }\n\n    @Test\n    public void testDecideWithLoopTask() {\n        WorkflowDef workflowDef = createLinearWorkflow();\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(\"junit_task_l1\");\n        task1.setReferenceTaskName(\"s1\");\n        task1.setSeq(1);\n        task1.setIteration(1);\n        task1.setRetried(false);\n        task1.setExecuted(false);\n        task1.setStatus(TaskModel.Status.COMPLETED);\n\n        workflow.getTasks().add(task1);\n\n        DeciderOutcome deciderOutcome = deciderService.decide(workflow);\n        assertNotNull(deciderOutcome);\n\n        assertFalse(workflow.getTaskByRefName(\"s1\").isRetried());\n        assertEquals(1, deciderOutcome.tasksToBeUpdated.size());\n        assertEquals(\"s1\", deciderOutcome.tasksToBeUpdated.get(0).getReferenceTaskName());\n        assertEquals(1, deciderOutcome.tasksToBeScheduled.size());\n        assertEquals(\"s2__1\", deciderOutcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n        assertFalse(deciderOutcome.isComplete);\n    }\n\n    @Test\n    public void testDecideFailedTask() {\n        WorkflowDef workflowDef = createLinearWorkflow();\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n\n        TaskModel task = new TaskModel();\n        task.setTaskType(\"junit_task_l1\");\n        task.setReferenceTaskName(\"s1\");\n        task.setSeq(1);\n        task.setRetried(false);\n        task.setExecuted(false);\n        task.setStatus(TaskModel.Status.FAILED);\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setTaskReferenceName(\"s1\");\n        workflowTask.setName(\"junit_task_l1\");\n        workflowTask.setTaskDefinition(new TaskDef(\"junit_task_l1\"));\n        task.setWorkflowTask(workflowTask);\n\n        workflow.getTasks().add(task);\n\n        DeciderOutcome deciderOutcome = deciderService.decide(workflow);\n        assertNotNull(deciderOutcome);\n        assertFalse(workflow.getTaskByRefName(\"s1\").isExecuted());\n        assertTrue(workflow.getTaskByRefName(\"s1\").isRetried());\n        assertEquals(1, deciderOutcome.tasksToBeUpdated.size());\n        assertEquals(\"s1\", deciderOutcome.tasksToBeUpdated.get(0).getReferenceTaskName());\n        assertEquals(1, deciderOutcome.tasksToBeScheduled.size());\n        assertEquals(\"s1\", deciderOutcome.tasksToBeScheduled.get(0).getReferenceTaskName());\n        assertFalse(deciderOutcome.isComplete);\n    }\n\n    @Test\n    public void testGetTasksToBeScheduled() {\n        WorkflowDef workflowDef = createLinearWorkflow();\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n\n        WorkflowTask workflowTask1 = new WorkflowTask();\n        workflowTask1.setName(\"s1\");\n        workflowTask1.setTaskReferenceName(\"s1\");\n        workflowTask1.setType(SIMPLE.name());\n        workflowTask1.setTaskDefinition(new TaskDef(\"s1\"));\n\n        List<TaskModel> tasksToBeScheduled =\n                deciderService.getTasksToBeScheduled(workflow, workflowTask1, 0, null);\n        assertNotNull(tasksToBeScheduled);\n        assertEquals(1, tasksToBeScheduled.size());\n        assertEquals(\"s1\", tasksToBeScheduled.get(0).getReferenceTaskName());\n\n        WorkflowTask workflowTask2 = new WorkflowTask();\n        workflowTask2.setName(\"s2\");\n        workflowTask2.setTaskReferenceName(\"s2\");\n        workflowTask2.setType(SIMPLE.name());\n        workflowTask2.setTaskDefinition(new TaskDef(\"s2\"));\n        tasksToBeScheduled = deciderService.getTasksToBeScheduled(workflow, workflowTask2, 0, null);\n        assertNotNull(tasksToBeScheduled);\n        assertEquals(1, tasksToBeScheduled.size());\n        assertEquals(\"s2\", tasksToBeScheduled.get(0).getReferenceTaskName());\n    }\n\n    @Test\n    public void testIsResponseTimedOut() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test_rt\");\n        taskDef.setResponseTimeoutSeconds(10);\n\n        TaskModel task = new TaskModel();\n        task.setTaskDefName(\"test_rt\");\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.setTaskId(\"aa\");\n        task.setTaskType(TaskType.TASK_TYPE_SIMPLE);\n        task.setUpdateTime(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(11));\n\n        assertTrue(deciderService.isResponseTimedOut(taskDef, task));\n\n        // verify that sub workflow tasks are not response timed out\n        task.setTaskType(TaskType.TASK_TYPE_SUB_WORKFLOW);\n        assertFalse(deciderService.isResponseTimedOut(taskDef, task));\n\n        task.setTaskType(\"asyncCompleteSystemTask\");\n        assertFalse(deciderService.isResponseTimedOut(taskDef, task));\n    }\n\n    @Test\n    public void testFilterNextLoopOverTasks() {\n\n        WorkflowModel workflow = new WorkflowModel();\n\n        TaskModel task1 = new TaskModel();\n        task1.setReferenceTaskName(\"task1\");\n        task1.setStatus(TaskModel.Status.COMPLETED);\n        task1.setTaskId(\"task1\");\n        task1.setIteration(1);\n\n        TaskModel task2 = new TaskModel();\n        task2.setReferenceTaskName(\"task2\");\n        task2.setStatus(TaskModel.Status.SCHEDULED);\n        task2.setTaskId(\"task2\");\n\n        TaskModel task3 = new TaskModel();\n        task3.setReferenceTaskName(\"task3__1\");\n        task3.setStatus(TaskModel.Status.IN_PROGRESS);\n        task3.setTaskId(\"task3__1\");\n\n        TaskModel task4 = new TaskModel();\n        task4.setReferenceTaskName(\"task4\");\n        task4.setStatus(TaskModel.Status.SCHEDULED);\n        task4.setTaskId(\"task4\");\n\n        TaskModel task5 = new TaskModel();\n        task5.setReferenceTaskName(\"task5\");\n        task5.setStatus(TaskModel.Status.COMPLETED);\n        task5.setTaskId(\"task5\");\n\n        workflow.getTasks().addAll(Arrays.asList(task1, task2, task3, task4, task5));\n        List<TaskModel> tasks =\n                deciderService.filterNextLoopOverTasks(\n                        Arrays.asList(task2, task3, task4), task1, workflow);\n        assertEquals(2, tasks.size());\n        tasks.forEach(\n                task -> {\n                    assertTrue(\n                            task.getReferenceTaskName()\n                                    .endsWith(TaskUtils.getLoopOverTaskRefNameSuffix(1)));\n                    assertEquals(1, task.getIteration());\n                });\n    }\n\n    @Test\n    public void testUpdateWorkflowOutput() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(new WorkflowDef());\n        deciderService.updateWorkflowOutput(workflow, null);\n        assertNotNull(workflow.getOutput());\n        assertTrue(workflow.getOutput().isEmpty());\n        TaskModel task = new TaskModel();\n        Map<String, Object> taskOutput = new HashMap<>();\n        taskOutput.put(\"taskKey\", \"taskValue\");\n        task.setOutputData(taskOutput);\n        workflow.getTasks().add(task);\n        WorkflowDef workflowDef = new WorkflowDef();\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(workflowDef));\n        deciderService.updateWorkflowOutput(workflow, null);\n        assertNotNull(workflow.getOutput());\n        assertEquals(\"taskValue\", workflow.getOutput().get(\"taskKey\"));\n    }\n\n    // when workflow definition has outputParameters defined\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n    @Test\n    public void testUpdateWorkflowOutput_WhenDefinitionHasOutputParameters() {\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setOutputParameters(\n                new HashMap() {\n                    {\n                        put(\"workflowKey\", \"workflowValue\");\n                    }\n                });\n        workflow.setWorkflowDefinition(workflowDef);\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(\"test_task\");\n        task.setOutputData(\n                new HashMap() {\n                    {\n                        put(\"taskKey\", \"taskValue\");\n                    }\n                });\n        workflow.getTasks().add(task);\n        deciderService.updateWorkflowOutput(workflow, null);\n        assertNotNull(workflow.getOutput());\n        assertEquals(\"workflowValue\", workflow.getOutput().get(\"workflowKey\"));\n    }\n\n    @Test\n    public void testUpdateWorkflowOutput_WhenWorkflowHasTerminateTask() {\n        WorkflowModel workflow = new WorkflowModel();\n        TaskModel task = new TaskModel();\n        task.setTaskType(TASK_TYPE_TERMINATE);\n        task.setStatus(TaskModel.Status.COMPLETED);\n        task.setOutputData(\n                new HashMap<String, Object>() {\n                    {\n                        put(\"taskKey\", \"taskValue\");\n                    }\n                });\n        workflow.getTasks().add(task);\n        deciderService.updateWorkflowOutput(workflow, null);\n        assertNotNull(workflow.getOutput());\n        assertEquals(\"taskValue\", workflow.getOutput().get(\"taskKey\"));\n        verify(externalPayloadStorageUtils, never()).downloadPayload(anyString());\n\n        // when terminate task has output in external payload storage\n        String externalOutputPayloadStoragePath = \"/task/output/terminate.json\";\n        workflow.getTasks().get(0).setOutputData(null);\n        workflow.getTasks()\n                .get(0)\n                .setExternalOutputPayloadStoragePath(externalOutputPayloadStoragePath);\n        when(externalPayloadStorageUtils.downloadPayload(externalOutputPayloadStoragePath))\n                .thenReturn(\n                        new HashMap() {\n                            {\n                                put(\"taskKey\", \"taskValue\");\n                            }\n                        });\n        deciderService.updateWorkflowOutput(workflow, null);\n        assertNotNull(workflow.getOutput());\n        assertEquals(\"taskValue\", workflow.getOutput().get(\"taskKey\"));\n        verify(externalPayloadStorageUtils, times(1)).downloadPayload(anyString());\n    }\n\n    @Test\n    public void testCheckWorkflowTimeout() {\n        Counter counter =\n                registry.counter(\n                        \"workflow_failure\",\n                        \"class\",\n                        \"WorkflowMonitor\",\n                        \"workflowName\",\n                        \"test\",\n                        \"status\",\n                        \"TIMED_OUT\",\n                        \"ownerApp\",\n                        \"junit\");\n        long counterCount = counter.count();\n        assertEquals(0, counter.count());\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test\");\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setOwnerApp(\"junit\");\n        workflow.setCreateTime(System.currentTimeMillis() - 10_000);\n        workflow.setWorkflowId(\"workflow_id\");\n\n        // no-op\n        workflow.setWorkflowDefinition(null);\n        deciderService.checkWorkflowTimeout(workflow);\n\n        // no-op\n        workflow.setWorkflowDefinition(workflowDef);\n        deciderService.checkWorkflowTimeout(workflow);\n\n        // alert\n        workflowDef.setTimeoutPolicy(WorkflowDef.TimeoutPolicy.ALERT_ONLY);\n        workflowDef.setTimeoutSeconds(2);\n        workflow.setWorkflowDefinition(workflowDef);\n        deciderService.checkWorkflowTimeout(workflow);\n        assertEquals(++counterCount, counter.count());\n\n        // time out\n        workflowDef.setTimeoutPolicy(WorkflowDef.TimeoutPolicy.TIME_OUT_WF);\n        workflow.setWorkflowDefinition(workflowDef);\n        try {\n            deciderService.checkWorkflowTimeout(workflow);\n        } catch (TerminateWorkflowException twe) {\n            assertTrue(twe.getMessage().contains(\"Workflow timed out\"));\n        }\n\n        // for a retried workflow\n        workflow.setLastRetriedTime(System.currentTimeMillis() - 5_000);\n        try {\n            deciderService.checkWorkflowTimeout(workflow);\n        } catch (TerminateWorkflowException twe) {\n            assertTrue(twe.getMessage().contains(\"Workflow timed out\"));\n        }\n    }\n\n    @Test\n    public void testCheckForWorkflowCompletion() {\n        WorkflowDef conditionalWorkflowDef = createConditionalWF();\n        WorkflowTask terminateWT = new WorkflowTask();\n        terminateWT.setType(TaskType.TERMINATE.name());\n        terminateWT.setTaskReferenceName(\"terminate\");\n        terminateWT.setName(\"terminate\");\n        terminateWT.getInputParameters().put(\"terminationStatus\", \"COMPLETED\");\n        conditionalWorkflowDef.getTasks().add(terminateWT);\n\n        // when workflow has no tasks\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(conditionalWorkflowDef);\n\n        // then workflow completion check returns false\n        assertFalse(deciderService.checkForWorkflowCompletion(workflow));\n\n        // when only part of the tasks are completed\n        TaskModel decTask = new TaskModel();\n        decTask.setTaskType(DECISION.name());\n        decTask.setReferenceTaskName(\"conditional2\");\n        decTask.setStatus(TaskModel.Status.COMPLETED);\n\n        TaskModel task1 = new TaskModel();\n        decTask.setTaskType(SIMPLE.name());\n        task1.setReferenceTaskName(\"t1\");\n        task1.setStatus(TaskModel.Status.COMPLETED);\n\n        workflow.getTasks().addAll(Arrays.asList(decTask, task1));\n\n        // then workflow completion check returns false\n        assertFalse(deciderService.checkForWorkflowCompletion(workflow));\n\n        // when the terminate task is COMPLETED\n        TaskModel task2 = new TaskModel();\n        decTask.setTaskType(SIMPLE.name());\n        task2.setReferenceTaskName(\"t2\");\n        task2.setStatus(TaskModel.Status.SCHEDULED);\n\n        TaskModel terminateTask = new TaskModel();\n        decTask.setTaskType(TaskType.TERMINATE.name());\n        terminateTask.setReferenceTaskName(\"terminate\");\n        terminateTask.setStatus(TaskModel.Status.COMPLETED);\n\n        workflow.getTasks().addAll(Arrays.asList(task2, terminateTask));\n\n        // then the workflow completion check returns true\n        assertTrue(deciderService.checkForWorkflowCompletion(workflow));\n    }\n\n    private WorkflowDef createConditionalWF() {\n\n        WorkflowTask workflowTask1 = new WorkflowTask();\n        workflowTask1.setName(\"junit_task_1\");\n        Map<String, Object> inputParams1 = new HashMap<>();\n        inputParams1.put(\"p1\", \"workflow.input.param1\");\n        inputParams1.put(\"p2\", \"workflow.input.param2\");\n        workflowTask1.setInputParameters(inputParams1);\n        workflowTask1.setTaskReferenceName(\"t1\");\n        workflowTask1.setTaskDefinition(new TaskDef(\"junit_task_1\"));\n\n        WorkflowTask workflowTask2 = new WorkflowTask();\n        workflowTask2.setName(\"junit_task_2\");\n        Map<String, Object> inputParams2 = new HashMap<>();\n        inputParams2.put(\"tp1\", \"workflow.input.param1\");\n        workflowTask2.setInputParameters(inputParams2);\n        workflowTask2.setTaskReferenceName(\"t2\");\n        workflowTask2.setTaskDefinition(new TaskDef(\"junit_task_2\"));\n\n        WorkflowTask workflowTask3 = new WorkflowTask();\n        workflowTask3.setName(\"junit_task_3\");\n        Map<String, Object> inputParams3 = new HashMap<>();\n        inputParams2.put(\"tp3\", \"workflow.input.param2\");\n        workflowTask3.setInputParameters(inputParams3);\n        workflowTask3.setTaskReferenceName(\"t3\");\n        workflowTask3.setTaskDefinition(new TaskDef(\"junit_task_3\"));\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"Conditional Workflow\");\n        workflowDef.setDescription(\"Conditional Workflow\");\n        workflowDef.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n\n        WorkflowTask decisionTask2 = new WorkflowTask();\n        decisionTask2.setType(DECISION.name());\n        decisionTask2.setCaseValueParam(\"case\");\n        decisionTask2.setName(\"conditional2\");\n        decisionTask2.setTaskReferenceName(\"conditional2\");\n        Map<String, List<WorkflowTask>> dc = new HashMap<>();\n        dc.put(\"one\", Arrays.asList(workflowTask1, workflowTask3));\n        dc.put(\"two\", Collections.singletonList(workflowTask2));\n        decisionTask2.setDecisionCases(dc);\n        decisionTask2.getInputParameters().put(\"case\", \"workflow.input.param2\");\n\n        WorkflowTask decisionTask = new WorkflowTask();\n        decisionTask.setType(DECISION.name());\n        decisionTask.setCaseValueParam(\"case\");\n        decisionTask.setName(\"conditional\");\n        decisionTask.setTaskReferenceName(\"conditional\");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"nested\", Collections.singletonList(decisionTask2));\n        decisionCases.put(\"three\", Collections.singletonList(workflowTask3));\n        decisionTask.setDecisionCases(decisionCases);\n        decisionTask.getInputParameters().put(\"case\", \"workflow.input.param1\");\n        decisionTask.getDefaultCase().add(workflowTask2);\n        workflowDef.getTasks().add(decisionTask);\n\n        WorkflowTask notifyTask = new WorkflowTask();\n        notifyTask.setName(\"junit_task_4\");\n        notifyTask.setTaskReferenceName(\"junit_task_4\");\n        notifyTask.setTaskDefinition(new TaskDef(\"junit_task_4\"));\n\n        WorkflowTask finalDecisionTask = new WorkflowTask();\n        finalDecisionTask.setName(\"finalcondition\");\n        finalDecisionTask.setTaskReferenceName(\"tf\");\n        finalDecisionTask.setType(DECISION.name());\n        finalDecisionTask.setCaseValueParam(\"finalCase\");\n        Map<String, Object> fi = new HashMap<>();\n        fi.put(\"finalCase\", \"workflow.input.finalCase\");\n        finalDecisionTask.setInputParameters(fi);\n        finalDecisionTask.getDecisionCases().put(\"notify\", Collections.singletonList(notifyTask));\n\n        workflowDef.getTasks().add(finalDecisionTask);\n        return workflowDef;\n    }\n\n    private WorkflowDef createLinearWorkflow() {\n\n        Map<String, Object> inputParams = new HashMap<>();\n        inputParams.put(\"p1\", \"workflow.input.param1\");\n        inputParams.put(\"p2\", \"workflow.input.param2\");\n\n        WorkflowTask workflowTask1 = new WorkflowTask();\n        workflowTask1.setName(\"junit_task_l1\");\n        workflowTask1.setInputParameters(inputParams);\n        workflowTask1.setTaskReferenceName(\"s1\");\n        workflowTask1.setTaskDefinition(new TaskDef(\"junit_task_l1\"));\n\n        WorkflowTask workflowTask2 = new WorkflowTask();\n        workflowTask2.setName(\"junit_task_l2\");\n        workflowTask2.setInputParameters(inputParams);\n        workflowTask2.setTaskReferenceName(\"s2\");\n        workflowTask2.setTaskDefinition(new TaskDef(\"junit_task_l2\"));\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n        workflowDef.setName(\"Linear Workflow\");\n        workflowDef.getTasks().addAll(Arrays.asList(workflowTask1, workflowTask2));\n\n        return workflowDef;\n    }\n\n    private WorkflowModel createDefaultWorkflow() {\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"TestDeciderService\");\n        workflowDef.setVersion(1);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        workflow.getInput().put(\"requestId\", \"request id 001\");\n        workflow.getInput().put(\"hasAwards\", true);\n        workflow.getInput().put(\"channelMapping\", 5);\n        Map<String, Object> name = new HashMap<>();\n        name.put(\"name\", \"The Who\");\n        name.put(\"year\", 1970);\n        Map<String, Object> name2 = new HashMap<>();\n        name2.put(\"name\", \"The Doors\");\n        name2.put(\"year\", 1975);\n\n        List<Object> names = new LinkedList<>();\n        names.add(name);\n        names.add(name2);\n\n        workflow.addOutput(\"name\", name);\n        workflow.addOutput(\"names\", names);\n        workflow.addOutput(\"awards\", 200);\n\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(\"task2\");\n        task.addOutput(\"location\", \"http://location\");\n        task.setStatus(TaskModel.Status.COMPLETED);\n\n        TaskModel task2 = new TaskModel();\n        task2.setReferenceTaskName(\"task3\");\n        task2.addOutput(\"refId\", \"abcddef_1234_7890_aaffcc\");\n        task2.setStatus(TaskModel.Status.SCHEDULED);\n\n        workflow.getTasks().add(task);\n        workflow.getTasks().add(task2);\n\n        return workflow;\n    }\n\n    private WorkflowDef createNestedWorkflow() {\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"Nested Workflow\");\n        workflowDef.setDescription(workflowDef.getName());\n        workflowDef.setVersion(1);\n        workflowDef.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n\n        Map<String, Object> inputParams = new HashMap<>();\n        inputParams.put(\"p1\", \"workflow.input.param1\");\n        inputParams.put(\"p2\", \"workflow.input.param2\");\n\n        List<WorkflowTask> tasks = new ArrayList<>(10);\n\n        for (int i = 0; i < 10; i++) {\n            WorkflowTask workflowTask = new WorkflowTask();\n            workflowTask.setName(\"junit_task_\" + i);\n            workflowTask.setInputParameters(inputParams);\n            workflowTask.setTaskReferenceName(\"t\" + i);\n            workflowTask.setTaskDefinition(new TaskDef(\"junit_task_\" + i));\n            tasks.add(workflowTask);\n        }\n\n        WorkflowTask decisionTask = new WorkflowTask();\n        decisionTask.setType(DECISION.name());\n        decisionTask.setName(\"Decision\");\n        decisionTask.setTaskReferenceName(\"d1\");\n        decisionTask.setDefaultCase(Collections.singletonList(tasks.get(8)));\n        decisionTask.setCaseValueParam(\"case\");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"a\", Arrays.asList(tasks.get(6), tasks.get(9)));\n        decisionCases.put(\"b\", Collections.singletonList(tasks.get(7)));\n        decisionTask.setDecisionCases(decisionCases);\n\n        WorkflowDef subWorkflowDef = createLinearWorkflow();\n        WorkflowTask subWorkflow = new WorkflowTask();\n        subWorkflow.setType(SUB_WORKFLOW.name());\n        subWorkflow.setName(\"sw1\");\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(subWorkflowDef.getName());\n        subWorkflow.setSubWorkflowParam(subWorkflowParams);\n        subWorkflow.setTaskReferenceName(\"sw1\");\n\n        WorkflowTask forkTask2 = new WorkflowTask();\n        forkTask2.setType(FORK_JOIN.name());\n        forkTask2.setName(\"second fork\");\n        forkTask2.setTaskReferenceName(\"fork2\");\n        forkTask2.getForkTasks().add(Arrays.asList(tasks.get(2), tasks.get(4)));\n        forkTask2.getForkTasks().add(Arrays.asList(tasks.get(3), decisionTask));\n\n        WorkflowTask joinTask2 = new WorkflowTask();\n        joinTask2.setName(\"join2\");\n        joinTask2.setType(JOIN.name());\n        joinTask2.setTaskReferenceName(\"join2\");\n        joinTask2.setJoinOn(Arrays.asList(\"t4\", \"d1\"));\n\n        WorkflowTask forkTask1 = new WorkflowTask();\n        forkTask1.setType(FORK_JOIN.name());\n        forkTask1.setName(\"fork1\");\n        forkTask1.setTaskReferenceName(\"fork1\");\n        forkTask1.getForkTasks().add(Collections.singletonList(tasks.get(1)));\n        forkTask1.getForkTasks().add(Arrays.asList(forkTask2, joinTask2));\n        forkTask1.getForkTasks().add(Collections.singletonList(subWorkflow));\n\n        WorkflowTask joinTask1 = new WorkflowTask();\n        joinTask1.setName(\"join1\");\n        joinTask1.setType(JOIN.name());\n        joinTask1.setTaskReferenceName(\"join1\");\n        joinTask1.setJoinOn(Arrays.asList(\"t1\", \"fork2\"));\n\n        workflowDef.getTasks().add(forkTask1);\n        workflowDef.getTasks().add(joinTask1);\n        workflowDef.getTasks().add(tasks.get(5));\n\n        return workflowDef;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/TestWorkflowDef.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class TestWorkflowDef {\n\n    @Test\n    public void testContainsType() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test_workflow\");\n        def.setVersion(1);\n        def.setSchemaVersion(2);\n        def.getTasks().add(createWorkflowTask(\"simple_task_1\"));\n        def.getTasks().add(createWorkflowTask(\"simple_task_2\"));\n\n        WorkflowTask task3 = createWorkflowTask(\"decision_task_1\");\n        def.getTasks().add(task3);\n        task3.setType(TaskType.DECISION.name());\n        task3.getDecisionCases()\n                .put(\n                        \"Case1\",\n                        Arrays.asList(\n                                createWorkflowTask(\"case_1_task_1\"),\n                                createWorkflowTask(\"case_1_task_2\")));\n        task3.getDecisionCases()\n                .put(\n                        \"Case2\",\n                        Arrays.asList(\n                                createWorkflowTask(\"case_2_task_1\"),\n                                createWorkflowTask(\"case_2_task_2\")));\n        task3.getDecisionCases()\n                .put(\n                        \"Case3\",\n                        Collections.singletonList(\n                                deciderTask(\n                                        \"decision_task_2\",\n                                        toMap(\"Case31\", \"case31_task_1\", \"case_31_task_2\"),\n                                        Collections.singletonList(\"case3_def_task\"))));\n        def.getTasks().add(createWorkflowTask(\"simple_task_3\"));\n\n        assertTrue(def.containsType(TaskType.SIMPLE.name()));\n        assertTrue(def.containsType(TaskType.DECISION.name()));\n        assertFalse(def.containsType(TaskType.DO_WHILE.name()));\n    }\n\n    @Test\n    public void testGetNextTask_Decision() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test_workflow\");\n        def.setVersion(1);\n        def.setSchemaVersion(2);\n        def.getTasks().add(createWorkflowTask(\"simple_task_1\"));\n        def.getTasks().add(createWorkflowTask(\"simple_task_2\"));\n\n        WorkflowTask task3 = createWorkflowTask(\"decision_task_1\");\n        def.getTasks().add(task3);\n        task3.setType(TaskType.DECISION.name());\n        task3.getDecisionCases()\n                .put(\n                        \"Case1\",\n                        Arrays.asList(\n                                createWorkflowTask(\"case_1_task_1\"),\n                                createWorkflowTask(\"case_1_task_2\")));\n        task3.getDecisionCases()\n                .put(\n                        \"Case2\",\n                        Arrays.asList(\n                                createWorkflowTask(\"case_2_task_1\"),\n                                createWorkflowTask(\"case_2_task_2\")));\n        task3.getDecisionCases()\n                .put(\n                        \"Case3\",\n                        Collections.singletonList(\n                                deciderTask(\n                                        \"decision_task_2\",\n                                        toMap(\"Case31\", \"case31_task_1\", \"case_31_task_2\"),\n                                        Collections.singletonList(\"case3_def_task\"))));\n        def.getTasks().add(createWorkflowTask(\"simple_task_3\"));\n\n        // Assertions\n        WorkflowTask next = def.getNextTask(\"simple_task_1\");\n        assertNotNull(next);\n        assertEquals(\"simple_task_2\", next.getTaskReferenceName());\n\n        next = def.getNextTask(\"simple_task_2\");\n        assertNotNull(next);\n        assertEquals(task3.getTaskReferenceName(), next.getTaskReferenceName());\n\n        next = def.getNextTask(\"decision_task_1\");\n        assertNotNull(next);\n        assertEquals(\"simple_task_3\", next.getTaskReferenceName());\n\n        next = def.getNextTask(\"case_1_task_1\");\n        assertNotNull(next);\n        assertEquals(\"case_1_task_2\", next.getTaskReferenceName());\n\n        next = def.getNextTask(\"case_1_task_2\");\n        assertNotNull(next);\n        assertEquals(\"simple_task_3\", next.getTaskReferenceName());\n\n        next = def.getNextTask(\"case3_def_task\");\n        assertNotNull(next);\n        assertEquals(\"simple_task_3\", next.getTaskReferenceName());\n\n        next = def.getNextTask(\"case31_task_1\");\n        assertNotNull(next);\n        assertEquals(\"case_31_task_2\", next.getTaskReferenceName());\n    }\n\n    @Test\n    public void testGetNextTask_Conditional() {\n        String COND_TASK_WF = \"COND_TASK_WF\";\n        List<WorkflowTask> workflowTasks = new ArrayList<>(10);\n        for (int i = 0; i < 10; i++) {\n            workflowTasks.add(createWorkflowTask(\"junit_task_\" + i));\n        }\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(COND_TASK_WF);\n        workflowDef.setDescription(COND_TASK_WF);\n\n        WorkflowTask subCaseTask = new WorkflowTask();\n        subCaseTask.setType(TaskType.DECISION.name());\n        subCaseTask.setCaseValueParam(\"case2\");\n        subCaseTask.setName(\"case2\");\n        subCaseTask.setTaskReferenceName(\"case2\");\n        Map<String, List<WorkflowTask>> dcx = new HashMap<>();\n        dcx.put(\"sc1\", workflowTasks.subList(4, 5));\n        dcx.put(\"sc2\", workflowTasks.subList(5, 7));\n        subCaseTask.setDecisionCases(dcx);\n\n        WorkflowTask caseTask = new WorkflowTask();\n        caseTask.setType(TaskType.DECISION.name());\n        caseTask.setCaseValueParam(\"case\");\n        caseTask.setName(\"case\");\n        caseTask.setTaskReferenceName(\"case\");\n        Map<String, List<WorkflowTask>> dc = new HashMap<>();\n        dc.put(\"c1\", Arrays.asList(workflowTasks.get(0), subCaseTask, workflowTasks.get(1)));\n        dc.put(\"c2\", Collections.singletonList(workflowTasks.get(3)));\n        caseTask.setDecisionCases(dc);\n\n        workflowDef.getTasks().add(caseTask);\n        workflowDef.getTasks().addAll(workflowTasks.subList(8, 9));\n\n        WorkflowTask nextTask = workflowDef.getNextTask(\"case\");\n        assertEquals(\"junit_task_8\", nextTask.getTaskReferenceName());\n\n        nextTask = workflowDef.getNextTask(\"junit_task_8\");\n        assertNull(nextTask);\n\n        nextTask = workflowDef.getNextTask(\"junit_task_0\");\n        assertNotNull(nextTask);\n        assertEquals(\"case2\", nextTask.getTaskReferenceName());\n\n        nextTask = workflowDef.getNextTask(\"case2\");\n        assertNotNull(nextTask);\n        assertEquals(\"junit_task_1\", nextTask.getTaskReferenceName());\n    }\n\n    private WorkflowTask createWorkflowTask(String name) {\n        WorkflowTask task = new WorkflowTask();\n        task.setName(name);\n        task.setTaskReferenceName(name);\n        return task;\n    }\n\n    private WorkflowTask deciderTask(\n            String name, Map<String, List<String>> decisions, List<String> defaultTasks) {\n        WorkflowTask task = createWorkflowTask(name);\n        task.setType(TaskType.DECISION.name());\n        decisions.forEach(\n                (key, value) -> {\n                    List<WorkflowTask> tasks = new LinkedList<>();\n                    value.forEach(taskName -> tasks.add(createWorkflowTask(taskName)));\n                    task.getDecisionCases().put(key, tasks);\n                });\n        List<WorkflowTask> tasks = new LinkedList<>();\n        defaultTasks.forEach(defaultTask -> tasks.add(createWorkflowTask(defaultTask)));\n        task.setDefaultCase(tasks);\n        return task;\n    }\n\n    private Map<String, List<String>> toMap(String key, String... values) {\n        Map<String, List<String>> map = new HashMap<>();\n        List<String> vals = Arrays.asList(values);\n        map.put(key, vals);\n        return map;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/TestWorkflowExecutor.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport java.time.Duration;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Collectors;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.stubbing.Answer;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.support.DefaultListableBeanFactory;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.event.WorkflowCreationEvent;\nimport com.netflix.conductor.core.exception.ConflictException;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.core.execution.mapper.*;\nimport com.netflix.conductor.core.execution.tasks.*;\nimport com.netflix.conductor.core.listener.TaskStatusListener;\nimport com.netflix.conductor.core.listener.WorkflowStatusListener;\nimport com.netflix.conductor.core.metadata.MetadataMapperService;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.conductor.service.ExecutionLockService;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.*;\n\nimport static java.util.Comparator.comparingInt;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.maxBy;\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@ContextConfiguration(\n        classes = {\n            TestObjectMapperConfiguration.class,\n            TestWorkflowExecutor.TestConfiguration.class\n        })\n@RunWith(SpringRunner.class)\npublic class TestWorkflowExecutor {\n\n    private WorkflowExecutor workflowExecutor;\n    private ExecutionDAOFacade executionDAOFacade;\n    private MetadataDAO metadataDAO;\n    private QueueDAO queueDAO;\n    private WorkflowStatusListener workflowStatusListener;\n    private TaskStatusListener taskStatusListener;\n    private ExecutionLockService executionLockService;\n    private ExternalPayloadStorageUtils externalPayloadStorageUtils;\n\n    @Configuration\n    @ComponentScan(basePackageClasses = {Evaluator.class}) // load all Evaluator beans.\n    public static class TestConfiguration {\n\n        @Bean(TASK_TYPE_SUB_WORKFLOW)\n        public SubWorkflow subWorkflow(ObjectMapper objectMapper) {\n            return new SubWorkflow(objectMapper, mock(StartWorkflowOperation.class));\n        }\n\n        @Bean(TASK_TYPE_LAMBDA)\n        public Lambda lambda() {\n            return new Lambda();\n        }\n\n        @Bean(TASK_TYPE_WAIT)\n        public Wait waitBean() {\n            return new Wait();\n        }\n\n        @Bean(\"HTTP\")\n        public WorkflowSystemTask http() {\n            return new WorkflowSystemTaskStub(\"HTTP\") {\n                @Override\n                public boolean isAsync() {\n                    return true;\n                }\n            };\n        }\n\n        @Bean(\"HTTP2\")\n        public WorkflowSystemTask http2() {\n            return new WorkflowSystemTaskStub(\"HTTP2\");\n        }\n\n        @Bean(TASK_TYPE_JSON_JQ_TRANSFORM)\n        public WorkflowSystemTask jsonBean() {\n            return new WorkflowSystemTaskStub(\"JSON_JQ_TRANSFORM\") {\n                @Override\n                public boolean isAsync() {\n                    return false;\n                }\n\n                @Override\n                public void start(\n                        WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n                    task.setStatus(TaskModel.Status.COMPLETED);\n                }\n            };\n        }\n\n        @Bean\n        public SystemTaskRegistry systemTaskRegistry(Set<WorkflowSystemTask> tasks) {\n            return new SystemTaskRegistry(tasks);\n        }\n    }\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Autowired private SystemTaskRegistry systemTaskRegistry;\n\n    @Autowired private DefaultListableBeanFactory beanFactory;\n\n    @Autowired private Map<String, Evaluator> evaluators;\n\n    private ApplicationEventPublisher eventPublisher;\n\n    @Before\n    public void init() {\n        executionDAOFacade = mock(ExecutionDAOFacade.class);\n        metadataDAO = mock(MetadataDAO.class);\n        queueDAO = mock(QueueDAO.class);\n        workflowStatusListener = mock(WorkflowStatusListener.class);\n        taskStatusListener = mock(TaskStatusListener.class);\n        externalPayloadStorageUtils = mock(ExternalPayloadStorageUtils.class);\n        executionLockService = mock(ExecutionLockService.class);\n        eventPublisher = mock(ApplicationEventPublisher.class);\n\n        ParametersUtils parametersUtils = new ParametersUtils(objectMapper);\n        IDGenerator idGenerator = new IDGenerator();\n        Map<String, TaskMapper> taskMappers = new HashMap<>();\n        taskMappers.put(DECISION.name(), new DecisionTaskMapper());\n        taskMappers.put(SWITCH.name(), new SwitchTaskMapper(evaluators));\n        taskMappers.put(DYNAMIC.name(), new DynamicTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(FORK_JOIN.name(), new ForkJoinTaskMapper());\n        taskMappers.put(JOIN.name(), new JoinTaskMapper());\n        taskMappers.put(\n                FORK_JOIN_DYNAMIC.name(),\n                new ForkJoinDynamicTaskMapper(\n                        idGenerator, parametersUtils, objectMapper, metadataDAO));\n        taskMappers.put(\n                USER_DEFINED.name(), new UserDefinedTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(SIMPLE.name(), new SimpleTaskMapper(parametersUtils));\n        taskMappers.put(\n                SUB_WORKFLOW.name(), new SubWorkflowTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(EVENT.name(), new EventTaskMapper(parametersUtils));\n        taskMappers.put(WAIT.name(), new WaitTaskMapper(parametersUtils));\n        taskMappers.put(HTTP.name(), new HTTPTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(LAMBDA.name(), new LambdaTaskMapper(parametersUtils, metadataDAO));\n        taskMappers.put(INLINE.name(), new InlineTaskMapper(parametersUtils, metadataDAO));\n\n        DeciderService deciderService =\n                new DeciderService(\n                        idGenerator,\n                        parametersUtils,\n                        metadataDAO,\n                        externalPayloadStorageUtils,\n                        systemTaskRegistry,\n                        taskMappers,\n                        Duration.ofMinutes(60));\n        MetadataMapperService metadataMapperService = new MetadataMapperService(metadataDAO);\n\n        ConductorProperties properties = mock(ConductorProperties.class);\n        when(properties.getActiveWorkerLastPollTimeout()).thenReturn(Duration.ofSeconds(100));\n        when(properties.getTaskExecutionPostponeDuration()).thenReturn(Duration.ofSeconds(60));\n        when(properties.getWorkflowOffsetTimeout()).thenReturn(Duration.ofSeconds(30));\n\n        workflowExecutor =\n                new WorkflowExecutor(\n                        deciderService,\n                        metadataDAO,\n                        queueDAO,\n                        metadataMapperService,\n                        workflowStatusListener,\n                        taskStatusListener,\n                        executionDAOFacade,\n                        properties,\n                        executionLockService,\n                        systemTaskRegistry,\n                        parametersUtils,\n                        idGenerator,\n                        eventPublisher);\n    }\n\n    @Test\n    public void testScheduleTask() {\n        IDGenerator idGenerator = new IDGenerator();\n        WorkflowSystemTaskStub httpTask = beanFactory.getBean(\"HTTP\", WorkflowSystemTaskStub.class);\n        WorkflowSystemTaskStub http2Task =\n                beanFactory.getBean(\"HTTP2\", WorkflowSystemTaskStub.class);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"1\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"1\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        List<TaskModel> tasks = new LinkedList<>();\n\n        WorkflowTask taskToSchedule = new WorkflowTask();\n        taskToSchedule.setWorkflowTaskType(TaskType.USER_DEFINED);\n        taskToSchedule.setType(\"HTTP\");\n\n        WorkflowTask taskToSchedule2 = new WorkflowTask();\n        taskToSchedule2.setWorkflowTaskType(TaskType.USER_DEFINED);\n        taskToSchedule2.setType(\"HTTP2\");\n\n        WorkflowTask wait = new WorkflowTask();\n        wait.setWorkflowTaskType(TaskType.WAIT);\n        wait.setType(\"WAIT\");\n        wait.setTaskReferenceName(\"wait\");\n\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(taskToSchedule.getType());\n        task1.setTaskDefName(taskToSchedule.getName());\n        task1.setReferenceTaskName(taskToSchedule.getTaskReferenceName());\n        task1.setWorkflowInstanceId(workflow.getWorkflowId());\n        task1.setCorrelationId(workflow.getCorrelationId());\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setInputData(new HashMap<>());\n        task1.setStatus(TaskModel.Status.SCHEDULED);\n        task1.setRetryCount(0);\n        task1.setCallbackAfterSeconds(taskToSchedule.getStartDelay());\n        task1.setWorkflowTask(taskToSchedule);\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(TASK_TYPE_WAIT);\n        task2.setTaskDefName(taskToSchedule.getName());\n        task2.setReferenceTaskName(taskToSchedule.getTaskReferenceName());\n        task2.setWorkflowInstanceId(workflow.getWorkflowId());\n        task2.setCorrelationId(workflow.getCorrelationId());\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setInputData(new HashMap<>());\n        task2.setTaskId(idGenerator.generate());\n        task2.setStatus(TaskModel.Status.IN_PROGRESS);\n        task2.setWorkflowTask(taskToSchedule);\n\n        TaskModel task3 = new TaskModel();\n        task3.setTaskType(taskToSchedule2.getType());\n        task3.setTaskDefName(taskToSchedule.getName());\n        task3.setReferenceTaskName(taskToSchedule.getTaskReferenceName());\n        task3.setWorkflowInstanceId(workflow.getWorkflowId());\n        task3.setCorrelationId(workflow.getCorrelationId());\n        task3.setScheduledTime(System.currentTimeMillis());\n        task3.setTaskId(idGenerator.generate());\n        task3.setInputData(new HashMap<>());\n        task3.setStatus(TaskModel.Status.SCHEDULED);\n        task3.setRetryCount(0);\n        task3.setCallbackAfterSeconds(taskToSchedule.getStartDelay());\n        task3.setWorkflowTask(taskToSchedule);\n\n        tasks.add(task1);\n        tasks.add(task2);\n        tasks.add(task3);\n\n        when(executionDAOFacade.createTasks(tasks)).thenReturn(tasks);\n        AtomicInteger startedTaskCount = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            startedTaskCount.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateTask(any());\n\n        AtomicInteger queuedTaskCount = new AtomicInteger(0);\n        final Answer answer =\n                invocation -> {\n                    String queueName = invocation.getArgument(0, String.class);\n                    queuedTaskCount.incrementAndGet();\n                    return null;\n                };\n        doAnswer(answer).when(queueDAO).push(any(), any(), anyLong());\n        doAnswer(answer).when(queueDAO).push(any(), any(), anyInt(), anyLong());\n\n        boolean stateChanged = workflowExecutor.scheduleTask(workflow, tasks);\n        // Wait task is no async to it will be queued.\n        assertEquals(1, startedTaskCount.get());\n        assertEquals(2, queuedTaskCount.get());\n        assertTrue(stateChanged);\n        assertFalse(httpTask.isStarted());\n        assertTrue(http2Task.isStarted());\n    }\n\n    @Test(expected = TerminateWorkflowException.class)\n    public void testScheduleTaskFailure() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"wid_01\");\n\n        List<TaskModel> tasks = new LinkedList<>();\n\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.TASK_TYPE_SIMPLE);\n        task1.setTaskDefName(\"task_1\");\n        task1.setReferenceTaskName(\"task_1\");\n        task1.setWorkflowInstanceId(workflow.getWorkflowId());\n        task1.setTaskId(\"tid_01\");\n        task1.setStatus(TaskModel.Status.SCHEDULED);\n        task1.setRetryCount(0);\n\n        tasks.add(task1);\n\n        when(executionDAOFacade.createTasks(tasks)).thenThrow(new RuntimeException());\n        workflowExecutor.scheduleTask(workflow, tasks);\n    }\n\n    /** Simulate Queue push failures and assert that scheduleTask doesn't throw an exception. */\n    @Test\n    public void testQueueFailuresDuringScheduleTask() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"wid_01\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"wid\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        List<TaskModel> tasks = new LinkedList<>();\n\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.TASK_TYPE_SIMPLE);\n        task1.setTaskDefName(\"task_1\");\n        task1.setReferenceTaskName(\"task_1\");\n        task1.setWorkflowInstanceId(workflow.getWorkflowId());\n        task1.setTaskId(\"tid_01\");\n        task1.setStatus(TaskModel.Status.SCHEDULED);\n        task1.setRetryCount(0);\n\n        tasks.add(task1);\n\n        when(executionDAOFacade.createTasks(tasks)).thenReturn(tasks);\n        doThrow(new RuntimeException())\n                .when(queueDAO)\n                .push(anyString(), anyString(), anyInt(), anyLong());\n        assertFalse(workflowExecutor.scheduleTask(workflow, tasks));\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    public void testCompleteWorkflow() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setWorkflowId(\"1\");\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setOwnerApp(\"junit_test\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        workflow.setOutput(Collections.EMPTY_MAP);\n\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        AtomicInteger updateWorkflowCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateWorkflowCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateWorkflow(any());\n\n        AtomicInteger updateTasksCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateTasksCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateTasks(any());\n\n        AtomicInteger removeQueueEntryCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            removeQueueEntryCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(queueDAO)\n                .remove(anyString(), anyString());\n\n        workflowExecutor.completeWorkflow(workflow);\n        assertEquals(WorkflowModel.Status.COMPLETED, workflow.getStatus());\n        assertEquals(1, updateWorkflowCalledCounter.get());\n        assertEquals(0, updateTasksCalledCounter.get());\n        assertEquals(0, removeQueueEntryCalledCounter.get());\n        verify(workflowStatusListener, times(1))\n                .onWorkflowCompletedIfEnabled(any(WorkflowModel.class));\n        verify(workflowStatusListener, times(0))\n                .onWorkflowFinalizedIfEnabled(any(WorkflowModel.class));\n\n        def.setWorkflowStatusListenerEnabled(true);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflowExecutor.completeWorkflow(workflow);\n        verify(workflowStatusListener, times(2))\n                .onWorkflowCompletedIfEnabled(any(WorkflowModel.class));\n        verify(workflowStatusListener, times(0))\n                .onWorkflowFinalizedIfEnabled(any(WorkflowModel.class));\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    public void testTerminateWorkflow() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setWorkflowId(\"1\");\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setOwnerApp(\"junit_test\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        workflow.setOutput(Collections.EMPTY_MAP);\n\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        AtomicInteger updateWorkflowCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateWorkflowCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateWorkflow(any());\n\n        AtomicInteger updateTasksCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateTasksCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateTasks(any());\n\n        AtomicInteger removeQueueEntryCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            removeQueueEntryCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(queueDAO)\n                .remove(anyString(), anyString());\n\n        workflowExecutor.terminateWorkflow(\"workflowId\", \"reason\");\n        assertEquals(WorkflowModel.Status.TERMINATED, workflow.getStatus());\n        assertEquals(1, updateWorkflowCalledCounter.get());\n        assertEquals(1, removeQueueEntryCalledCounter.get());\n\n        verify(workflowStatusListener, times(1))\n                .onWorkflowTerminatedIfEnabled(any(WorkflowModel.class));\n        verify(workflowStatusListener, times(1))\n                .onWorkflowFinalizedIfEnabled(any(WorkflowModel.class));\n\n        def.setWorkflowStatusListenerEnabled(true);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflowExecutor.completeWorkflow(workflow);\n        verify(workflowStatusListener, times(1))\n                .onWorkflowCompletedIfEnabled(any(WorkflowModel.class));\n        verify(workflowStatusListener, times(1))\n                .onWorkflowFinalizedIfEnabled(any(WorkflowModel.class));\n    }\n\n    @Test\n    public void testUploadOutputFailuresDuringTerminateWorkflow() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n        def.setWorkflowStatusListenerEnabled(true);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setWorkflowId(\"1\");\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setOwnerApp(\"junit_test\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        workflow.setOutput(Collections.EMPTY_MAP);\n\n        List<TaskModel> tasks = new LinkedList<>();\n\n        TaskModel task = new TaskModel();\n        task.setScheduledTime(1L);\n        task.setSeq(1);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setReferenceTaskName(\"t1\");\n        task.setWorkflowInstanceId(workflow.getWorkflowId());\n        task.setTaskDefName(\"task1\");\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        tasks.add(task);\n        workflow.setTasks(tasks);\n\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        AtomicInteger updateWorkflowCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateWorkflowCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateWorkflow(any());\n\n        doThrow(new RuntimeException(\"any exception\"))\n                .when(externalPayloadStorageUtils)\n                .verifyAndUpload(workflow, ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT);\n\n        workflowExecutor.terminateWorkflow(workflow.getWorkflowId(), \"reason\");\n        assertEquals(WorkflowModel.Status.TERMINATED, workflow.getStatus());\n        assertEquals(1, updateWorkflowCalledCounter.get());\n        verify(workflowStatusListener, times(1))\n                .onWorkflowTerminatedIfEnabled(any(WorkflowModel.class));\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    public void testQueueExceptionsIgnoredDuringTerminateWorkflow() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n        def.setWorkflowStatusListenerEnabled(true);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setWorkflowId(\"1\");\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setOwnerApp(\"junit_test\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        workflow.setOutput(Collections.EMPTY_MAP);\n\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        AtomicInteger updateWorkflowCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateWorkflowCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateWorkflow(any());\n\n        AtomicInteger updateTasksCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateTasksCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateTasks(any());\n\n        doThrow(new RuntimeException()).when(queueDAO).remove(anyString(), anyString());\n\n        workflowExecutor.terminateWorkflow(\"workflowId\", \"reason\");\n        assertEquals(WorkflowModel.Status.TERMINATED, workflow.getStatus());\n        assertEquals(1, updateWorkflowCalledCounter.get());\n        verify(workflowStatusListener, times(1))\n                .onWorkflowTerminatedIfEnabled(any(WorkflowModel.class));\n    }\n\n    @Test\n    public void testRestartWorkflow() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"test_task\");\n        workflowTask.setTaskReferenceName(\"task_ref\");\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testDef\");\n        workflowDef.setVersion(1);\n        workflowDef.setRestartable(true);\n        workflowDef.getTasks().add(workflowTask);\n\n        TaskModel task_1 = new TaskModel();\n        task_1.setTaskId(UUID.randomUUID().toString());\n        task_1.setSeq(1);\n        task_1.setStatus(TaskModel.Status.FAILED);\n        task_1.setTaskDefName(workflowTask.getName());\n        task_1.setReferenceTaskName(workflowTask.getTaskReferenceName());\n\n        TaskModel task_2 = new TaskModel();\n        task_2.setTaskId(UUID.randomUUID().toString());\n        task_2.setSeq(2);\n        task_2.setStatus(TaskModel.Status.FAILED);\n        task_2.setTaskDefName(workflowTask.getName());\n        task_2.setReferenceTaskName(workflowTask.getTaskReferenceName());\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setWorkflowId(\"test-workflow-id\");\n        workflow.getTasks().addAll(Arrays.asList(task_1, task_2));\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n        workflow.setEndTime(500);\n        workflow.setLastRetriedTime(100);\n\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        doNothing().when(executionDAOFacade).removeTask(any());\n        when(metadataDAO.getWorkflowDef(workflow.getWorkflowName(), workflow.getWorkflowVersion()))\n                .thenReturn(Optional.of(workflowDef));\n        when(metadataDAO.getTaskDef(workflowTask.getName())).thenReturn(new TaskDef());\n        when(executionDAOFacade.updateWorkflow(any())).thenReturn(\"\");\n\n        workflowExecutor.restart(workflow.getWorkflowId(), false);\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertEquals(0, workflow.getEndTime());\n        assertEquals(0, workflow.getLastRetriedTime());\n        verify(metadataDAO, never()).getLatestWorkflowDef(any());\n\n        ArgumentCaptor<WorkflowModel> argumentCaptor = ArgumentCaptor.forClass(WorkflowModel.class);\n        verify(executionDAOFacade, times(1)).createWorkflow(argumentCaptor.capture());\n        assertEquals(\n                workflow.getWorkflowId(), argumentCaptor.getAllValues().get(0).getWorkflowId());\n        assertEquals(\n                workflow.getWorkflowDefinition(),\n                argumentCaptor.getAllValues().get(0).getWorkflowDefinition());\n\n        // add a new version of the workflow definition and restart with latest\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        workflow.setEndTime(500);\n        workflow.setLastRetriedTime(100);\n        workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testDef\");\n        workflowDef.setVersion(2);\n        workflowDef.setRestartable(true);\n        workflowDef.getTasks().addAll(Collections.singletonList(workflowTask));\n\n        when(metadataDAO.getLatestWorkflowDef(workflow.getWorkflowName()))\n                .thenReturn(Optional.of(workflowDef));\n        workflowExecutor.restart(workflow.getWorkflowId(), true);\n        assertEquals(WorkflowModel.Status.COMPLETED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertEquals(0, workflow.getEndTime());\n        assertEquals(0, workflow.getLastRetriedTime());\n        verify(metadataDAO, times(1)).getLatestWorkflowDef(anyString());\n\n        argumentCaptor = ArgumentCaptor.forClass(WorkflowModel.class);\n        verify(executionDAOFacade, times(2)).createWorkflow(argumentCaptor.capture());\n        assertEquals(\n                workflow.getWorkflowId(), argumentCaptor.getAllValues().get(1).getWorkflowId());\n        assertEquals(workflowDef, argumentCaptor.getAllValues().get(1).getWorkflowDefinition());\n    }\n\n    @Test(expected = NotFoundException.class)\n    public void testRetryNonTerminalWorkflow() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryNonTerminalWorkflow\");\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n    }\n\n    @Test(expected = ConflictException.class)\n    public void testRetryWorkflowNoTasks() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"ApplicationException\");\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n        workflow.setTasks(Collections.emptyList());\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n    }\n\n    @Test(expected = ConflictException.class)\n    public void testRetryWorkflowNoFailedTasks() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRetryWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        // add 2 failed task in 2 forks and 1 cancelled in the 3rd fork\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(1);\n        task_1_1.setRetryCount(0);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.FAILED);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_1_2 = new TaskModel();\n        task_1_2.setTaskId(UUID.randomUUID().toString());\n        task_1_2.setSeq(2);\n        task_1_2.setRetryCount(1);\n        task_1_2.setTaskType(TaskType.SIMPLE.toString());\n        task_1_2.setStatus(TaskModel.Status.COMPLETED);\n        task_1_2.setTaskDefName(\"task1\");\n        task_1_2.setReferenceTaskName(\"task1_ref1\");\n\n        workflow.getTasks().addAll(Arrays.asList(task_1_1, task_1_2));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n    }\n\n    @Test\n    public void testRetryWorkflow() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRetryWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        AtomicInteger updateWorkflowCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateWorkflowCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateWorkflow(any());\n\n        AtomicInteger updateTasksCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateTasksCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateTasks(any());\n\n        AtomicInteger updateTaskCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateTaskCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateTask(any());\n\n        // add 2 failed task in 2 forks and 1 cancelled in the 3rd fork\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(20);\n        task_1_1.setRetryCount(1);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.CANCELED);\n        task_1_1.setRetried(true);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setWorkflowTask(new WorkflowTask());\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_1_2 = new TaskModel();\n        task_1_2.setTaskId(UUID.randomUUID().toString());\n        task_1_2.setSeq(21);\n        task_1_2.setRetryCount(1);\n        task_1_2.setTaskType(TaskType.SIMPLE.toString());\n        task_1_2.setStatus(TaskModel.Status.FAILED);\n        task_1_2.setTaskDefName(\"task1\");\n        task_1_2.setWorkflowTask(new WorkflowTask());\n        task_1_2.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_2_1 = new TaskModel();\n        task_2_1.setTaskId(UUID.randomUUID().toString());\n        task_2_1.setSeq(22);\n        task_2_1.setRetryCount(1);\n        task_2_1.setStatus(TaskModel.Status.FAILED);\n        task_2_1.setTaskType(TaskType.SIMPLE.toString());\n        task_2_1.setTaskDefName(\"task2\");\n        task_2_1.setWorkflowTask(new WorkflowTask());\n        task_2_1.setReferenceTaskName(\"task2_ref1\");\n\n        TaskModel task_3_1 = new TaskModel();\n        task_3_1.setTaskId(UUID.randomUUID().toString());\n        task_3_1.setSeq(23);\n        task_3_1.setRetryCount(1);\n        task_3_1.setStatus(TaskModel.Status.CANCELED);\n        task_3_1.setTaskType(TaskType.SIMPLE.toString());\n        task_3_1.setTaskDefName(\"task3\");\n        task_3_1.setWorkflowTask(new WorkflowTask());\n        task_3_1.setReferenceTaskName(\"task3_ref1\");\n\n        TaskModel task_4_1 = new TaskModel();\n        task_4_1.setTaskId(UUID.randomUUID().toString());\n        task_4_1.setSeq(122);\n        task_4_1.setRetryCount(1);\n        task_4_1.setStatus(TaskModel.Status.FAILED);\n        task_4_1.setTaskType(TaskType.SIMPLE.toString());\n        task_4_1.setTaskDefName(\"task1\");\n        task_4_1.setWorkflowTask(new WorkflowTask());\n        task_4_1.setReferenceTaskName(\"task4_refABC\");\n\n        workflow.getTasks().addAll(Arrays.asList(task_1_1, task_1_2, task_2_1, task_3_1, task_4_1));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n\n        // then:\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertEquals(1, updateWorkflowCalledCounter.get());\n        assertEquals(1, updateTasksCalledCounter.get());\n        assertEquals(0, updateTaskCalledCounter.get());\n    }\n\n    @Test\n    public void testRetryWorkflowReturnsNoDuplicates() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRetryWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(10);\n        task_1_1.setRetryCount(0);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.FAILED);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setWorkflowTask(new WorkflowTask());\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_1_2 = new TaskModel();\n        task_1_2.setTaskId(UUID.randomUUID().toString());\n        task_1_2.setSeq(11);\n        task_1_2.setRetryCount(1);\n        task_1_2.setTaskType(TaskType.SIMPLE.toString());\n        task_1_2.setStatus(TaskModel.Status.COMPLETED);\n        task_1_2.setTaskDefName(\"task1\");\n        task_1_2.setWorkflowTask(new WorkflowTask());\n        task_1_2.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_2_1 = new TaskModel();\n        task_2_1.setTaskId(UUID.randomUUID().toString());\n        task_2_1.setSeq(21);\n        task_2_1.setRetryCount(0);\n        task_2_1.setStatus(TaskModel.Status.CANCELED);\n        task_2_1.setTaskType(TaskType.SIMPLE.toString());\n        task_2_1.setTaskDefName(\"task2\");\n        task_2_1.setWorkflowTask(new WorkflowTask());\n        task_2_1.setReferenceTaskName(\"task2_ref1\");\n\n        TaskModel task_3_1 = new TaskModel();\n        task_3_1.setTaskId(UUID.randomUUID().toString());\n        task_3_1.setSeq(31);\n        task_3_1.setRetryCount(1);\n        task_3_1.setStatus(TaskModel.Status.FAILED_WITH_TERMINAL_ERROR);\n        task_3_1.setTaskType(TaskType.SIMPLE.toString());\n        task_3_1.setTaskDefName(\"task1\");\n        task_3_1.setWorkflowTask(new WorkflowTask());\n        task_3_1.setReferenceTaskName(\"task3_ref1\");\n\n        TaskModel task_4_1 = new TaskModel();\n        task_4_1.setTaskId(UUID.randomUUID().toString());\n        task_4_1.setSeq(41);\n        task_4_1.setRetryCount(0);\n        task_4_1.setStatus(TaskModel.Status.TIMED_OUT);\n        task_4_1.setTaskType(TaskType.SIMPLE.toString());\n        task_4_1.setTaskDefName(\"task1\");\n        task_4_1.setWorkflowTask(new WorkflowTask());\n        task_4_1.setReferenceTaskName(\"task4_ref1\");\n\n        workflow.getTasks().addAll(Arrays.asList(task_1_1, task_1_2, task_2_1, task_3_1, task_4_1));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n\n        assertEquals(8, workflow.getTasks().size());\n    }\n\n    @Test\n    public void testRetryWorkflowMultipleRetries() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRetryWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(10);\n        task_1_1.setRetryCount(0);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.FAILED);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setWorkflowTask(new WorkflowTask());\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_2_1 = new TaskModel();\n        task_2_1.setTaskId(UUID.randomUUID().toString());\n        task_2_1.setSeq(20);\n        task_2_1.setRetryCount(0);\n        task_2_1.setTaskType(TaskType.SIMPLE.toString());\n        task_2_1.setStatus(TaskModel.Status.CANCELED);\n        task_2_1.setTaskDefName(\"task1\");\n        task_2_1.setWorkflowTask(new WorkflowTask());\n        task_2_1.setReferenceTaskName(\"task2_ref1\");\n\n        workflow.getTasks().addAll(Arrays.asList(task_1_1, task_2_1));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n\n        assertEquals(4, workflow.getTasks().size());\n\n        // Reset Last Workflow Task to FAILED.\n        TaskModel lastTask =\n                workflow.getTasks().stream()\n                        .filter(t -> t.getReferenceTaskName().equals(\"task1_ref1\"))\n                        .collect(\n                                groupingBy(\n                                        TaskModel::getReferenceTaskName,\n                                        maxBy(comparingInt(TaskModel::getSeq))))\n                        .values()\n                        .stream()\n                        .map(Optional::get)\n                        .collect(Collectors.toList())\n                        .get(0);\n        lastTask.setStatus(TaskModel.Status.FAILED);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n\n        assertEquals(5, workflow.getTasks().size());\n\n        // Reset Last Workflow Task to FAILED.\n        // Reset Last Workflow Task to FAILED.\n        TaskModel lastTask2 =\n                workflow.getTasks().stream()\n                        .filter(t -> t.getReferenceTaskName().equals(\"task1_ref1\"))\n                        .collect(\n                                groupingBy(\n                                        TaskModel::getReferenceTaskName,\n                                        maxBy(comparingInt(TaskModel::getSeq))))\n                        .values()\n                        .stream()\n                        .map(Optional::get)\n                        .collect(Collectors.toList())\n                        .get(0);\n        lastTask2.setStatus(TaskModel.Status.FAILED);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n\n        assertEquals(6, workflow.getTasks().size());\n    }\n\n    @Test\n    public void testRetryWorkflowWithJoinTask() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRetryWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        TaskModel forkTask = new TaskModel();\n        forkTask.setTaskType(TaskType.FORK_JOIN.toString());\n        forkTask.setTaskId(UUID.randomUUID().toString());\n        forkTask.setSeq(1);\n        forkTask.setRetryCount(1);\n        forkTask.setStatus(TaskModel.Status.COMPLETED);\n        forkTask.setReferenceTaskName(\"task_fork\");\n\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(20);\n        task_1_1.setRetryCount(1);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.FAILED);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setWorkflowTask(new WorkflowTask());\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_2_1 = new TaskModel();\n        task_2_1.setTaskId(UUID.randomUUID().toString());\n        task_2_1.setSeq(22);\n        task_2_1.setRetryCount(1);\n        task_2_1.setStatus(TaskModel.Status.CANCELED);\n        task_2_1.setTaskType(TaskType.SIMPLE.toString());\n        task_2_1.setTaskDefName(\"task2\");\n        task_2_1.setWorkflowTask(new WorkflowTask());\n        task_2_1.setReferenceTaskName(\"task2_ref1\");\n\n        TaskModel joinTask = new TaskModel();\n        joinTask.setTaskType(TaskType.JOIN.toString());\n        joinTask.setTaskId(UUID.randomUUID().toString());\n        joinTask.setSeq(25);\n        joinTask.setRetryCount(1);\n        joinTask.setStatus(TaskModel.Status.CANCELED);\n        joinTask.setReferenceTaskName(\"task_join\");\n        joinTask.getInputData()\n                .put(\n                        \"joinOn\",\n                        Arrays.asList(\n                                task_1_1.getReferenceTaskName(), task_2_1.getReferenceTaskName()));\n\n        workflow.getTasks().addAll(Arrays.asList(forkTask, task_1_1, task_2_1, joinTask));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n\n        assertEquals(6, workflow.getTasks().size());\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n    }\n\n    @Test\n    public void testRetryFromLastFailedSubWorkflowTaskThenStartWithLastFailedTask() {\n        IDGenerator idGenerator = new IDGenerator();\n        // given\n        String id = idGenerator.generate();\n        String workflowInstanceId = idGenerator.generate();\n        TaskModel task = new TaskModel();\n        task.setTaskType(TaskType.SIMPLE.name());\n        task.setTaskDefName(\"task\");\n        task.setReferenceTaskName(\"task_ref\");\n        task.setWorkflowInstanceId(workflowInstanceId);\n        task.setScheduledTime(System.currentTimeMillis());\n        task.setTaskId(idGenerator.generate());\n        task.setStatus(TaskModel.Status.COMPLETED);\n        task.setRetryCount(0);\n        task.setWorkflowTask(new WorkflowTask());\n        task.setOutputData(new HashMap<>());\n        task.setSubWorkflowId(id);\n        task.setSeq(1);\n\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.SIMPLE.name());\n        task1.setTaskDefName(\"task1\");\n        task1.setReferenceTaskName(\"task1_ref\");\n        task1.setWorkflowInstanceId(workflowInstanceId);\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setStatus(TaskModel.Status.FAILED);\n        task1.setRetryCount(0);\n        task1.setWorkflowTask(new WorkflowTask());\n        task1.setOutputData(new HashMap<>());\n        task1.setSubWorkflowId(id);\n        task1.setSeq(2);\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setWorkflowId(id);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"subworkflow\");\n        workflowDef.setVersion(1);\n        subWorkflow.setWorkflowDefinition(workflowDef);\n        subWorkflow.setStatus(WorkflowModel.Status.FAILED);\n        subWorkflow.getTasks().addAll(Arrays.asList(task, task1));\n        subWorkflow.setParentWorkflowId(\"testRunWorkflowId\");\n\n        TaskModel task2 = new TaskModel();\n        task2.setWorkflowInstanceId(subWorkflow.getWorkflowId());\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setTaskId(idGenerator.generate());\n        task2.setStatus(TaskModel.Status.FAILED);\n        task2.setRetryCount(0);\n        task2.setOutputData(new HashMap<>());\n        task2.setSubWorkflowId(id);\n        task2.setTaskType(TaskType.SUB_WORKFLOW.name());\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRunWorkflowId\");\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n        workflow.setTasks(Collections.singletonList(task2));\n        workflowDef = new WorkflowDef();\n        workflowDef.setName(\"first_workflow\");\n        workflow.setWorkflowDefinition(workflowDef);\n\n        // when\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        when(executionDAOFacade.getWorkflowModel(task.getSubWorkflowId(), true))\n                .thenReturn(subWorkflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(workflowDef));\n        when(executionDAOFacade.getTaskModel(subWorkflow.getParentWorkflowTaskId()))\n                .thenReturn(task1);\n        when(executionDAOFacade.getWorkflowModel(subWorkflow.getParentWorkflowId(), false))\n                .thenReturn(workflow);\n\n        workflowExecutor.retry(workflow.getWorkflowId(), true);\n\n        // then\n        assertEquals(task.getStatus(), TaskModel.Status.COMPLETED);\n        assertEquals(task1.getStatus(), TaskModel.Status.IN_PROGRESS);\n        assertEquals(workflow.getPreviousStatus(), WorkflowModel.Status.FAILED);\n        assertEquals(workflow.getStatus(), WorkflowModel.Status.RUNNING);\n        assertEquals(subWorkflow.getPreviousStatus(), WorkflowModel.Status.FAILED);\n        assertEquals(subWorkflow.getStatus(), WorkflowModel.Status.RUNNING);\n    }\n\n    @Test\n    public void testRetryTimedOutWorkflowWithoutFailedTasks() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRetryWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.TIMED_OUT);\n\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(20);\n        task_1_1.setRetryCount(1);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.COMPLETED);\n        task_1_1.setRetried(true);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setWorkflowTask(new WorkflowTask());\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_2_1 = new TaskModel();\n        task_2_1.setTaskId(UUID.randomUUID().toString());\n        task_2_1.setSeq(22);\n        task_2_1.setRetryCount(1);\n        task_2_1.setStatus(TaskModel.Status.COMPLETED);\n        task_2_1.setTaskType(TaskType.SIMPLE.toString());\n        task_2_1.setTaskDefName(\"task2\");\n        task_2_1.setWorkflowTask(new WorkflowTask());\n        task_2_1.setReferenceTaskName(\"task2_ref1\");\n\n        workflow.getTasks().addAll(Arrays.asList(task_1_1, task_2_1));\n\n        AtomicInteger updateWorkflowCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateWorkflowCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateWorkflow(any());\n\n        AtomicInteger updateTasksCalledCounter = new AtomicInteger(0);\n        doAnswer(\n                        invocation -> {\n                            updateTasksCalledCounter.incrementAndGet();\n                            return null;\n                        })\n                .when(executionDAOFacade)\n                .updateTasks(any());\n        // end of setup\n\n        // when\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n\n        workflowExecutor.retry(workflow.getWorkflowId(), false);\n\n        // then\n        assertEquals(WorkflowModel.Status.TIMED_OUT, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertTrue(workflow.getLastRetriedTime() > 0);\n        assertEquals(1, updateWorkflowCalledCounter.get());\n        assertEquals(1, updateTasksCalledCounter.get());\n    }\n\n    @Test(expected = ConflictException.class)\n    public void testRerunNonTerminalWorkflow() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryNonTerminalWorkflow\");\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        RerunWorkflowRequest rerunWorkflowRequest = new RerunWorkflowRequest();\n        rerunWorkflowRequest.setReRunFromWorkflowId(workflow.getWorkflowId());\n        workflowExecutor.rerun(rerunWorkflowRequest);\n    }\n\n    @Test\n    public void testRerunWorkflow() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRerunWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRerunWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n        workflow.setReasonForIncompletion(\"task1 failed\");\n        workflow.setFailedReferenceTaskNames(\n                new HashSet<>() {\n                    {\n                        add(\"task1_ref1\");\n                    }\n                });\n        workflow.setFailedTaskNames(\n                new HashSet<>() {\n                    {\n                        add(\"task1\");\n                    }\n                });\n\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(20);\n        task_1_1.setRetryCount(1);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.FAILED);\n        task_1_1.setRetried(true);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setWorkflowTask(new WorkflowTask());\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_2_1 = new TaskModel();\n        task_2_1.setTaskId(UUID.randomUUID().toString());\n        task_2_1.setSeq(22);\n        task_2_1.setRetryCount(1);\n        task_2_1.setStatus(TaskModel.Status.CANCELED);\n        task_2_1.setTaskType(TaskType.SIMPLE.toString());\n        task_2_1.setTaskDefName(\"task2\");\n        task_2_1.setWorkflowTask(new WorkflowTask());\n        task_2_1.setReferenceTaskName(\"task2_ref1\");\n\n        workflow.getTasks().addAll(Arrays.asList(task_1_1, task_2_1));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n        RerunWorkflowRequest rerunWorkflowRequest = new RerunWorkflowRequest();\n        rerunWorkflowRequest.setReRunFromWorkflowId(workflow.getWorkflowId());\n        workflowExecutor.rerun(rerunWorkflowRequest);\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertNull(workflow.getReasonForIncompletion());\n        assertEquals(new HashSet<>(), workflow.getFailedReferenceTaskNames());\n        assertEquals(new HashSet<>(), workflow.getFailedTaskNames());\n    }\n\n    @Test\n    public void testRerunSubWorkflow() {\n        IDGenerator idGenerator = new IDGenerator();\n        // setup\n        String parentWorkflowId = idGenerator.generate();\n        String subWorkflowId = idGenerator.generate();\n\n        // sub workflow setup\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.SIMPLE.name());\n        task1.setTaskDefName(\"task1\");\n        task1.setReferenceTaskName(\"task1_ref\");\n        task1.setWorkflowInstanceId(subWorkflowId);\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setStatus(TaskModel.Status.COMPLETED);\n        task1.setWorkflowTask(new WorkflowTask());\n        task1.setOutputData(new HashMap<>());\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(TaskType.SIMPLE.name());\n        task2.setTaskDefName(\"task2\");\n        task2.setReferenceTaskName(\"task2_ref\");\n        task2.setWorkflowInstanceId(subWorkflowId);\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setTaskId(idGenerator.generate());\n        task2.setStatus(TaskModel.Status.COMPLETED);\n        task2.setWorkflowTask(new WorkflowTask());\n        task2.setOutputData(new HashMap<>());\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setParentWorkflowId(parentWorkflowId);\n        subWorkflow.setWorkflowId(subWorkflowId);\n        WorkflowDef subworkflowDef = new WorkflowDef();\n        subworkflowDef.setName(\"subworkflow\");\n        subworkflowDef.setVersion(1);\n        subWorkflow.setWorkflowDefinition(subworkflowDef);\n        subWorkflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        subWorkflow.setStatus(WorkflowModel.Status.COMPLETED);\n        subWorkflow.getTasks().addAll(Arrays.asList(task1, task2));\n\n        // parent workflow setup\n        TaskModel task = new TaskModel();\n        task.setWorkflowInstanceId(parentWorkflowId);\n        task.setScheduledTime(System.currentTimeMillis());\n        task.setTaskId(idGenerator.generate());\n        task.setStatus(TaskModel.Status.COMPLETED);\n        task.setOutputData(new HashMap<>());\n        task.setSubWorkflowId(subWorkflowId);\n        task.setTaskType(TaskType.SUB_WORKFLOW.name());\n        task.setWorkflowTask(new WorkflowTask());\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(parentWorkflowId);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"parentworkflow\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        workflow.getTasks().addAll(Arrays.asList(task));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        when(executionDAOFacade.getWorkflowModel(task.getSubWorkflowId(), true))\n                .thenReturn(subWorkflow);\n        when(executionDAOFacade.getTaskModel(subWorkflow.getParentWorkflowTaskId()))\n                .thenReturn(task);\n        when(executionDAOFacade.getWorkflowModel(subWorkflow.getParentWorkflowId(), false))\n                .thenReturn(workflow);\n\n        RerunWorkflowRequest rerunWorkflowRequest = new RerunWorkflowRequest();\n        rerunWorkflowRequest.setReRunFromWorkflowId(subWorkflow.getWorkflowId());\n        workflowExecutor.rerun(rerunWorkflowRequest);\n\n        // then:\n        assertEquals(TaskModel.Status.IN_PROGRESS, task.getStatus());\n        assertEquals(WorkflowModel.Status.COMPLETED, subWorkflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, subWorkflow.getStatus());\n        assertEquals(WorkflowModel.Status.COMPLETED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n    }\n\n    @Test\n    public void testRerunWorkflowWithTaskId() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRerunWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n        workflow.setReasonForIncompletion(\"task1 failed\");\n        workflow.setFailedReferenceTaskNames(\n                new HashSet<>() {\n                    {\n                        add(\"task1_ref1\");\n                    }\n                });\n        workflow.setFailedTaskNames(\n                new HashSet<>() {\n                    {\n                        add(\"task1\");\n                    }\n                });\n        TaskModel task_1_1 = new TaskModel();\n        task_1_1.setTaskId(UUID.randomUUID().toString());\n        task_1_1.setSeq(20);\n        task_1_1.setRetryCount(1);\n        task_1_1.setTaskType(TaskType.SIMPLE.toString());\n        task_1_1.setStatus(TaskModel.Status.FAILED);\n        task_1_1.setRetried(true);\n        task_1_1.setTaskDefName(\"task1\");\n        task_1_1.setWorkflowTask(new WorkflowTask());\n        task_1_1.setReferenceTaskName(\"task1_ref1\");\n\n        TaskModel task_2_1 = new TaskModel();\n        task_2_1.setTaskId(UUID.randomUUID().toString());\n        task_2_1.setSeq(22);\n        task_2_1.setRetryCount(1);\n        task_2_1.setStatus(TaskModel.Status.CANCELED);\n        task_2_1.setTaskType(TaskType.SIMPLE.toString());\n        task_2_1.setTaskDefName(\"task2\");\n        task_2_1.setWorkflowTask(new WorkflowTask());\n        task_2_1.setReferenceTaskName(\"task2_ref1\");\n\n        workflow.getTasks().addAll(Arrays.asList(task_1_1, task_2_1));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n        when(metadataDAO.getWorkflowDef(anyString(), anyInt()))\n                .thenReturn(Optional.of(new WorkflowDef()));\n        RerunWorkflowRequest rerunWorkflowRequest = new RerunWorkflowRequest();\n        rerunWorkflowRequest.setReRunFromWorkflowId(workflow.getWorkflowId());\n        rerunWorkflowRequest.setReRunFromTaskId(task_1_1.getTaskId());\n        workflowExecutor.rerun(rerunWorkflowRequest);\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertNull(workflow.getReasonForIncompletion());\n        assertEquals(new HashSet<>(), workflow.getFailedReferenceTaskNames());\n        assertEquals(new HashSet<>(), workflow.getFailedTaskNames());\n    }\n\n    @Test\n    public void testRerunWorkflowWithSyncSystemTaskId() {\n        IDGenerator idGenerator = new IDGenerator();\n        // setup\n        String workflowId = idGenerator.generate();\n\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.SIMPLE.name());\n        task1.setTaskDefName(\"task1\");\n        task1.setReferenceTaskName(\"task1_ref\");\n        task1.setWorkflowInstanceId(workflowId);\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setStatus(TaskModel.Status.COMPLETED);\n        task1.setWorkflowTask(new WorkflowTask());\n        task1.setOutputData(new HashMap<>());\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(TaskType.JSON_JQ_TRANSFORM.name());\n        task2.setReferenceTaskName(\"task2_ref\");\n        task2.setWorkflowInstanceId(workflowId);\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setTaskId(\"system-task-id\");\n        task2.setStatus(TaskModel.Status.FAILED);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"workflow\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n        workflow.setReasonForIncompletion(\"task2 failed\");\n        workflow.setFailedReferenceTaskNames(\n                new HashSet<>() {\n                    {\n                        add(\"task2_ref\");\n                    }\n                });\n        workflow.setFailedTaskNames(\n                new HashSet<>() {\n                    {\n                        add(\"task2\");\n                    }\n                });\n        workflow.getTasks().addAll(Arrays.asList(task1, task2));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        RerunWorkflowRequest rerunWorkflowRequest = new RerunWorkflowRequest();\n        rerunWorkflowRequest.setReRunFromWorkflowId(workflow.getWorkflowId());\n        rerunWorkflowRequest.setReRunFromTaskId(task2.getTaskId());\n        workflowExecutor.rerun(rerunWorkflowRequest);\n\n        // then:\n        assertEquals(TaskModel.Status.COMPLETED, task2.getStatus());\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertNull(workflow.getReasonForIncompletion());\n        assertEquals(new HashSet<>(), workflow.getFailedReferenceTaskNames());\n        assertEquals(new HashSet<>(), workflow.getFailedTaskNames());\n    }\n\n    @Test\n    public void testRerunSubWorkflowWithTaskId() {\n        IDGenerator idGenerator = new IDGenerator();\n\n        // setup\n        String parentWorkflowId = idGenerator.generate();\n        String subWorkflowId = idGenerator.generate();\n\n        // sub workflow setup\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.SIMPLE.name());\n        task1.setTaskDefName(\"task1\");\n        task1.setReferenceTaskName(\"task1_ref\");\n        task1.setWorkflowInstanceId(subWorkflowId);\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setStatus(TaskModel.Status.COMPLETED);\n        task1.setWorkflowTask(new WorkflowTask());\n        task1.setOutputData(new HashMap<>());\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(TaskType.SIMPLE.name());\n        task2.setTaskDefName(\"task2\");\n        task2.setReferenceTaskName(\"task2_ref\");\n        task2.setWorkflowInstanceId(subWorkflowId);\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setTaskId(idGenerator.generate());\n        task2.setStatus(TaskModel.Status.COMPLETED);\n        task2.setWorkflowTask(new WorkflowTask());\n        task2.setOutputData(new HashMap<>());\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setParentWorkflowId(parentWorkflowId);\n        subWorkflow.setWorkflowId(subWorkflowId);\n        WorkflowDef subworkflowDef = new WorkflowDef();\n        subworkflowDef.setName(\"subworkflow\");\n        subworkflowDef.setVersion(1);\n        subWorkflow.setWorkflowDefinition(subworkflowDef);\n        subWorkflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        subWorkflow.setStatus(WorkflowModel.Status.COMPLETED);\n        subWorkflow.getTasks().addAll(Arrays.asList(task1, task2));\n\n        // parent workflow setup\n        TaskModel task = new TaskModel();\n        task.setWorkflowInstanceId(parentWorkflowId);\n        task.setScheduledTime(System.currentTimeMillis());\n        task.setTaskId(idGenerator.generate());\n        task.setStatus(TaskModel.Status.COMPLETED);\n        task.setOutputData(new HashMap<>());\n        task.setSubWorkflowId(subWorkflowId);\n        task.setTaskType(TaskType.SUB_WORKFLOW.name());\n        task.setWorkflowTask(new WorkflowTask());\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(parentWorkflowId);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"parentworkflow\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        workflow.getTasks().addAll(Arrays.asList(task));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        when(executionDAOFacade.getWorkflowModel(task.getSubWorkflowId(), true))\n                .thenReturn(subWorkflow);\n        when(executionDAOFacade.getTaskModel(subWorkflow.getParentWorkflowTaskId()))\n                .thenReturn(task);\n        when(executionDAOFacade.getWorkflowModel(subWorkflow.getParentWorkflowId(), false))\n                .thenReturn(workflow);\n\n        RerunWorkflowRequest rerunWorkflowRequest = new RerunWorkflowRequest();\n        rerunWorkflowRequest.setReRunFromWorkflowId(subWorkflow.getWorkflowId());\n        rerunWorkflowRequest.setReRunFromTaskId(task2.getTaskId());\n        workflowExecutor.rerun(rerunWorkflowRequest);\n\n        // then:\n        assertEquals(TaskModel.Status.SCHEDULED, task2.getStatus());\n        assertEquals(TaskModel.Status.IN_PROGRESS, task.getStatus());\n        assertEquals(WorkflowModel.Status.COMPLETED, subWorkflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, subWorkflow.getStatus());\n        assertEquals(WorkflowModel.Status.COMPLETED, workflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n    }\n\n    @Test\n    public void testGetActiveDomain() throws Exception {\n        String taskType = \"test-task\";\n        String[] domains = new String[] {\"domain1\", \"domain2\"};\n\n        PollData pollData1 =\n                new PollData(\n                        \"queue1\", domains[0], \"worker1\", System.currentTimeMillis() - 99 * 1000);\n        when(executionDAOFacade.getTaskPollDataByDomain(taskType, domains[0]))\n                .thenReturn(pollData1);\n        String activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertEquals(domains[0], activeDomain);\n        Thread.sleep(2000L);\n\n        PollData pollData2 =\n                new PollData(\n                        \"queue2\", domains[1], \"worker2\", System.currentTimeMillis() - 99 * 1000);\n        when(executionDAOFacade.getTaskPollDataByDomain(taskType, domains[1]))\n                .thenReturn(pollData2);\n        activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertEquals(domains[1], activeDomain);\n\n        Thread.sleep(2000L);\n        activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertEquals(domains[1], activeDomain);\n\n        domains = new String[] {\"\"};\n        when(executionDAOFacade.getTaskPollDataByDomain(any(), any())).thenReturn(new PollData());\n        activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertNotNull(activeDomain);\n        assertEquals(\"\", activeDomain);\n\n        domains = new String[] {};\n        activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertNull(activeDomain);\n\n        activeDomain = workflowExecutor.getActiveDomain(taskType, null);\n        assertNull(activeDomain);\n\n        domains = new String[] {\"test-domain\"};\n        when(executionDAOFacade.getTaskPollDataByDomain(anyString(), anyString())).thenReturn(null);\n        activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertNotNull(activeDomain);\n        assertEquals(\"test-domain\", activeDomain);\n    }\n\n    @Test\n    public void testInactiveDomains() {\n        String taskType = \"test-task\";\n        String[] domains = new String[] {\"domain1\", \"domain2\"};\n\n        PollData pollData1 =\n                new PollData(\n                        \"queue1\", domains[0], \"worker1\", System.currentTimeMillis() - 99 * 10000);\n        when(executionDAOFacade.getTaskPollDataByDomain(taskType, domains[0]))\n                .thenReturn(pollData1);\n        when(executionDAOFacade.getTaskPollDataByDomain(taskType, domains[1])).thenReturn(null);\n        String activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertEquals(\"domain2\", activeDomain);\n    }\n\n    @Test\n    public void testDefaultDomain() {\n        String taskType = \"test-task\";\n        String[] domains = new String[] {\"domain1\", \"domain2\", \"NO_DOMAIN\"};\n\n        PollData pollData1 =\n                new PollData(\n                        \"queue1\", domains[0], \"worker1\", System.currentTimeMillis() - 99 * 10000);\n        when(executionDAOFacade.getTaskPollDataByDomain(taskType, domains[0]))\n                .thenReturn(pollData1);\n        when(executionDAOFacade.getTaskPollDataByDomain(taskType, domains[1])).thenReturn(null);\n        String activeDomain = workflowExecutor.getActiveDomain(taskType, domains);\n        assertNull(activeDomain);\n    }\n\n    @Test\n    public void testTaskToDomain() {\n        WorkflowModel workflow = generateSampleWorkflow();\n        List<TaskModel> tasks = generateSampleTasks(3);\n\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(\"*\", \"mydomain\");\n        workflow.setTaskToDomain(taskToDomain);\n\n        PollData pollData1 =\n                new PollData(\n                        \"queue1\", \"mydomain\", \"worker1\", System.currentTimeMillis() - 99 * 100);\n        when(executionDAOFacade.getTaskPollDataByDomain(anyString(), anyString()))\n                .thenReturn(pollData1);\n        workflowExecutor.setTaskDomains(tasks, workflow);\n\n        assertNotNull(tasks);\n        tasks.forEach(task -> assertEquals(\"mydomain\", task.getDomain()));\n    }\n\n    @Test\n    public void testTaskToDomainsPerTask() {\n        WorkflowModel workflow = generateSampleWorkflow();\n        List<TaskModel> tasks = generateSampleTasks(2);\n\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(\"*\", \"mydomain, NO_DOMAIN\");\n        workflow.setTaskToDomain(taskToDomain);\n\n        PollData pollData1 =\n                new PollData(\n                        \"queue1\", \"mydomain\", \"worker1\", System.currentTimeMillis() - 99 * 100);\n        when(executionDAOFacade.getTaskPollDataByDomain(eq(\"task1\"), anyString()))\n                .thenReturn(pollData1);\n        when(executionDAOFacade.getTaskPollDataByDomain(eq(\"task2\"), anyString())).thenReturn(null);\n        workflowExecutor.setTaskDomains(tasks, workflow);\n\n        assertEquals(\"mydomain\", tasks.get(0).getDomain());\n        assertNull(tasks.get(1).getDomain());\n    }\n\n    @Test\n    public void testTaskToDomainOverrides() {\n        WorkflowModel workflow = generateSampleWorkflow();\n        List<TaskModel> tasks = generateSampleTasks(4);\n\n        Map<String, String> taskToDomain = new HashMap<>();\n        taskToDomain.put(\"*\", \"mydomain\");\n        taskToDomain.put(\"task2\", \"someInactiveDomain, NO_DOMAIN\");\n        taskToDomain.put(\"task3\", \"someActiveDomain, NO_DOMAIN\");\n        taskToDomain.put(\"task4\", \"someInactiveDomain, someInactiveDomain2\");\n        workflow.setTaskToDomain(taskToDomain);\n\n        PollData pollData1 =\n                new PollData(\n                        \"queue1\", \"mydomain\", \"worker1\", System.currentTimeMillis() - 99 * 100);\n        PollData pollData2 =\n                new PollData(\n                        \"queue2\",\n                        \"someActiveDomain\",\n                        \"worker2\",\n                        System.currentTimeMillis() - 99 * 100);\n        when(executionDAOFacade.getTaskPollDataByDomain(anyString(), eq(\"mydomain\")))\n                .thenReturn(pollData1);\n        when(executionDAOFacade.getTaskPollDataByDomain(anyString(), eq(\"someInactiveDomain\")))\n                .thenReturn(null);\n        when(executionDAOFacade.getTaskPollDataByDomain(anyString(), eq(\"someActiveDomain\")))\n                .thenReturn(pollData2);\n        when(executionDAOFacade.getTaskPollDataByDomain(anyString(), eq(\"someInactiveDomain\")))\n                .thenReturn(null);\n        workflowExecutor.setTaskDomains(tasks, workflow);\n\n        assertEquals(\"mydomain\", tasks.get(0).getDomain());\n        assertNull(tasks.get(1).getDomain());\n        assertEquals(\"someActiveDomain\", tasks.get(2).getDomain());\n        assertEquals(\"someInactiveDomain2\", tasks.get(3).getDomain());\n    }\n\n    @Test\n    public void testDedupAndAddTasks() {\n        WorkflowModel workflow = new WorkflowModel();\n\n        TaskModel task1 = new TaskModel();\n        task1.setReferenceTaskName(\"task1\");\n        task1.setRetryCount(1);\n\n        TaskModel task2 = new TaskModel();\n        task2.setReferenceTaskName(\"task2\");\n        task2.setRetryCount(2);\n\n        List<TaskModel> tasks = new ArrayList<>(Arrays.asList(task1, task2));\n\n        List<TaskModel> taskList = workflowExecutor.dedupAndAddTasks(workflow, tasks);\n        assertEquals(2, taskList.size());\n        assertEquals(tasks, taskList);\n        assertEquals(workflow.getTasks(), taskList);\n\n        // Adding the same tasks again\n        taskList = workflowExecutor.dedupAndAddTasks(workflow, tasks);\n        assertEquals(0, taskList.size());\n        assertEquals(workflow.getTasks(), tasks);\n\n        // Adding 2 new tasks\n        TaskModel newTask = new TaskModel();\n        newTask.setReferenceTaskName(\"newTask\");\n        newTask.setRetryCount(0);\n\n        taskList = workflowExecutor.dedupAndAddTasks(workflow, Collections.singletonList(newTask));\n        assertEquals(1, taskList.size());\n        assertEquals(newTask, taskList.get(0));\n        assertEquals(3, workflow.getTasks().size());\n    }\n\n    @Test(expected = ConflictException.class)\n    public void testTerminateCompletedWorkflow() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testTerminateTerminalWorkflow\");\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        when(executionDAOFacade.getWorkflowModel(anyString(), anyBoolean())).thenReturn(workflow);\n\n        workflowExecutor.terminateWorkflow(\n                workflow.getWorkflowId(), \"test terminating terminal workflow\");\n    }\n\n    @Test\n    public void testResetCallbacksForWorkflowTasks() {\n        String workflowId = \"test-workflow-id\";\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n\n        TaskModel completedTask = new TaskModel();\n        completedTask.setTaskType(TaskType.SIMPLE.name());\n        completedTask.setReferenceTaskName(\"completedTask\");\n        completedTask.setWorkflowInstanceId(workflowId);\n        completedTask.setScheduledTime(System.currentTimeMillis());\n        completedTask.setCallbackAfterSeconds(300);\n        completedTask.setTaskId(\"simple-task-id\");\n        completedTask.setStatus(TaskModel.Status.COMPLETED);\n\n        TaskModel systemTask = new TaskModel();\n        systemTask.setTaskType(TaskType.WAIT.name());\n        systemTask.setReferenceTaskName(\"waitTask\");\n        systemTask.setWorkflowInstanceId(workflowId);\n        systemTask.setScheduledTime(System.currentTimeMillis());\n        systemTask.setTaskId(\"system-task-id\");\n        systemTask.setStatus(TaskModel.Status.SCHEDULED);\n\n        TaskModel simpleTask = new TaskModel();\n        simpleTask.setTaskType(TaskType.SIMPLE.name());\n        simpleTask.setReferenceTaskName(\"simpleTask\");\n        simpleTask.setWorkflowInstanceId(workflowId);\n        simpleTask.setScheduledTime(System.currentTimeMillis());\n        simpleTask.setCallbackAfterSeconds(300);\n        simpleTask.setTaskId(\"simple-task-id\");\n        simpleTask.setStatus(TaskModel.Status.SCHEDULED);\n\n        TaskModel noCallbackTask = new TaskModel();\n        noCallbackTask.setTaskType(TaskType.SIMPLE.name());\n        noCallbackTask.setReferenceTaskName(\"noCallbackTask\");\n        noCallbackTask.setWorkflowInstanceId(workflowId);\n        noCallbackTask.setScheduledTime(System.currentTimeMillis());\n        noCallbackTask.setCallbackAfterSeconds(0);\n        noCallbackTask.setTaskId(\"no-callback-task-id\");\n        noCallbackTask.setStatus(TaskModel.Status.SCHEDULED);\n\n        workflow.getTasks()\n                .addAll(Arrays.asList(completedTask, systemTask, simpleTask, noCallbackTask));\n        when(executionDAOFacade.getWorkflowModel(workflowId, true)).thenReturn(workflow);\n\n        workflowExecutor.resetCallbacksForWorkflow(workflowId);\n        verify(queueDAO, times(1)).resetOffsetTime(anyString(), anyString());\n    }\n\n    @Test\n    public void testUpdateParentWorkflowTask() {\n        String parentWorkflowTaskId = \"parent_workflow_task_id\";\n        String workflowId = \"workflow_id\";\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setWorkflowId(workflowId);\n        subWorkflow.setParentWorkflowTaskId(parentWorkflowTaskId);\n        subWorkflow.setStatus(WorkflowModel.Status.COMPLETED);\n\n        TaskModel subWorkflowTask = new TaskModel();\n        subWorkflowTask.setSubWorkflowId(workflowId);\n        subWorkflowTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        subWorkflowTask.setExternalOutputPayloadStoragePath(null);\n\n        when(executionDAOFacade.getTaskModel(parentWorkflowTaskId)).thenReturn(subWorkflowTask);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(subWorkflow);\n\n        workflowExecutor.updateParentWorkflowTask(subWorkflow);\n        ArgumentCaptor<TaskModel> argumentCaptor = ArgumentCaptor.forClass(TaskModel.class);\n        verify(executionDAOFacade, times(1)).updateTask(argumentCaptor.capture());\n        assertEquals(TaskModel.Status.COMPLETED, argumentCaptor.getAllValues().get(0).getStatus());\n        assertEquals(workflowId, argumentCaptor.getAllValues().get(0).getSubWorkflowId());\n    }\n\n    @Test\n    public void testScheduleNextIteration() {\n        WorkflowModel workflow = generateSampleWorkflow();\n        workflow.setTaskToDomain(\n                new HashMap<>() {\n                    {\n                        put(\"TEST\", \"domain1\");\n                    }\n                });\n        TaskModel loopTask = mock(TaskModel.class);\n        WorkflowTask loopWfTask = mock(WorkflowTask.class);\n        when(loopTask.getWorkflowTask()).thenReturn(loopWfTask);\n        List<WorkflowTask> loopOver =\n                new ArrayList<>() {\n                    {\n                        WorkflowTask workflowTask = new WorkflowTask();\n                        workflowTask.setType(TaskType.TASK_TYPE_SIMPLE);\n                        workflowTask.setName(\"TEST\");\n                        workflowTask.setTaskDefinition(new TaskDef());\n                        add(workflowTask);\n                    }\n                };\n        when(loopWfTask.getLoopOver()).thenReturn(loopOver);\n\n        workflowExecutor.scheduleNextIteration(loopTask, workflow);\n        verify(executionDAOFacade).getTaskPollDataByDomain(\"TEST\", \"domain1\");\n    }\n\n    @Test\n    public void testCancelNonTerminalTasks() {\n        WorkflowDef def = new WorkflowDef();\n        def.setWorkflowStatusListenerEnabled(true);\n\n        WorkflowModel workflow = generateSampleWorkflow();\n        workflow.setWorkflowDefinition(def);\n\n        TaskModel subWorkflowTask = new TaskModel();\n        subWorkflowTask.setTaskId(UUID.randomUUID().toString());\n        subWorkflowTask.setTaskType(TaskType.SUB_WORKFLOW.name());\n        subWorkflowTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        TaskModel lambdaTask = new TaskModel();\n        lambdaTask.setTaskId(UUID.randomUUID().toString());\n        lambdaTask.setTaskType(TaskType.LAMBDA.name());\n        lambdaTask.setStatus(TaskModel.Status.SCHEDULED);\n\n        TaskModel simpleTask = new TaskModel();\n        simpleTask.setTaskId(UUID.randomUUID().toString());\n        simpleTask.setTaskType(TaskType.SIMPLE.name());\n        simpleTask.setStatus(TaskModel.Status.COMPLETED);\n\n        workflow.getTasks().addAll(Arrays.asList(subWorkflowTask, lambdaTask, simpleTask));\n\n        List<String> erroredTasks = workflowExecutor.cancelNonTerminalTasks(workflow);\n        assertTrue(erroredTasks.isEmpty());\n        ArgumentCaptor<TaskModel> argumentCaptor = ArgumentCaptor.forClass(TaskModel.class);\n        verify(executionDAOFacade, times(2)).updateTask(argumentCaptor.capture());\n        assertEquals(2, argumentCaptor.getAllValues().size());\n        assertEquals(\n                TaskType.SUB_WORKFLOW.name(), argumentCaptor.getAllValues().get(0).getTaskType());\n        assertEquals(TaskModel.Status.CANCELED, argumentCaptor.getAllValues().get(0).getStatus());\n        assertEquals(TaskType.LAMBDA.name(), argumentCaptor.getAllValues().get(1).getTaskType());\n        assertEquals(TaskModel.Status.CANCELED, argumentCaptor.getAllValues().get(1).getStatus());\n        verify(workflowStatusListener, times(1))\n                .onWorkflowFinalizedIfEnabled(any(WorkflowModel.class));\n    }\n\n    @Test\n    public void testPauseWorkflow() {\n        when(executionLockService.acquireLock(anyString(), anyLong())).thenReturn(true);\n        doNothing().when(executionLockService).releaseLock(anyString());\n\n        String workflowId = \"testPauseWorkflowId\";\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n\n        // if workflow is in terminal state\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(workflow);\n        try {\n            workflowExecutor.pauseWorkflow(workflowId);\n            fail(\"Expected \" + ConflictException.class);\n        } catch (ConflictException e) {\n            verify(executionDAOFacade, never()).updateWorkflow(any(WorkflowModel.class));\n            verify(queueDAO, never()).remove(anyString(), anyString());\n        }\n\n        // if workflow is already PAUSED\n        workflow.setStatus(WorkflowModel.Status.PAUSED);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(workflow);\n        workflowExecutor.pauseWorkflow(workflowId);\n        assertEquals(WorkflowModel.Status.PAUSED, workflow.getStatus());\n        verify(executionDAOFacade, never()).updateWorkflow(any(WorkflowModel.class));\n        verify(queueDAO, never()).remove(anyString(), anyString());\n\n        // if workflow is RUNNING\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(workflow);\n        workflowExecutor.pauseWorkflow(workflowId);\n        assertEquals(WorkflowModel.Status.PAUSED, workflow.getStatus());\n        verify(executionDAOFacade, times(1)).updateWorkflow(any(WorkflowModel.class));\n        verify(queueDAO, times(1)).remove(anyString(), anyString());\n    }\n\n    @Test\n    public void testResumeWorkflow() {\n        String workflowId = \"testResumeWorkflowId\";\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n\n        // if workflow is not in PAUSED state\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(workflow);\n        try {\n            workflowExecutor.resumeWorkflow(workflowId);\n        } catch (Exception e) {\n            assertTrue(e instanceof IllegalStateException);\n            verify(executionDAOFacade, never()).updateWorkflow(any(WorkflowModel.class));\n            verify(queueDAO, never()).push(anyString(), anyString(), anyInt(), anyLong());\n        }\n\n        // if workflow is in PAUSED state\n        workflow.setStatus(WorkflowModel.Status.PAUSED);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(workflow);\n        workflowExecutor.resumeWorkflow(workflowId);\n        assertEquals(WorkflowModel.Status.RUNNING, workflow.getStatus());\n        assertTrue(workflow.getLastRetriedTime() > 0);\n        verify(executionDAOFacade, times(1)).updateWorkflow(any(WorkflowModel.class));\n        verify(queueDAO, times(1)).push(anyString(), anyString(), anyInt(), anyLong());\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    public void testTerminateWorkflowWithFailureWorkflow() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"workflow\");\n        workflowDef.setFailureWorkflow(\"failure_workflow\");\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"1\");\n        workflow.setCorrelationId(\"testid\");\n        workflow.setWorkflowDefinition(new WorkflowDef());\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setOwnerApp(\"junit_test\");\n        workflow.setEndTime(100L);\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskModel successTask = new TaskModel();\n        successTask.setTaskId(\"taskid1\");\n        successTask.setReferenceTaskName(\"success\");\n        successTask.setStatus(TaskModel.Status.COMPLETED);\n\n        TaskModel failedTask = new TaskModel();\n        failedTask.setTaskId(\"taskid2\");\n        failedTask.setReferenceTaskName(\"failed\");\n        failedTask.setStatus(TaskModel.Status.FAILED);\n        workflow.getTasks().addAll(Arrays.asList(successTask, failedTask));\n\n        WorkflowDef failureWorkflowDef = new WorkflowDef();\n        failureWorkflowDef.setName(\"failure_workflow\");\n        when(metadataDAO.getLatestWorkflowDef(failureWorkflowDef.getName()))\n                .thenReturn(Optional.of(failureWorkflowDef));\n\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        when(executionLockService.acquireLock(anyString())).thenReturn(true);\n\n        workflowExecutor.decide(workflow.getWorkflowId());\n\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getStatus());\n        ArgumentCaptor<WorkflowCreationEvent> argumentCaptor =\n                ArgumentCaptor.forClass(WorkflowCreationEvent.class);\n        verify(eventPublisher, times(1)).publishEvent(argumentCaptor.capture());\n        StartWorkflowInput startWorkflowInput = argumentCaptor.getValue().getStartWorkflowInput();\n        assertEquals(workflow.getCorrelationId(), startWorkflowInput.getCorrelationId());\n        assertEquals(\n                workflow.getWorkflowId(), startWorkflowInput.getWorkflowInput().get(\"workflowId\"));\n        assertEquals(\n                failedTask.getTaskId(), startWorkflowInput.getWorkflowInput().get(\"failureTaskId\"));\n        assertNotNull(\n                failedTask.getTaskId(),\n                startWorkflowInput.getWorkflowInput().get(\"failedWorkflow\"));\n    }\n\n    @Test\n    public void testRerunOptionalSubWorkflow() {\n        IDGenerator idGenerator = new IDGenerator();\n        // setup\n        String parentWorkflowId = idGenerator.generate();\n        String subWorkflowId = idGenerator.generate();\n\n        // sub workflow setup\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.SIMPLE.name());\n        task1.setTaskDefName(\"task1\");\n        task1.setReferenceTaskName(\"task1_ref\");\n        task1.setWorkflowInstanceId(subWorkflowId);\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setStatus(TaskModel.Status.COMPLETED);\n        task1.setWorkflowTask(new WorkflowTask());\n        task1.setOutputData(new HashMap<>());\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(TaskType.SIMPLE.name());\n        task2.setTaskDefName(\"task2\");\n        task2.setReferenceTaskName(\"task2_ref\");\n        task2.setWorkflowInstanceId(subWorkflowId);\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setTaskId(idGenerator.generate());\n        task2.setStatus(TaskModel.Status.FAILED);\n        task2.setWorkflowTask(new WorkflowTask());\n        task2.setOutputData(new HashMap<>());\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setParentWorkflowId(parentWorkflowId);\n        subWorkflow.setWorkflowId(subWorkflowId);\n        WorkflowDef subworkflowDef = new WorkflowDef();\n        subworkflowDef.setName(\"subworkflow\");\n        subworkflowDef.setVersion(1);\n        subWorkflow.setWorkflowDefinition(subworkflowDef);\n        subWorkflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        subWorkflow.setStatus(WorkflowModel.Status.FAILED);\n        subWorkflow.getTasks().addAll(Arrays.asList(task1, task2));\n\n        // parent workflow setup\n        TaskModel task = new TaskModel();\n        task.setWorkflowInstanceId(parentWorkflowId);\n        task.setScheduledTime(System.currentTimeMillis());\n        task.setTaskId(idGenerator.generate());\n        task.setStatus(TaskModel.Status.COMPLETED_WITH_ERRORS);\n        task.setOutputData(new HashMap<>());\n        task.setSubWorkflowId(subWorkflowId);\n        task.setTaskType(TaskType.SUB_WORKFLOW.name());\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setOptional(true);\n        task.setWorkflowTask(workflowTask);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(parentWorkflowId);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"parentworkflow\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        workflow.getTasks().addAll(Arrays.asList(task));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        when(executionDAOFacade.getWorkflowModel(task.getSubWorkflowId(), true))\n                .thenReturn(subWorkflow);\n        when(executionDAOFacade.getTaskModel(subWorkflow.getParentWorkflowTaskId()))\n                .thenReturn(task);\n        when(executionDAOFacade.getWorkflowModel(subWorkflow.getParentWorkflowId(), false))\n                .thenReturn(workflow);\n\n        RerunWorkflowRequest rerunWorkflowRequest = new RerunWorkflowRequest();\n        rerunWorkflowRequest.setReRunFromWorkflowId(subWorkflow.getWorkflowId());\n        workflowExecutor.rerun(rerunWorkflowRequest);\n\n        // then: parent workflow remains the same\n        assertEquals(WorkflowModel.Status.FAILED, subWorkflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, subWorkflow.getStatus());\n        assertEquals(TaskModel.Status.COMPLETED_WITH_ERRORS, task.getStatus());\n        assertEquals(WorkflowModel.Status.COMPLETED, workflow.getStatus());\n    }\n\n    @Test\n    public void testRestartOptionalSubWorkflow() {\n        IDGenerator idGenerator = new IDGenerator();\n        // setup\n        String parentWorkflowId = idGenerator.generate();\n        String subWorkflowId = idGenerator.generate();\n\n        // sub workflow setup\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.SIMPLE.name());\n        task1.setTaskDefName(\"task1\");\n        task1.setReferenceTaskName(\"task1_ref\");\n        task1.setWorkflowInstanceId(subWorkflowId);\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setStatus(TaskModel.Status.COMPLETED);\n        task1.setWorkflowTask(new WorkflowTask());\n        task1.setOutputData(new HashMap<>());\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(TaskType.SIMPLE.name());\n        task2.setTaskDefName(\"task2\");\n        task2.setReferenceTaskName(\"task2_ref\");\n        task2.setWorkflowInstanceId(subWorkflowId);\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setTaskId(idGenerator.generate());\n        task2.setStatus(TaskModel.Status.FAILED);\n        task2.setWorkflowTask(new WorkflowTask());\n        task2.setOutputData(new HashMap<>());\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setParentWorkflowId(parentWorkflowId);\n        subWorkflow.setWorkflowId(subWorkflowId);\n        WorkflowDef subworkflowDef = new WorkflowDef();\n        subworkflowDef.setName(\"subworkflow\");\n        subworkflowDef.setVersion(1);\n        subWorkflow.setWorkflowDefinition(subworkflowDef);\n        subWorkflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        subWorkflow.setStatus(WorkflowModel.Status.FAILED);\n        subWorkflow.getTasks().addAll(Arrays.asList(task1, task2));\n\n        // parent workflow setup\n        TaskModel task = new TaskModel();\n        task.setWorkflowInstanceId(parentWorkflowId);\n        task.setScheduledTime(System.currentTimeMillis());\n        task.setTaskId(idGenerator.generate());\n        task.setStatus(TaskModel.Status.COMPLETED_WITH_ERRORS);\n        task.setOutputData(new HashMap<>());\n        task.setSubWorkflowId(subWorkflowId);\n        task.setTaskType(TaskType.SUB_WORKFLOW.name());\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setOptional(true);\n        task.setWorkflowTask(workflowTask);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(parentWorkflowId);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"parentworkflow\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        workflow.getTasks().addAll(Arrays.asList(task));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        when(executionDAOFacade.getWorkflowModel(task.getSubWorkflowId(), true))\n                .thenReturn(subWorkflow);\n        when(executionDAOFacade.getTaskModel(subWorkflow.getParentWorkflowTaskId()))\n                .thenReturn(task);\n        when(executionDAOFacade.getWorkflowModel(subWorkflow.getParentWorkflowId(), false))\n                .thenReturn(workflow);\n\n        workflowExecutor.restart(subWorkflowId, false);\n\n        // then: parent workflow remains the same\n        assertEquals(WorkflowModel.Status.FAILED, subWorkflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, subWorkflow.getStatus());\n        assertEquals(TaskModel.Status.COMPLETED_WITH_ERRORS, task.getStatus());\n        assertEquals(WorkflowModel.Status.COMPLETED, workflow.getStatus());\n    }\n\n    @Test\n    public void testRetryOptionalSubWorkflow() {\n        IDGenerator idGenerator = new IDGenerator();\n        // setup\n        String parentWorkflowId = idGenerator.generate();\n        String subWorkflowId = idGenerator.generate();\n\n        // sub workflow setup\n        TaskModel task1 = new TaskModel();\n        task1.setTaskType(TaskType.SIMPLE.name());\n        task1.setTaskDefName(\"task1\");\n        task1.setReferenceTaskName(\"task1_ref\");\n        task1.setWorkflowInstanceId(subWorkflowId);\n        task1.setScheduledTime(System.currentTimeMillis());\n        task1.setTaskId(idGenerator.generate());\n        task1.setStatus(TaskModel.Status.COMPLETED);\n        task1.setWorkflowTask(new WorkflowTask());\n        task1.setOutputData(new HashMap<>());\n\n        TaskModel task2 = new TaskModel();\n        task2.setTaskType(TaskType.SIMPLE.name());\n        task2.setTaskDefName(\"task2\");\n        task2.setReferenceTaskName(\"task2_ref\");\n        task2.setWorkflowInstanceId(subWorkflowId);\n        task2.setScheduledTime(System.currentTimeMillis());\n        task2.setTaskId(idGenerator.generate());\n        task2.setStatus(TaskModel.Status.FAILED);\n        task2.setWorkflowTask(new WorkflowTask());\n        task2.setOutputData(new HashMap<>());\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setParentWorkflowId(parentWorkflowId);\n        subWorkflow.setWorkflowId(subWorkflowId);\n        WorkflowDef subworkflowDef = new WorkflowDef();\n        subworkflowDef.setName(\"subworkflow\");\n        subworkflowDef.setVersion(1);\n        subWorkflow.setWorkflowDefinition(subworkflowDef);\n        subWorkflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        subWorkflow.setStatus(WorkflowModel.Status.FAILED);\n        subWorkflow.getTasks().addAll(Arrays.asList(task1, task2));\n\n        // parent workflow setup\n        TaskModel task = new TaskModel();\n        task.setWorkflowInstanceId(parentWorkflowId);\n        task.setScheduledTime(System.currentTimeMillis());\n        task.setTaskId(idGenerator.generate());\n        task.setStatus(TaskModel.Status.COMPLETED_WITH_ERRORS);\n        task.setOutputData(new HashMap<>());\n        task.setSubWorkflowId(subWorkflowId);\n        task.setTaskType(TaskType.SUB_WORKFLOW.name());\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setOptional(true);\n        task.setWorkflowTask(workflowTask);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(parentWorkflowId);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"parentworkflow\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRerunWorkflowId\");\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        workflow.getTasks().addAll(Arrays.asList(task));\n        // end of setup\n\n        // when:\n        when(executionDAOFacade.getWorkflowModel(workflow.getWorkflowId(), true))\n                .thenReturn(workflow);\n        when(executionDAOFacade.getWorkflowModel(task.getSubWorkflowId(), true))\n                .thenReturn(subWorkflow);\n        when(executionDAOFacade.getTaskModel(subWorkflow.getParentWorkflowTaskId()))\n                .thenReturn(task);\n        when(executionDAOFacade.getWorkflowModel(subWorkflow.getParentWorkflowId(), false))\n                .thenReturn(workflow);\n\n        workflowExecutor.retry(subWorkflowId, true);\n\n        // then: parent workflow remains the same\n        assertEquals(WorkflowModel.Status.FAILED, subWorkflow.getPreviousStatus());\n        assertEquals(WorkflowModel.Status.RUNNING, subWorkflow.getStatus());\n        assertEquals(TaskModel.Status.COMPLETED_WITH_ERRORS, task.getStatus());\n        assertEquals(WorkflowModel.Status.COMPLETED, workflow.getStatus());\n    }\n\n    @Test\n    public void testUpdateTaskWithCallbackAfterSeconds() {\n        String workflowId = \"test-workflow-id\";\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setWorkflowDefinition(new WorkflowDef());\n\n        TaskModel simpleTask = new TaskModel();\n        simpleTask.setTaskType(TaskType.SIMPLE.name());\n        simpleTask.setReferenceTaskName(\"simpleTask\");\n        simpleTask.setWorkflowInstanceId(workflowId);\n        simpleTask.setScheduledTime(System.currentTimeMillis());\n        simpleTask.setCallbackAfterSeconds(0);\n        simpleTask.setTaskId(\"simple-task-id\");\n        simpleTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        workflow.getTasks().add(simpleTask);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(workflow);\n        when(executionDAOFacade.getTaskModel(simpleTask.getTaskId())).thenReturn(simpleTask);\n\n        TaskResult taskResult = new TaskResult();\n        taskResult.setWorkflowInstanceId(workflowId);\n        taskResult.setTaskId(simpleTask.getTaskId());\n        taskResult.setWorkerId(\"test-worker-id\");\n        taskResult.log(\"not ready yet\");\n        taskResult.setCallbackAfterSeconds(300);\n        taskResult.setStatus(TaskResult.Status.IN_PROGRESS);\n\n        workflowExecutor.updateTask(taskResult);\n        verify(queueDAO, times(1)).postpone(anyString(), anyString(), anyInt(), anyLong());\n        ArgumentCaptor<TaskModel> argumentCaptor = ArgumentCaptor.forClass(TaskModel.class);\n        verify(executionDAOFacade, times(1)).updateTask(argumentCaptor.capture());\n        assertEquals(TaskModel.Status.SCHEDULED, argumentCaptor.getAllValues().get(0).getStatus());\n        assertEquals(\n                taskResult.getCallbackAfterSeconds(),\n                argumentCaptor.getAllValues().get(0).getCallbackAfterSeconds());\n        assertEquals(taskResult.getWorkerId(), argumentCaptor.getAllValues().get(0).getWorkerId());\n    }\n\n    @Test\n    public void testUpdateTaskWithOutCallbackAfterSeconds() {\n        String workflowId = \"test-workflow-id\";\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        workflow.setWorkflowDefinition(new WorkflowDef());\n\n        TaskModel simpleTask = new TaskModel();\n        simpleTask.setTaskType(TaskType.SIMPLE.name());\n        simpleTask.setReferenceTaskName(\"simpleTask\");\n        simpleTask.setWorkflowInstanceId(workflowId);\n        simpleTask.setScheduledTime(System.currentTimeMillis());\n        simpleTask.setCallbackAfterSeconds(0);\n        simpleTask.setTaskId(\"simple-task-id\");\n        simpleTask.setStatus(TaskModel.Status.IN_PROGRESS);\n\n        workflow.getTasks().add(simpleTask);\n        when(executionDAOFacade.getWorkflowModel(workflowId, false)).thenReturn(workflow);\n        when(executionDAOFacade.getTaskModel(simpleTask.getTaskId())).thenReturn(simpleTask);\n\n        TaskResult taskResult = new TaskResult();\n        taskResult.setWorkflowInstanceId(workflowId);\n        taskResult.setTaskId(simpleTask.getTaskId());\n        taskResult.setWorkerId(\"test-worker-id\");\n        taskResult.log(\"not ready yet\");\n        taskResult.setStatus(TaskResult.Status.IN_PROGRESS);\n\n        workflowExecutor.updateTask(taskResult);\n        verify(queueDAO, times(1)).postpone(anyString(), anyString(), anyInt(), anyLong());\n        ArgumentCaptor<TaskModel> argumentCaptor = ArgumentCaptor.forClass(TaskModel.class);\n        verify(executionDAOFacade, times(1)).updateTask(argumentCaptor.capture());\n        assertEquals(TaskModel.Status.SCHEDULED, argumentCaptor.getAllValues().get(0).getStatus());\n        assertEquals(0, argumentCaptor.getAllValues().get(0).getCallbackAfterSeconds());\n        assertEquals(taskResult.getWorkerId(), argumentCaptor.getAllValues().get(0).getWorkerId());\n    }\n\n    @Test\n    public void testIsLazyEvaluateWorkflow() {\n        // setup\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"lazyEvaluate\");\n        workflowDef.setVersion(1);\n\n        WorkflowTask simpleTask = new WorkflowTask();\n        simpleTask.setType(SIMPLE.name());\n        simpleTask.setName(\"simple\");\n        simpleTask.setTaskReferenceName(\"simple\");\n\n        WorkflowTask forkTask = new WorkflowTask();\n        forkTask.setType(FORK_JOIN.name());\n        forkTask.setName(\"fork\");\n        forkTask.setTaskReferenceName(\"fork\");\n\n        WorkflowTask branchTask1 = new WorkflowTask();\n        branchTask1.setType(SIMPLE.name());\n        branchTask1.setName(\"branchTask1\");\n        branchTask1.setTaskReferenceName(\"branchTask1\");\n\n        WorkflowTask branchTask2 = new WorkflowTask();\n        branchTask2.setType(SIMPLE.name());\n        branchTask2.setName(\"branchTask2\");\n        branchTask2.setTaskReferenceName(\"branchTask2\");\n\n        forkTask.getForkTasks().add(Arrays.asList(branchTask1, branchTask2));\n\n        WorkflowTask joinTask = new WorkflowTask();\n        joinTask.setType(JOIN.name());\n        joinTask.setName(\"join\");\n        joinTask.setTaskReferenceName(\"join\");\n        joinTask.setJoinOn(List.of(\"branchTask2\"));\n\n        WorkflowTask doWhile = new WorkflowTask();\n        doWhile.setType(DO_WHILE.name());\n        doWhile.setName(\"doWhile\");\n        doWhile.setTaskReferenceName(\"doWhile\");\n\n        WorkflowTask loopTask = new WorkflowTask();\n        loopTask.setType(SIMPLE.name());\n        loopTask.setName(\"loopTask\");\n        loopTask.setTaskReferenceName(\"loopTask\");\n\n        doWhile.setLoopOver(List.of(loopTask));\n\n        workflowDef.getTasks().addAll(List.of(simpleTask, forkTask, joinTask, doWhile));\n\n        TaskModel task = new TaskModel();\n        task.setStatus(TaskModel.Status.COMPLETED);\n\n        // when:\n        task.setReferenceTaskName(\"dynamic\");\n        assertTrue(workflowExecutor.isLazyEvaluateWorkflow(workflowDef, task));\n\n        task.setReferenceTaskName(\"branchTask1\");\n        assertFalse(workflowExecutor.isLazyEvaluateWorkflow(workflowDef, task));\n\n        task.setReferenceTaskName(\"branchTask2\");\n        assertTrue(workflowExecutor.isLazyEvaluateWorkflow(workflowDef, task));\n\n        task.setReferenceTaskName(\"simple\");\n        assertFalse(workflowExecutor.isLazyEvaluateWorkflow(workflowDef, task));\n\n        task.setReferenceTaskName(\"loopTask__1\");\n        task.setIteration(1);\n        assertFalse(workflowExecutor.isLazyEvaluateWorkflow(workflowDef, task));\n\n        task.setReferenceTaskName(\"branchTask1\");\n        task.setStatus(TaskModel.Status.FAILED);\n        assertFalse(workflowExecutor.isLazyEvaluateWorkflow(workflowDef, task));\n    }\n\n    @Test\n    public void testTaskExtendLease() {\n        TaskModel simpleTask = new TaskModel();\n        simpleTask.setTaskType(TaskType.SIMPLE.name());\n        simpleTask.setReferenceTaskName(\"simpleTask\");\n        simpleTask.setWorkflowInstanceId(\"test-workflow-id\");\n        simpleTask.setScheduledTime(System.currentTimeMillis());\n        simpleTask.setCallbackAfterSeconds(0);\n        simpleTask.setTaskId(\"simple-task-id\");\n        simpleTask.setStatus(TaskModel.Status.IN_PROGRESS);\n        when(executionDAOFacade.getTaskModel(simpleTask.getTaskId())).thenReturn(simpleTask);\n\n        TaskResult taskResult = new TaskResult();\n        taskResult.setWorkflowInstanceId(simpleTask.getWorkflowInstanceId());\n        taskResult.setTaskId(simpleTask.getTaskId());\n        taskResult.log(\"extend lease\");\n        taskResult.setExtendLease(true);\n\n        workflowExecutor.updateTask(taskResult);\n        verify(executionDAOFacade, times(1)).extendLease(simpleTask);\n        verify(queueDAO, times(0)).postpone(anyString(), anyString(), anyInt(), anyLong());\n        verify(executionDAOFacade, times(0)).updateTask(any());\n    }\n\n    private WorkflowModel generateSampleWorkflow() {\n        // setup\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"testRetryWorkflowId\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testRetryWorkflowId\");\n        workflowDef.setVersion(1);\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setOwnerApp(\"junit_testRetryWorkflowId\");\n        workflow.setCreateTime(10L);\n        workflow.setEndTime(100L);\n        //noinspection unchecked\n        workflow.setOutput(Collections.EMPTY_MAP);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n\n        return workflow;\n    }\n\n    private List<TaskModel> generateSampleTasks(int count) {\n        if (count == 0) {\n            return null;\n        }\n        List<TaskModel> tasks = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            TaskModel task = new TaskModel();\n            task.setTaskId(UUID.randomUUID().toString());\n            task.setSeq(i);\n            task.setRetryCount(1);\n            task.setTaskType(\"task\" + (i + 1));\n            task.setStatus(TaskModel.Status.COMPLETED);\n            task.setTaskDefName(\"taskX\");\n            task.setReferenceTaskName(\"task_ref\" + (i + 1));\n            tasks.add(task);\n        }\n\n        return tasks;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/WorkflowSystemTaskStub.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution;\n\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\npublic class WorkflowSystemTaskStub extends WorkflowSystemTask {\n\n    private boolean started = false;\n\n    public WorkflowSystemTaskStub(String taskType) {\n        super(taskType);\n    }\n\n    @Override\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n        started = true;\n        task.setStatus(TaskModel.Status.COMPLETED);\n        super.start(workflow, task, executor);\n    }\n\n    public boolean isStarted() {\n        return started;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/DecisionTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class DecisionTaskMapperTest {\n\n    private IDGenerator idGenerator;\n    private ParametersUtils parametersUtils;\n    private DeciderService deciderService;\n    // Subject\n    private DecisionTaskMapper decisionTaskMapper;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    Map<String, Object> ip1;\n    WorkflowTask task1;\n    WorkflowTask task2;\n    WorkflowTask task3;\n\n    @Before\n    public void setUp() {\n        parametersUtils = new ParametersUtils(objectMapper);\n        idGenerator = new IDGenerator();\n\n        ip1 = new HashMap<>();\n        ip1.put(\"p1\", \"${workflow.input.param1}\");\n        ip1.put(\"p2\", \"${workflow.input.param2}\");\n        ip1.put(\"case\", \"${workflow.input.case}\");\n\n        task1 = new WorkflowTask();\n        task1.setName(\"Test1\");\n        task1.setInputParameters(ip1);\n        task1.setTaskReferenceName(\"t1\");\n\n        task2 = new WorkflowTask();\n        task2.setName(\"Test2\");\n        task2.setInputParameters(ip1);\n        task2.setTaskReferenceName(\"t2\");\n\n        task3 = new WorkflowTask();\n        task3.setName(\"Test3\");\n        task3.setInputParameters(ip1);\n        task3.setTaskReferenceName(\"t3\");\n        deciderService = mock(DeciderService.class);\n        decisionTaskMapper = new DecisionTaskMapper();\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        // Given\n        // Task Definition\n        TaskDef taskDef = new TaskDef();\n        Map<String, Object> inputMap = new HashMap<>();\n        inputMap.put(\"Id\", \"${workflow.input.Id}\");\n        List<Map<String, Object>> taskDefinitionInput = new LinkedList<>();\n        taskDefinitionInput.add(inputMap);\n\n        // Decision task instance\n        WorkflowTask decisionTask = new WorkflowTask();\n        decisionTask.setType(TaskType.DECISION.name());\n        decisionTask.setName(\"Decision\");\n        decisionTask.setTaskReferenceName(\"decisionTask\");\n        decisionTask.setDefaultCase(Collections.singletonList(task1));\n        decisionTask.setCaseValueParam(\"case\");\n        decisionTask.getInputParameters().put(\"Id\", \"${workflow.input.Id}\");\n        decisionTask.setCaseExpression(\n                \"if ($.Id == null) 'bad input'; else if ( ($.Id != null && $.Id % 2 == 0)) 'even'; else 'odd'; \");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"even\", Collections.singletonList(task2));\n        decisionCases.put(\"odd\", Collections.singletonList(task3));\n        decisionTask.setDecisionCases(decisionCases);\n        // Workflow instance\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setSchemaVersion(2);\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\"Id\", \"22\");\n        workflowModel.setInput(workflowInput);\n\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input\", taskDefinitionInput);\n        taskDef.getInputTemplate().putAll(body);\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        decisionTask.getInputParameters(), workflowModel, null, null);\n\n        TaskModel theTask = new TaskModel();\n        theTask.setReferenceTaskName(\"Foo\");\n        theTask.setTaskId(idGenerator.generate());\n\n        when(deciderService.getTasksToBeScheduled(workflowModel, task2, 0, null))\n                .thenReturn(Collections.singletonList(theTask));\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(decisionTask)\n                        .withTaskInput(input)\n                        .withRetryCount(0)\n                        .withTaskId(idGenerator.generate())\n                        .withDeciderService(deciderService)\n                        .build();\n\n        // When\n        List<TaskModel> mappedTasks = decisionTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(2, mappedTasks.size());\n        assertEquals(\"decisionTask\", mappedTasks.get(0).getReferenceTaskName());\n        assertEquals(\"Foo\", mappedTasks.get(1).getReferenceTaskName());\n    }\n\n    @Test\n    public void getEvaluatedCaseValue() {\n        WorkflowTask decisionTask = new WorkflowTask();\n        decisionTask.setType(TaskType.DECISION.name());\n        decisionTask.setName(\"Decision\");\n        decisionTask.setTaskReferenceName(\"decisionTask\");\n        decisionTask.setInputParameters(ip1);\n        decisionTask.setDefaultCase(Collections.singletonList(task1));\n        decisionTask.setCaseValueParam(\"case\");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"0\", Collections.singletonList(task2));\n        decisionCases.put(\"1\", Collections.singletonList(task3));\n        decisionTask.setDecisionCases(decisionCases);\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(new WorkflowDef());\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\"param1\", \"test1\");\n        workflowInput.put(\"param2\", \"test2\");\n        workflowInput.put(\"case\", \"0\");\n        workflowModel.setInput(workflowInput);\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        decisionTask.getInputParameters(), workflowModel, null, null);\n\n        assertEquals(\"0\", decisionTaskMapper.getEvaluatedCaseValue(decisionTask, input));\n    }\n\n    @Test\n    public void getEvaluatedCaseValueUsingExpression() {\n        // Given\n        // Task Definition\n        TaskDef taskDef = new TaskDef();\n        Map<String, Object> inputMap = new HashMap<>();\n        inputMap.put(\"Id\", \"${workflow.input.Id}\");\n        List<Map<String, Object>> taskDefinitionInput = new LinkedList<>();\n        taskDefinitionInput.add(inputMap);\n\n        // Decision task instance\n        WorkflowTask decisionTask = new WorkflowTask();\n        decisionTask.setType(TaskType.DECISION.name());\n        decisionTask.setName(\"Decision\");\n        decisionTask.setTaskReferenceName(\"decisionTask\");\n        decisionTask.setDefaultCase(Collections.singletonList(task1));\n        decisionTask.setCaseValueParam(\"case\");\n        decisionTask.getInputParameters().put(\"Id\", \"${workflow.input.Id}\");\n        decisionTask.setCaseExpression(\n                \"if ($.Id == null) 'bad input'; else if ( ($.Id != null && $.Id % 2 == 0)) 'even'; else 'odd'; \");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"even\", Collections.singletonList(task2));\n        decisionCases.put(\"odd\", Collections.singletonList(task3));\n        decisionTask.setDecisionCases(decisionCases);\n\n        // Workflow instance\n        WorkflowDef def = new WorkflowDef();\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(def);\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\"Id\", \"22\");\n        workflowModel.setInput(workflowInput);\n\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input\", taskDefinitionInput);\n        taskDef.getInputTemplate().putAll(body);\n\n        Map<String, Object> evaluatorInput =\n                parametersUtils.getTaskInput(\n                        decisionTask.getInputParameters(), workflowModel, taskDef, null);\n\n        assertEquals(\n                \"even\", decisionTaskMapper.getEvaluatedCaseValue(decisionTask, evaluatorInput));\n    }\n\n    @Test\n    public void getEvaluatedCaseValueException() {\n        // Given\n        // Task Definition\n        TaskDef taskDef = new TaskDef();\n        Map<String, Object> inputMap = new HashMap<>();\n        inputMap.put(\"Id\", \"${workflow.input.Id}\");\n        List<Map<String, Object>> taskDefinitionInput = new LinkedList<>();\n        taskDefinitionInput.add(inputMap);\n\n        // Decision task instance\n        WorkflowTask decisionTask = new WorkflowTask();\n        decisionTask.setType(TaskType.DECISION.name());\n        decisionTask.setName(\"Decision\");\n        decisionTask.setTaskReferenceName(\"decisionTask\");\n        decisionTask.setDefaultCase(Collections.singletonList(task1));\n        decisionTask.setCaseValueParam(\"case\");\n        decisionTask.getInputParameters().put(\"Id\", \"${workflow.input.Id}\");\n        decisionTask.setCaseExpression(\n                \"if ($Id == null) 'bad input'; else if ( ($Id != null && $Id % 2 == 0)) 'even'; else 'odd'; \");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"even\", Collections.singletonList(task2));\n        decisionCases.put(\"odd\", Collections.singletonList(task3));\n        decisionTask.setDecisionCases(decisionCases);\n\n        // Workflow instance\n        WorkflowDef def = new WorkflowDef();\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(def);\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\".Id\", \"22\");\n        workflowModel.setInput(workflowInput);\n\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input\", taskDefinitionInput);\n        taskDef.getInputTemplate().putAll(body);\n\n        Map<String, Object> evaluatorInput =\n                parametersUtils.getTaskInput(\n                        decisionTask.getInputParameters(), workflowModel, taskDef, null);\n\n        expectedException.expect(TerminateWorkflowException.class);\n        expectedException.expectMessage(\n                \"Error while evaluating script: \" + decisionTask.getCaseExpression());\n\n        decisionTaskMapper.getEvaluatedCaseValue(decisionTask, evaluatorInput);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/DoWhileTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.utils.TaskUtils;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_DO_WHILE;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class DoWhileTaskMapperTest {\n\n    private TaskModel task1;\n    private DeciderService deciderService;\n    private WorkflowModel workflow;\n    private WorkflowTask workflowTask1;\n    private TaskMapperContext taskMapperContext;\n    private MetadataDAO metadataDAO;\n    private ParametersUtils parametersUtils;\n\n    @Before\n    public void setup() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setType(TaskType.DO_WHILE.name());\n        workflowTask.setTaskReferenceName(\"Test\");\n        workflowTask.setInputParameters(Map.of(\"value\", \"${workflow.input.foo}\"));\n        task1 = new TaskModel();\n        task1.setReferenceTaskName(\"task1\");\n        TaskModel task2 = new TaskModel();\n        task2.setReferenceTaskName(\"task2\");\n        workflowTask1 = new WorkflowTask();\n        workflowTask1.setTaskReferenceName(\"task1\");\n        WorkflowTask workflowTask2 = new WorkflowTask();\n        workflowTask2.setTaskReferenceName(\"task2\");\n        task1.setWorkflowTask(workflowTask1);\n        task2.setWorkflowTask(workflowTask2);\n        workflowTask.setLoopOver(Arrays.asList(task1.getWorkflowTask(), task2.getWorkflowTask()));\n        workflowTask.setLoopCondition(\n                \"if ($.second_task + $.first_task > 10) { false; } else { true; }\");\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setInput(Map.of(\"foo\", \"bar\"));\n\n        deciderService = Mockito.mock(DeciderService.class);\n        metadataDAO = Mockito.mock(MetadataDAO.class);\n\n        taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withDeciderService(deciderService)\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        parametersUtils = new ParametersUtils(new ObjectMapper());\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        Mockito.doReturn(Collections.singletonList(task1))\n                .when(deciderService)\n                .getTasksToBeScheduled(workflow, workflowTask1, 0);\n\n        List<TaskModel> mappedTasks =\n                new DoWhileTaskMapper(metadataDAO, parametersUtils)\n                        .getMappedTasks(taskMapperContext);\n\n        assertNotNull(mappedTasks);\n        assertEquals(mappedTasks.size(), 1);\n        assertEquals(TASK_TYPE_DO_WHILE, mappedTasks.get(0).getTaskType());\n        assertNotNull(mappedTasks.get(0).getInputData());\n        assertEquals(Map.of(\"value\", \"bar\"), mappedTasks.get(0).getInputData());\n    }\n\n    @Test\n    public void shouldNotScheduleCompletedTask() {\n\n        task1.setStatus(TaskModel.Status.COMPLETED);\n\n        List<TaskModel> mappedTasks =\n                new DoWhileTaskMapper(metadataDAO, parametersUtils)\n                        .getMappedTasks(taskMapperContext);\n\n        assertNotNull(mappedTasks);\n        assertEquals(mappedTasks.size(), 1);\n    }\n\n    @Test\n    public void testAppendIteration() {\n        assertEquals(\"task__1\", TaskUtils.appendIteration(\"task\", 1));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/DynamicTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class DynamicTaskMapperTest {\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n    private ParametersUtils parametersUtils;\n    private MetadataDAO metadataDAO;\n    private DynamicTaskMapper dynamicTaskMapper;\n\n    @Before\n    public void setUp() {\n        parametersUtils = mock(ParametersUtils.class);\n        metadataDAO = mock(MetadataDAO.class);\n\n        dynamicTaskMapper = new DynamicTaskMapper(parametersUtils, metadataDAO);\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"DynoTask\");\n        workflowTask.setDynamicTaskNameParam(\"dynamicTaskName\");\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"DynoTask\");\n        workflowTask.setTaskDefinition(taskDef);\n\n        Map<String, Object> taskInput = new HashMap<>();\n        taskInput.put(\"dynamicTaskName\", \"DynoTask\");\n\n        when(parametersUtils.getTaskInput(\n                        anyMap(), any(WorkflowModel.class), any(TaskDef.class), anyString()))\n                .thenReturn(taskInput);\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(workflowTask.getTaskDefinition())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(taskInput)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        when(metadataDAO.getTaskDef(\"DynoTask\")).thenReturn(new TaskDef());\n\n        List<TaskModel> mappedTasks = dynamicTaskMapper.getMappedTasks(taskMapperContext);\n\n        assertEquals(1, mappedTasks.size());\n\n        TaskModel dynamicTask = mappedTasks.get(0);\n        assertEquals(taskId, dynamicTask.getTaskId());\n    }\n\n    @Test\n    public void getDynamicTaskName() {\n        Map<String, Object> taskInput = new HashMap<>();\n        taskInput.put(\"dynamicTaskName\", \"DynoTask\");\n\n        String dynamicTaskName = dynamicTaskMapper.getDynamicTaskName(taskInput, \"dynamicTaskName\");\n\n        assertEquals(\"DynoTask\", dynamicTaskName);\n    }\n\n    @Test\n    public void getDynamicTaskNameNotAvailable() {\n        Map<String, Object> taskInput = new HashMap<>();\n\n        expectedException.expect(TerminateWorkflowException.class);\n        expectedException.expectMessage(\n                String.format(\n                        \"Cannot map a dynamic task based on the parameter and input. \"\n                                + \"Parameter= %s, input= %s\",\n                        \"dynamicTaskName\", taskInput));\n\n        dynamicTaskMapper.getDynamicTaskName(taskInput, \"dynamicTaskName\");\n    }\n\n    @Test\n    public void getDynamicTaskDefinition() {\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"Foo\");\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"Foo\");\n        workflowTask.setTaskDefinition(taskDef);\n\n        when(metadataDAO.getTaskDef(any())).thenReturn(new TaskDef());\n\n        // when\n        TaskDef dynamicTaskDefinition = dynamicTaskMapper.getDynamicTaskDefinition(workflowTask);\n\n        assertEquals(dynamicTaskDefinition, taskDef);\n    }\n\n    @Test\n    public void getDynamicTaskDefinitionNull() {\n\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"Foo\");\n\n        expectedException.expect(TerminateWorkflowException.class);\n        expectedException.expectMessage(\n                String.format(\n                        \"Invalid task specified.  Cannot find task by name %s in the task definitions\",\n                        workflowTask.getName()));\n\n        dynamicTaskMapper.getDynamicTaskDefinition(workflowTask);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/EventTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.when;\n\npublic class EventTaskMapperTest {\n\n    @Test\n    public void getMappedTasks() {\n        ParametersUtils parametersUtils = Mockito.mock(ParametersUtils.class);\n        EventTaskMapper eventTaskMapper = new EventTaskMapper(parametersUtils);\n\n        WorkflowTask taskToBeScheduled = new WorkflowTask();\n        taskToBeScheduled.setSink(\"SQSSINK\");\n        String taskId = new IDGenerator().generate();\n\n        Map<String, Object> eventTaskInput = new HashMap<>();\n        eventTaskInput.put(\"sink\", \"SQSSINK\");\n\n        when(parametersUtils.getTaskInput(\n                        anyMap(), any(WorkflowModel.class), any(TaskDef.class), anyString()))\n                .thenReturn(eventTaskInput);\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(taskToBeScheduled)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks = eventTaskMapper.getMappedTasks(taskMapperContext);\n        assertEquals(1, mappedTasks.size());\n\n        TaskModel eventTask = mappedTasks.get(0);\n        assertEquals(taskId, eventTask.getTaskId());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/ForkJoinDynamicTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.*;\n\nimport org.apache.commons.lang3.tuple.Pair;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.DynamicForkJoinTaskList;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.when;\n\n@SuppressWarnings(\"unchecked\")\npublic class ForkJoinDynamicTaskMapperTest {\n\n    private IDGenerator idGenerator;\n    private ParametersUtils parametersUtils;\n    private ObjectMapper objectMapper;\n    private DeciderService deciderService;\n    private ForkJoinDynamicTaskMapper forkJoinDynamicTaskMapper;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() {\n        MetadataDAO metadataDAO = Mockito.mock(MetadataDAO.class);\n        idGenerator = new IDGenerator();\n        parametersUtils = Mockito.mock(ParametersUtils.class);\n        objectMapper = Mockito.mock(ObjectMapper.class);\n        deciderService = Mockito.mock(DeciderService.class);\n\n        forkJoinDynamicTaskMapper =\n                new ForkJoinDynamicTaskMapper(\n                        idGenerator, parametersUtils, objectMapper, metadataDAO);\n    }\n\n    @Test\n    public void getMappedTasksException() {\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"DYNAMIC_FORK_JOIN_WF\");\n        def.setDescription(def.getName());\n        def.setVersion(1);\n        def.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(def);\n\n        WorkflowTask dynamicForkJoinToSchedule = new WorkflowTask();\n        dynamicForkJoinToSchedule.setType(TaskType.FORK_JOIN_DYNAMIC.name());\n        dynamicForkJoinToSchedule.setTaskReferenceName(\"dynamicfanouttask\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksParam(\"dynamicTasks\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksInputParamName(\"dynamicTasksInput\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasks\", \"dt1.output.dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasksInput\", \"dt1.output.dynamicTasksInput\");\n\n        WorkflowTask join = new WorkflowTask();\n        join.setType(TaskType.JOIN.name());\n        join.setTaskReferenceName(\"dynamictask_join\");\n\n        def.getTasks().add(dynamicForkJoinToSchedule);\n\n        Map<String, Object> input1 = new HashMap<>();\n        input1.put(\"k1\", \"v1\");\n        WorkflowTask wt2 = new WorkflowTask();\n        wt2.setName(\"junit_task_2\");\n        wt2.setTaskReferenceName(\"xdt1\");\n\n        Map<String, Object> input2 = new HashMap<>();\n        input2.put(\"k2\", \"v2\");\n\n        WorkflowTask wt3 = new WorkflowTask();\n        wt3.setName(\"junit_task_3\");\n        wt3.setTaskReferenceName(\"xdt2\");\n\n        HashMap<String, Object> dynamicTasksInput = new HashMap<>();\n        dynamicTasksInput.put(\"xdt1\", input1);\n        dynamicTasksInput.put(\"xdt2\", input2);\n        dynamicTasksInput.put(\"dynamicTasks\", Arrays.asList(wt2, wt3));\n        dynamicTasksInput.put(\"dynamicTasksInput\", dynamicTasksInput);\n\n        // when\n        when(parametersUtils.getTaskInput(anyMap(), any(WorkflowModel.class), any(), any()))\n                .thenReturn(dynamicTasksInput);\n\n        when(objectMapper.convertValue(any(), any(TypeReference.class)))\n                .thenReturn(Arrays.asList(wt2, wt3));\n\n        TaskModel simpleTask1 = new TaskModel();\n        simpleTask1.setReferenceTaskName(\"xdt1\");\n\n        TaskModel simpleTask2 = new TaskModel();\n        simpleTask2.setReferenceTaskName(\"xdt2\");\n\n        when(deciderService.getTasksToBeScheduled(workflowModel, wt2, 0))\n                .thenReturn(Collections.singletonList(simpleTask1));\n        when(deciderService.getTasksToBeScheduled(workflowModel, wt3, 0))\n                .thenReturn(Collections.singletonList(simpleTask2));\n\n        String taskId = idGenerator.generate();\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(dynamicForkJoinToSchedule)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .withDeciderService(deciderService)\n                        .build();\n\n        // then\n        expectedException.expect(TerminateWorkflowException.class);\n        forkJoinDynamicTaskMapper.getMappedTasks(taskMapperContext);\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"DYNAMIC_FORK_JOIN_WF\");\n        def.setDescription(def.getName());\n        def.setVersion(1);\n        def.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(def);\n\n        WorkflowTask dynamicForkJoinToSchedule = new WorkflowTask();\n        dynamicForkJoinToSchedule.setType(TaskType.FORK_JOIN_DYNAMIC.name());\n        dynamicForkJoinToSchedule.setTaskReferenceName(\"dynamicfanouttask\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksParam(\"dynamicTasks\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksInputParamName(\"dynamicTasksInput\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasks\", \"dt1.output.dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasksInput\", \"dt1.output.dynamicTasksInput\");\n\n        WorkflowTask join = new WorkflowTask();\n        join.setType(TaskType.JOIN.name());\n        join.setTaskReferenceName(\"dynamictask_join\");\n\n        def.getTasks().add(dynamicForkJoinToSchedule);\n        def.getTasks().add(join);\n\n        Map<String, Object> input1 = new HashMap<>();\n        input1.put(\"k1\", \"v1\");\n        WorkflowTask wt2 = new WorkflowTask();\n        wt2.setName(\"junit_task_2\");\n        wt2.setTaskReferenceName(\"xdt1\");\n\n        Map<String, Object> input2 = new HashMap<>();\n        input2.put(\"k2\", \"v2\");\n\n        WorkflowTask wt3 = new WorkflowTask();\n        wt3.setName(\"junit_task_3\");\n        wt3.setTaskReferenceName(\"xdt2\");\n\n        HashMap<String, Object> dynamicTasksInput = new HashMap<>();\n        dynamicTasksInput.put(\"xdt1\", input1);\n        dynamicTasksInput.put(\"xdt2\", input2);\n        dynamicTasksInput.put(\"dynamicTasks\", Arrays.asList(wt2, wt3));\n        dynamicTasksInput.put(\"dynamicTasksInput\", dynamicTasksInput);\n\n        // when\n        when(parametersUtils.getTaskInput(anyMap(), any(WorkflowModel.class), any(), any()))\n                .thenReturn(dynamicTasksInput);\n        when(objectMapper.convertValue(any(), any(TypeReference.class)))\n                .thenReturn(Arrays.asList(wt2, wt3));\n\n        TaskModel simpleTask1 = new TaskModel();\n        simpleTask1.setReferenceTaskName(\"xdt1\");\n\n        TaskModel simpleTask2 = new TaskModel();\n        simpleTask2.setReferenceTaskName(\"xdt2\");\n\n        when(deciderService.getTasksToBeScheduled(workflowModel, wt2, 0))\n                .thenReturn(Collections.singletonList(simpleTask1));\n        when(deciderService.getTasksToBeScheduled(workflowModel, wt3, 0))\n                .thenReturn(Collections.singletonList(simpleTask2));\n\n        String taskId = idGenerator.generate();\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(dynamicForkJoinToSchedule)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .withDeciderService(deciderService)\n                        .build();\n\n        // then\n        List<TaskModel> mappedTasks = forkJoinDynamicTaskMapper.getMappedTasks(taskMapperContext);\n\n        assertEquals(4, mappedTasks.size());\n\n        assertEquals(TASK_TYPE_FORK, mappedTasks.get(0).getTaskType());\n        assertEquals(TASK_TYPE_JOIN, mappedTasks.get(3).getTaskType());\n        List<String> joinTaskNames = (List<String>) mappedTasks.get(3).getInputData().get(\"joinOn\");\n        assertEquals(\"xdt1, xdt2\", String.join(\", \", joinTaskNames));\n    }\n\n    @Test\n    public void getDynamicForkJoinTasksAndInput() {\n        // Given\n        WorkflowTask dynamicForkJoinToSchedule = new WorkflowTask();\n        dynamicForkJoinToSchedule.setType(TaskType.FORK_JOIN_DYNAMIC.name());\n        dynamicForkJoinToSchedule.setTaskReferenceName(\"dynamicfanouttask\");\n        dynamicForkJoinToSchedule.setDynamicForkJoinTasksParam(\"dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasks\", \"dt1.output.dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasksInput\", \"dt1.output.dynamicTasksInput\");\n\n        DynamicForkJoinTaskList dtasks = new DynamicForkJoinTaskList();\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"v1\");\n        dtasks.add(\"junit_task_2\", null, \"xdt1\", input);\n\n        HashMap<String, Object> input2 = new HashMap<>();\n        input2.put(\"k2\", \"v2\");\n        dtasks.add(\"junit_task_3\", null, \"xdt2\", input2);\n\n        Map<String, Object> dynamicTasksInput = new HashMap<>();\n        dynamicTasksInput.put(\"dynamicTasks\", dtasks);\n\n        // when\n        when(parametersUtils.getTaskInput(\n                        anyMap(), any(WorkflowModel.class), any(TaskDef.class), anyString()))\n                .thenReturn(dynamicTasksInput);\n\n        when(objectMapper.convertValue(any(), any(Class.class))).thenReturn(dtasks);\n\n        Pair<List<WorkflowTask>, Map<String, Map<String, Object>>> dynamicForkJoinTasksAndInput =\n                forkJoinDynamicTaskMapper.getDynamicForkJoinTasksAndInput(\n                        dynamicForkJoinToSchedule, new WorkflowModel());\n        // then\n        assertNotNull(dynamicForkJoinTasksAndInput.getLeft());\n        assertEquals(2, dynamicForkJoinTasksAndInput.getLeft().size());\n        assertEquals(2, dynamicForkJoinTasksAndInput.getRight().size());\n    }\n\n    @Test\n    public void getDynamicForkJoinTasksAndInputException() {\n        // Given\n        WorkflowTask dynamicForkJoinToSchedule = new WorkflowTask();\n        dynamicForkJoinToSchedule.setType(TaskType.FORK_JOIN_DYNAMIC.name());\n        dynamicForkJoinToSchedule.setTaskReferenceName(\"dynamicfanouttask\");\n        dynamicForkJoinToSchedule.setDynamicForkJoinTasksParam(\"dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasks\", \"dt1.output.dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasksInput\", \"dt1.output.dynamicTasksInput\");\n\n        DynamicForkJoinTaskList dtasks = new DynamicForkJoinTaskList();\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"v1\");\n        dtasks.add(\"junit_task_2\", null, \"xdt1\", input);\n\n        HashMap<String, Object> input2 = new HashMap<>();\n        input2.put(\"k2\", \"v2\");\n        dtasks.add(\"junit_task_3\", null, \"xdt2\", input2);\n\n        Map<String, Object> dynamicTasksInput = new HashMap<>();\n        dynamicTasksInput.put(\"dynamicTasks\", dtasks);\n\n        // when\n        when(parametersUtils.getTaskInput(\n                        anyMap(), any(WorkflowModel.class), any(TaskDef.class), anyString()))\n                .thenReturn(dynamicTasksInput);\n\n        when(objectMapper.convertValue(any(), any(Class.class))).thenReturn(null);\n\n        // then\n        expectedException.expect(TerminateWorkflowException.class);\n\n        forkJoinDynamicTaskMapper.getDynamicForkJoinTasksAndInput(\n                dynamicForkJoinToSchedule, new WorkflowModel());\n    }\n\n    @Test\n    public void getDynamicForkTasksAndInput() {\n        // Given\n        WorkflowTask dynamicForkJoinToSchedule = new WorkflowTask();\n        dynamicForkJoinToSchedule.setType(TaskType.FORK_JOIN_DYNAMIC.name());\n        dynamicForkJoinToSchedule.setTaskReferenceName(\"dynamicfanouttask\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksParam(\"dynamicTasks\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksInputParamName(\"dynamicTasksInput\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasks\", \"dt1.output.dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasksInput\", \"dt1.output.dynamicTasksInput\");\n\n        Map<String, Object> input1 = new HashMap<>();\n        input1.put(\"k1\", \"v1\");\n        WorkflowTask wt2 = new WorkflowTask();\n        wt2.setName(\"junit_task_2\");\n        wt2.setTaskReferenceName(\"xdt1\");\n\n        Map<String, Object> input2 = new HashMap<>();\n        input2.put(\"k2\", \"v2\");\n\n        WorkflowTask wt3 = new WorkflowTask();\n        wt3.setName(\"junit_task_3\");\n        wt3.setTaskReferenceName(\"xdt2\");\n\n        HashMap<String, Object> dynamicTasksInput = new HashMap<>();\n        dynamicTasksInput.put(\"xdt1\", input1);\n        dynamicTasksInput.put(\"xdt2\", input2);\n        dynamicTasksInput.put(\"dynamicTasks\", Arrays.asList(wt2, wt3));\n        dynamicTasksInput.put(\"dynamicTasksInput\", dynamicTasksInput);\n\n        // when\n        when(parametersUtils.getTaskInput(anyMap(), any(WorkflowModel.class), any(), any()))\n                .thenReturn(dynamicTasksInput);\n\n        when(objectMapper.convertValue(any(), any(TypeReference.class)))\n                .thenReturn(Arrays.asList(wt2, wt3));\n\n        Pair<List<WorkflowTask>, Map<String, Map<String, Object>>> dynamicTasks =\n                forkJoinDynamicTaskMapper.getDynamicForkTasksAndInput(\n                        dynamicForkJoinToSchedule, new WorkflowModel(), \"dynamicTasks\");\n\n        // then\n        assertNotNull(dynamicTasks.getLeft());\n    }\n\n    @Test\n    public void getDynamicForkTasksAndInputException() {\n\n        // Given\n        WorkflowTask dynamicForkJoinToSchedule = new WorkflowTask();\n        dynamicForkJoinToSchedule.setType(TaskType.FORK_JOIN_DYNAMIC.name());\n        dynamicForkJoinToSchedule.setTaskReferenceName(\"dynamicfanouttask\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksParam(\"dynamicTasks\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksInputParamName(\"dynamicTasksInput\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasks\", \"dt1.output.dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasksInput\", \"dt1.output.dynamicTasksInput\");\n\n        Map<String, Object> input1 = new HashMap<>();\n        input1.put(\"k1\", \"v1\");\n        WorkflowTask wt2 = new WorkflowTask();\n        wt2.setName(\"junit_task_2\");\n        wt2.setTaskReferenceName(\"xdt1\");\n\n        Map<String, Object> input2 = new HashMap<>();\n        input2.put(\"k2\", \"v2\");\n\n        WorkflowTask wt3 = new WorkflowTask();\n        wt3.setName(\"junit_task_3\");\n        wt3.setTaskReferenceName(\"xdt2\");\n\n        HashMap<String, Object> dynamicTasksInput = new HashMap<>();\n        dynamicTasksInput.put(\"xdt1\", input1);\n        dynamicTasksInput.put(\"xdt2\", input2);\n        dynamicTasksInput.put(\"dynamicTasks\", Arrays.asList(wt2, wt3));\n        dynamicTasksInput.put(\"dynamicTasksInput\", null);\n\n        when(parametersUtils.getTaskInput(anyMap(), any(WorkflowModel.class), any(), any()))\n                .thenReturn(dynamicTasksInput);\n\n        when(objectMapper.convertValue(any(), any(TypeReference.class)))\n                .thenReturn(Arrays.asList(wt2, wt3));\n        // then\n        expectedException.expect(TerminateWorkflowException.class);\n        // when\n        forkJoinDynamicTaskMapper.getDynamicForkTasksAndInput(\n                dynamicForkJoinToSchedule, new WorkflowModel(), \"dynamicTasks\");\n    }\n\n    @Test\n    public void testDynamicTaskDuplicateTaskRefName() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"DYNAMIC_FORK_JOIN_WF\");\n        def.setDescription(def.getName());\n        def.setVersion(1);\n        def.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(def);\n\n        WorkflowTask dynamicForkJoinToSchedule = new WorkflowTask();\n        dynamicForkJoinToSchedule.setType(TaskType.FORK_JOIN_DYNAMIC.name());\n        dynamicForkJoinToSchedule.setTaskReferenceName(\"dynamicfanouttask\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksParam(\"dynamicTasks\");\n        dynamicForkJoinToSchedule.setDynamicForkTasksInputParamName(\"dynamicTasksInput\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasks\", \"dt1.output.dynamicTasks\");\n        dynamicForkJoinToSchedule\n                .getInputParameters()\n                .put(\"dynamicTasksInput\", \"dt1.output.dynamicTasksInput\");\n\n        WorkflowTask join = new WorkflowTask();\n        join.setType(TaskType.JOIN.name());\n        join.setTaskReferenceName(\"dynamictask_join\");\n\n        def.getTasks().add(dynamicForkJoinToSchedule);\n        def.getTasks().add(join);\n\n        Map<String, Object> input1 = new HashMap<>();\n        input1.put(\"k1\", \"v1\");\n        WorkflowTask wt2 = new WorkflowTask();\n        wt2.setName(\"junit_task_2\");\n        wt2.setTaskReferenceName(\"xdt1\");\n\n        Map<String, Object> input2 = new HashMap<>();\n        input2.put(\"k2\", \"v2\");\n\n        WorkflowTask wt3 = new WorkflowTask();\n        wt3.setName(\"junit_task_3\");\n        wt3.setTaskReferenceName(\"xdt2\");\n\n        HashMap<String, Object> dynamicTasksInput = new HashMap<>();\n        dynamicTasksInput.put(\"xdt1\", input1);\n        dynamicTasksInput.put(\"xdt2\", input2);\n        dynamicTasksInput.put(\"dynamicTasks\", Arrays.asList(wt2, wt3));\n        dynamicTasksInput.put(\"dynamicTasksInput\", dynamicTasksInput);\n\n        // dynamic\n        when(parametersUtils.getTaskInput(anyMap(), any(WorkflowModel.class), any(), any()))\n                .thenReturn(dynamicTasksInput);\n        when(objectMapper.convertValue(any(), any(TypeReference.class)))\n                .thenReturn(Arrays.asList(wt2, wt3));\n\n        TaskModel simpleTask1 = new TaskModel();\n        simpleTask1.setReferenceTaskName(\"xdt1\");\n\n        // Empty list, this is a bad state, workflow should terminate\n        when(deciderService.getTasksToBeScheduled(workflowModel, wt2, 0))\n                .thenReturn(new ArrayList<>());\n\n        String taskId = idGenerator.generate();\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(dynamicForkJoinToSchedule)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .withDeciderService(deciderService)\n                        .build();\n\n        expectedException.expect(TerminateWorkflowException.class);\n        forkJoinDynamicTaskMapper.getMappedTasks(taskMapperContext);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/ForkJoinTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class ForkJoinTaskMapperTest {\n\n    private DeciderService deciderService;\n    private ForkJoinTaskMapper forkJoinTaskMapper;\n    private IDGenerator idGenerator;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() {\n        deciderService = Mockito.mock(DeciderService.class);\n        forkJoinTaskMapper = new ForkJoinTaskMapper();\n        idGenerator = new IDGenerator();\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"FORK_JOIN_WF\");\n        def.setDescription(def.getName());\n        def.setVersion(1);\n        def.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n\n        WorkflowTask forkTask = new WorkflowTask();\n        forkTask.setType(TaskType.FORK_JOIN.name());\n        forkTask.setTaskReferenceName(\"forktask\");\n\n        WorkflowTask wft1 = new WorkflowTask();\n        wft1.setName(\"junit_task_1\");\n        Map<String, Object> ip1 = new HashMap<>();\n        ip1.put(\"p1\", \"workflow.input.param1\");\n        ip1.put(\"p2\", \"workflow.input.param2\");\n        wft1.setInputParameters(ip1);\n        wft1.setTaskReferenceName(\"t1\");\n\n        WorkflowTask wft3 = new WorkflowTask();\n        wft3.setName(\"junit_task_3\");\n        wft3.setInputParameters(ip1);\n        wft3.setTaskReferenceName(\"t3\");\n\n        WorkflowTask wft2 = new WorkflowTask();\n        wft2.setName(\"junit_task_2\");\n        Map<String, Object> ip2 = new HashMap<>();\n        ip2.put(\"tp1\", \"workflow.input.param1\");\n        wft2.setInputParameters(ip2);\n        wft2.setTaskReferenceName(\"t2\");\n\n        WorkflowTask wft4 = new WorkflowTask();\n        wft4.setName(\"junit_task_4\");\n        wft4.setInputParameters(ip2);\n        wft4.setTaskReferenceName(\"t4\");\n\n        forkTask.getForkTasks().add(Arrays.asList(wft1, wft3));\n        forkTask.getForkTasks().add(Collections.singletonList(wft2));\n\n        def.getTasks().add(forkTask);\n\n        WorkflowTask join = new WorkflowTask();\n        join.setType(TaskType.JOIN.name());\n        join.setTaskReferenceName(\"forktask_join\");\n        join.setJoinOn(Arrays.asList(\"t3\", \"t2\"));\n\n        def.getTasks().add(join);\n        def.getTasks().add(wft4);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n\n        TaskModel task1 = new TaskModel();\n        task1.setReferenceTaskName(wft1.getTaskReferenceName());\n\n        TaskModel task3 = new TaskModel();\n        task3.setReferenceTaskName(wft3.getTaskReferenceName());\n\n        Mockito.when(deciderService.getTasksToBeScheduled(workflow, wft1, 0))\n                .thenReturn(Collections.singletonList(task1));\n        Mockito.when(deciderService.getTasksToBeScheduled(workflow, wft2, 0))\n                .thenReturn(Collections.singletonList(task3));\n\n        String taskId = idGenerator.generate();\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withWorkflowTask(forkTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .withDeciderService(deciderService)\n                        .build();\n\n        List<TaskModel> mappedTasks = forkJoinTaskMapper.getMappedTasks(taskMapperContext);\n\n        assertEquals(3, mappedTasks.size());\n        assertEquals(TASK_TYPE_FORK, mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void getMappedTasksException() {\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"FORK_JOIN_WF\");\n        def.setDescription(def.getName());\n        def.setVersion(1);\n        def.setInputParameters(Arrays.asList(\"param1\", \"param2\"));\n\n        WorkflowTask forkTask = new WorkflowTask();\n        forkTask.setType(TaskType.FORK_JOIN.name());\n        forkTask.setTaskReferenceName(\"forktask\");\n\n        WorkflowTask wft1 = new WorkflowTask();\n        wft1.setName(\"junit_task_1\");\n        Map<String, Object> ip1 = new HashMap<>();\n        ip1.put(\"p1\", \"workflow.input.param1\");\n        ip1.put(\"p2\", \"workflow.input.param2\");\n        wft1.setInputParameters(ip1);\n        wft1.setTaskReferenceName(\"t1\");\n\n        WorkflowTask wft3 = new WorkflowTask();\n        wft3.setName(\"junit_task_3\");\n        wft3.setInputParameters(ip1);\n        wft3.setTaskReferenceName(\"t3\");\n\n        WorkflowTask wft2 = new WorkflowTask();\n        wft2.setName(\"junit_task_2\");\n        Map<String, Object> ip2 = new HashMap<>();\n        ip2.put(\"tp1\", \"workflow.input.param1\");\n        wft2.setInputParameters(ip2);\n        wft2.setTaskReferenceName(\"t2\");\n\n        WorkflowTask wft4 = new WorkflowTask();\n        wft4.setName(\"junit_task_4\");\n        wft4.setInputParameters(ip2);\n        wft4.setTaskReferenceName(\"t4\");\n\n        forkTask.getForkTasks().add(Arrays.asList(wft1, wft3));\n        forkTask.getForkTasks().add(Collections.singletonList(wft2));\n\n        def.getTasks().add(forkTask);\n\n        WorkflowTask join = new WorkflowTask();\n        join.setType(TaskType.JOIN.name());\n        join.setTaskReferenceName(\"forktask_join\");\n        join.setJoinOn(Arrays.asList(\"t3\", \"t2\"));\n\n        def.getTasks().add(wft4);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n\n        TaskModel task1 = new TaskModel();\n        task1.setReferenceTaskName(wft1.getTaskReferenceName());\n\n        TaskModel task3 = new TaskModel();\n        task3.setReferenceTaskName(wft3.getTaskReferenceName());\n\n        Mockito.when(deciderService.getTasksToBeScheduled(workflow, wft1, 0))\n                .thenReturn(Collections.singletonList(task1));\n        Mockito.when(deciderService.getTasksToBeScheduled(workflow, wft2, 0))\n                .thenReturn(Collections.singletonList(task3));\n\n        String taskId = idGenerator.generate();\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withWorkflowTask(forkTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .withDeciderService(deciderService)\n                        .build();\n\n        expectedException.expect(TerminateWorkflowException.class);\n        expectedException.expectMessage(\n                \"Fork task definition is not followed by a join task.  Check the blueprint\");\n        forkJoinTaskMapper.getMappedTasks(taskMapperContext);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/HTTPTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\n\npublic class HTTPTaskMapperTest {\n\n    private HTTPTaskMapper httpTaskMapper;\n    private IDGenerator idGenerator;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() {\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        MetadataDAO metadataDAO = mock(MetadataDAO.class);\n        httpTaskMapper = new HTTPTaskMapper(parametersUtils, metadataDAO);\n        idGenerator = new IDGenerator();\n    }\n\n    @Test\n    public void getMappedTasks() {\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"http_task\");\n        workflowTask.setType(TaskType.HTTP.name());\n        workflowTask.setTaskDefinition(new TaskDef(\"http_task\"));\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        // when\n        List<TaskModel> mappedTasks = httpTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(TaskType.HTTP.name(), mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void getMappedTasks_WithoutTaskDef() {\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"http_task\");\n        workflowTask.setType(TaskType.HTTP.name());\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(null)\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        // when\n        List<TaskModel> mappedTasks = httpTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(TaskType.HTTP.name(), mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/HumanTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_HUMAN;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\n\npublic class HumanTaskMapperTest {\n\n    @Test\n    public void getMappedTasks() {\n\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"human_task\");\n        workflowTask.setType(TaskType.HUMAN.name());\n        String taskId = new IDGenerator().generate();\n\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        HumanTaskMapper humanTaskMapper = new HumanTaskMapper(parametersUtils);\n        // When\n        List<TaskModel> mappedTasks = humanTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(TASK_TYPE_HUMAN, mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/InlineTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.evaluators.JavascriptEvaluator;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\n\npublic class InlineTaskMapperTest {\n\n    private ParametersUtils parametersUtils;\n    private MetadataDAO metadataDAO;\n\n    @Before\n    public void setUp() {\n        parametersUtils = mock(ParametersUtils.class);\n        metadataDAO = mock(MetadataDAO.class);\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"inline_task\");\n        workflowTask.setType(TaskType.INLINE.name());\n        workflowTask.setTaskDefinition(new TaskDef(\"inline_task\"));\n        workflowTask.setEvaluatorType(JavascriptEvaluator.NAME);\n        workflowTask.setExpression(\n                \"function scriptFun() {if ($.input.a==1){return {testValue: true}} else{return \"\n                        + \"{testValue: false} }}; scriptFun();\");\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks =\n                new InlineTaskMapper(parametersUtils, metadataDAO)\n                        .getMappedTasks(taskMapperContext);\n\n        assertEquals(1, mappedTasks.size());\n        assertNotNull(mappedTasks);\n        assertEquals(TaskType.INLINE.name(), mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void getMappedTasks_WithoutTaskDef() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setType(TaskType.INLINE.name());\n        workflowTask.setEvaluatorType(JavascriptEvaluator.NAME);\n        workflowTask.setExpression(\n                \"function scriptFun() {if ($.input.a==1){return {testValue: true}} else{return \"\n                        + \"{testValue: false} }}; scriptFun();\");\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(null)\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks =\n                new InlineTaskMapper(parametersUtils, metadataDAO)\n                        .getMappedTasks(taskMapperContext);\n\n        assertEquals(1, mappedTasks.size());\n        assertNotNull(mappedTasks);\n        assertEquals(TaskType.INLINE.name(), mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/JoinTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class JoinTaskMapperTest {\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setType(TaskType.JOIN.name());\n        workflowTask.setJoinOn(Arrays.asList(\"task1\", \"task2\"));\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowDef wd = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(wd);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks = new JoinTaskMapper().getMappedTasks(taskMapperContext);\n\n        assertNotNull(mappedTasks);\n        assertEquals(TASK_TYPE_JOIN, mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/JsonJQTransformTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\n\npublic class JsonJQTransformTaskMapperTest {\n\n    private IDGenerator idGenerator;\n    private ParametersUtils parametersUtils;\n    private MetadataDAO metadataDAO;\n\n    @Before\n    public void setUp() {\n        parametersUtils = mock(ParametersUtils.class);\n        metadataDAO = mock(MetadataDAO.class);\n        idGenerator = new IDGenerator();\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"json_jq_transform_task\");\n        workflowTask.setType(TaskType.JSON_JQ_TRANSFORM.name());\n        workflowTask.setTaskDefinition(new TaskDef(\"json_jq_transform_task\"));\n\n        Map<String, Object> taskInput = new HashMap<>();\n        taskInput.put(\"in1\", new String[] {\"a\", \"b\"});\n        taskInput.put(\"in2\", new String[] {\"c\", \"d\"});\n        taskInput.put(\"queryExpression\", \"{ out: (.in1 + .in2) }\");\n        workflowTask.setInputParameters(taskInput);\n\n        String taskId = idGenerator.generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(taskInput)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks =\n                new JsonJQTransformTaskMapper(parametersUtils, metadataDAO)\n                        .getMappedTasks(taskMapperContext);\n\n        assertEquals(1, mappedTasks.size());\n        assertNotNull(mappedTasks);\n        assertEquals(TaskType.JSON_JQ_TRANSFORM.name(), mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void getMappedTasks_WithoutTaskDef() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"json_jq_transform_task\");\n        workflowTask.setType(TaskType.JSON_JQ_TRANSFORM.name());\n\n        Map<String, Object> taskInput = new HashMap<>();\n        taskInput.put(\"in1\", new String[] {\"a\", \"b\"});\n        taskInput.put(\"in2\", new String[] {\"c\", \"d\"});\n        taskInput.put(\"queryExpression\", \"{ out: (.in1 + .in2) }\");\n        workflowTask.setInputParameters(taskInput);\n\n        String taskId = idGenerator.generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(null)\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(taskInput)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks =\n                new JsonJQTransformTaskMapper(parametersUtils, metadataDAO)\n                        .getMappedTasks(taskMapperContext);\n\n        assertEquals(1, mappedTasks.size());\n        assertNotNull(mappedTasks);\n        assertEquals(TaskType.JSON_JQ_TRANSFORM.name(), mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/KafkaPublishTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\n\npublic class KafkaPublishTaskMapperTest {\n\n    private IDGenerator idGenerator;\n    private KafkaPublishTaskMapper kafkaTaskMapper;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() {\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        MetadataDAO metadataDAO = mock(MetadataDAO.class);\n        kafkaTaskMapper = new KafkaPublishTaskMapper(parametersUtils, metadataDAO);\n        idGenerator = new IDGenerator();\n    }\n\n    @Test\n    public void getMappedTasks() {\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"kafka_task\");\n        workflowTask.setType(TaskType.KAFKA_PUBLISH.name());\n        workflowTask.setTaskDefinition(new TaskDef(\"kafka_task\"));\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        // when\n        List<TaskModel> mappedTasks = kafkaTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(TaskType.KAFKA_PUBLISH.name(), mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void getMappedTasks_WithoutTaskDef() {\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"kafka_task\");\n        workflowTask.setType(TaskType.KAFKA_PUBLISH.name());\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskDef taskdefinition = new TaskDef();\n        String testExecutionNameSpace = \"testExecutionNameSpace\";\n        taskdefinition.setExecutionNameSpace(testExecutionNameSpace);\n        String testIsolationGroupId = \"testIsolationGroupId\";\n        taskdefinition.setIsolationGroupId(testIsolationGroupId);\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(taskdefinition)\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        // when\n        List<TaskModel> mappedTasks = kafkaTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(TaskType.KAFKA_PUBLISH.name(), mappedTasks.get(0).getTaskType());\n        assertEquals(testExecutionNameSpace, mappedTasks.get(0).getExecutionNameSpace());\n        assertEquals(testIsolationGroupId, mappedTasks.get(0).getIsolationGroupId());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/LambdaTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\n\npublic class LambdaTaskMapperTest {\n\n    private IDGenerator idGenerator;\n    private ParametersUtils parametersUtils;\n    private MetadataDAO metadataDAO;\n\n    @Before\n    public void setUp() {\n        parametersUtils = mock(ParametersUtils.class);\n        metadataDAO = mock(MetadataDAO.class);\n        idGenerator = new IDGenerator();\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"lambda_task\");\n        workflowTask.setType(TaskType.LAMBDA.name());\n        workflowTask.setTaskDefinition(new TaskDef(\"lambda_task\"));\n        workflowTask.setScriptExpression(\n                \"if ($.input.a==1){return {testValue: true}} else{return {testValue: false} }\");\n\n        String taskId = idGenerator.generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks =\n                new LambdaTaskMapper(parametersUtils, metadataDAO)\n                        .getMappedTasks(taskMapperContext);\n\n        assertEquals(1, mappedTasks.size());\n        assertNotNull(mappedTasks);\n        assertEquals(TaskType.LAMBDA.name(), mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void getMappedTasks_WithoutTaskDef() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setType(TaskType.LAMBDA.name());\n        workflowTask.setScriptExpression(\n                \"if ($.input.a==1){return {testValue: true}} else{return {testValue: false} }\");\n\n        String taskId = idGenerator.generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(null)\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks =\n                new LambdaTaskMapper(parametersUtils, metadataDAO)\n                        .getMappedTasks(taskMapperContext);\n\n        assertEquals(1, mappedTasks.size());\n        assertNotNull(mappedTasks);\n        assertEquals(TaskType.LAMBDA.name(), mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/NoopTaskMapperTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\npublic class NoopTaskMapperTest {\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setType(TaskType.TASK_TYPE_NOOP);\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks = new NoopTaskMapper().getMappedTasks(taskMapperContext);\n\n        Assert.assertNotNull(mappedTasks);\n        Assert.assertEquals(1, mappedTasks.size());\n        Assert.assertEquals(TaskType.TASK_TYPE_NOOP, mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/SetVariableTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\npublic class SetVariableTaskMapperTest {\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setType(TaskType.TASK_TYPE_SET_VARIABLE);\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks = new SetVariableTaskMapper().getMappedTasks(taskMapperContext);\n\n        Assert.assertNotNull(mappedTasks);\n        Assert.assertEquals(1, mappedTasks.size());\n        Assert.assertEquals(TaskType.TASK_TYPE_SET_VARIABLE, mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/SimpleTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\n\npublic class SimpleTaskMapperTest {\n\n    private SimpleTaskMapper simpleTaskMapper;\n\n    private IDGenerator idGenerator = new IDGenerator();\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() {\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        simpleTaskMapper = new SimpleTaskMapper(parametersUtils);\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"simple_task\");\n        workflowTask.setTaskDefinition(new TaskDef(\"simple_task\"));\n\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks = simpleTaskMapper.getMappedTasks(taskMapperContext);\n        assertNotNull(mappedTasks);\n        assertEquals(1, mappedTasks.size());\n    }\n\n    @Test\n    public void getMappedTasksException() {\n\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"simple_task\");\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        // then\n        expectedException.expect(TerminateWorkflowException.class);\n        expectedException.expectMessage(\n                String.format(\n                        \"Invalid task. Task %s does not have a definition\",\n                        workflowTask.getName()));\n\n        // when\n        simpleTaskMapper.getMappedTasks(taskMapperContext);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/SubWorkflowTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class SubWorkflowTaskMapperTest {\n\n    private SubWorkflowTaskMapper subWorkflowTaskMapper;\n    private ParametersUtils parametersUtils;\n    private DeciderService deciderService;\n    private IDGenerator idGenerator;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() {\n        parametersUtils = mock(ParametersUtils.class);\n        MetadataDAO metadataDAO = mock(MetadataDAO.class);\n        subWorkflowTaskMapper = new SubWorkflowTaskMapper(parametersUtils, metadataDAO);\n        deciderService = mock(DeciderService.class);\n        idGenerator = new IDGenerator();\n    }\n\n    @Test\n    public void getMappedTasks() {\n        // Given\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        WorkflowTask workflowTask = new WorkflowTask();\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(\"Foo\");\n        subWorkflowParams.setVersion(2);\n        workflowTask.setSubWorkflowParam(subWorkflowParams);\n        workflowTask.setStartDelay(30);\n        Map<String, Object> taskInput = new HashMap<>();\n        Map<String, String> taskToDomain =\n                new HashMap<>() {\n                    {\n                        put(\"*\", \"unittest\");\n                    }\n                };\n\n        Map<String, Object> subWorkflowParamMap = new HashMap<>();\n        subWorkflowParamMap.put(\"name\", \"FooWorkFlow\");\n        subWorkflowParamMap.put(\"version\", 2);\n        subWorkflowParamMap.put(\"taskToDomain\", taskToDomain);\n        when(parametersUtils.getTaskInputV2(anyMap(), any(WorkflowModel.class), any(), any()))\n                .thenReturn(subWorkflowParamMap);\n\n        // When\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(taskInput)\n                        .withRetryCount(0)\n                        .withTaskId(idGenerator.generate())\n                        .withDeciderService(deciderService)\n                        .build();\n\n        List<TaskModel> mappedTasks = subWorkflowTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertFalse(mappedTasks.isEmpty());\n        assertEquals(1, mappedTasks.size());\n\n        TaskModel subWorkFlowTask = mappedTasks.get(0);\n        assertEquals(TaskModel.Status.SCHEDULED, subWorkFlowTask.getStatus());\n        assertEquals(TASK_TYPE_SUB_WORKFLOW, subWorkFlowTask.getTaskType());\n        assertEquals(30, subWorkFlowTask.getCallbackAfterSeconds());\n        assertEquals(taskToDomain, subWorkFlowTask.getInputData().get(\"subWorkflowTaskToDomain\"));\n    }\n\n    @Test\n    public void testTaskToDomain() {\n        // Given\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        WorkflowTask workflowTask = new WorkflowTask();\n        Map<String, String> taskToDomain =\n                new HashMap<>() {\n                    {\n                        put(\"*\", \"unittest\");\n                    }\n                };\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(\"Foo\");\n        subWorkflowParams.setVersion(2);\n        subWorkflowParams.setTaskToDomain(taskToDomain);\n        workflowTask.setSubWorkflowParam(subWorkflowParams);\n        Map<String, Object> taskInput = new HashMap<>();\n\n        Map<String, Object> subWorkflowParamMap = new HashMap<>();\n        subWorkflowParamMap.put(\"name\", \"FooWorkFlow\");\n        subWorkflowParamMap.put(\"version\", 2);\n\n        when(parametersUtils.getTaskInputV2(anyMap(), any(WorkflowModel.class), any(), any()))\n                .thenReturn(subWorkflowParamMap);\n\n        // When\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(taskInput)\n                        .withRetryCount(0)\n                        .withTaskId(new IDGenerator().generate())\n                        .withDeciderService(deciderService)\n                        .build();\n\n        List<TaskModel> mappedTasks = subWorkflowTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertFalse(mappedTasks.isEmpty());\n        assertEquals(1, mappedTasks.size());\n\n        TaskModel subWorkFlowTask = mappedTasks.get(0);\n        assertEquals(TaskModel.Status.SCHEDULED, subWorkFlowTask.getStatus());\n        assertEquals(TASK_TYPE_SUB_WORKFLOW, subWorkFlowTask.getTaskType());\n    }\n\n    @Test\n    public void getSubWorkflowParams() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(\"Foo\");\n        subWorkflowParams.setVersion(2);\n        workflowTask.setSubWorkflowParam(subWorkflowParams);\n\n        assertEquals(subWorkflowParams, subWorkflowTaskMapper.getSubWorkflowParams(workflowTask));\n    }\n\n    @Test\n    public void getExceptionWhenNoSubWorkflowParamsPassed() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"FooWorkFLow\");\n\n        expectedException.expect(TerminateWorkflowException.class);\n        expectedException.expectMessage(\n                String.format(\n                        \"Task %s is defined as sub-workflow and is missing subWorkflowParams. \"\n                                + \"Please check the workflow definition\",\n                        workflowTask.getName()));\n\n        subWorkflowTaskMapper.getSubWorkflowParams(workflowTask);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/SwitchTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.core.execution.evaluators.JavascriptEvaluator;\nimport com.netflix.conductor.core.execution.evaluators.ValueParamEvaluator;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@ContextConfiguration(\n        classes = {\n            TestObjectMapperConfiguration.class,\n            SwitchTaskMapperTest.TestConfiguration.class\n        })\n@RunWith(SpringRunner.class)\npublic class SwitchTaskMapperTest {\n\n    private IDGenerator idGenerator;\n    private ParametersUtils parametersUtils;\n    private DeciderService deciderService;\n    // Subject\n    private SwitchTaskMapper switchTaskMapper;\n\n    @Configuration\n    @ComponentScan(basePackageClasses = {Evaluator.class}) // load all Evaluator beans.\n    public static class TestConfiguration {}\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Autowired private Map<String, Evaluator> evaluators;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    Map<String, Object> ip1;\n    WorkflowTask task1;\n    WorkflowTask task2;\n    WorkflowTask task3;\n\n    @Before\n    public void setUp() {\n        parametersUtils = new ParametersUtils(objectMapper);\n        idGenerator = new IDGenerator();\n\n        ip1 = new HashMap<>();\n        ip1.put(\"p1\", \"${workflow.input.param1}\");\n        ip1.put(\"p2\", \"${workflow.input.param2}\");\n        ip1.put(\"case\", \"${workflow.input.case}\");\n\n        task1 = new WorkflowTask();\n        task1.setName(\"Test1\");\n        task1.setInputParameters(ip1);\n        task1.setTaskReferenceName(\"t1\");\n\n        task2 = new WorkflowTask();\n        task2.setName(\"Test2\");\n        task2.setInputParameters(ip1);\n        task2.setTaskReferenceName(\"t2\");\n\n        task3 = new WorkflowTask();\n        task3.setName(\"Test3\");\n        task3.setInputParameters(ip1);\n        task3.setTaskReferenceName(\"t3\");\n        deciderService = mock(DeciderService.class);\n        switchTaskMapper = new SwitchTaskMapper(evaluators);\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        // Given\n        // Task Definition\n        TaskDef taskDef = new TaskDef();\n        Map<String, Object> inputMap = new HashMap<>();\n        inputMap.put(\"Id\", \"${workflow.input.Id}\");\n        List<Map<String, Object>> taskDefinitionInput = new LinkedList<>();\n        taskDefinitionInput.add(inputMap);\n\n        // Switch task instance\n        WorkflowTask switchTask = new WorkflowTask();\n        switchTask.setType(TaskType.SWITCH.name());\n        switchTask.setName(\"Switch\");\n        switchTask.setTaskReferenceName(\"switchTask\");\n        switchTask.setDefaultCase(Collections.singletonList(task1));\n        switchTask.getInputParameters().put(\"Id\", \"${workflow.input.Id}\");\n        switchTask.setEvaluatorType(JavascriptEvaluator.NAME);\n        switchTask.setExpression(\n                \"if ($.Id == null) 'bad input'; else if ( ($.Id != null && $.Id % 2 == 0)) 'even'; else 'odd'; \");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"even\", Collections.singletonList(task2));\n        decisionCases.put(\"odd\", Collections.singletonList(task3));\n        switchTask.setDecisionCases(decisionCases);\n        // Workflow instance\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setSchemaVersion(2);\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\"Id\", \"22\");\n        workflowModel.setInput(workflowInput);\n\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input\", taskDefinitionInput);\n        taskDef.getInputTemplate().putAll(body);\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        switchTask.getInputParameters(), workflowModel, null, null);\n\n        TaskModel theTask = new TaskModel();\n        theTask.setReferenceTaskName(\"Foo\");\n        theTask.setTaskId(idGenerator.generate());\n\n        when(deciderService.getTasksToBeScheduled(workflowModel, task2, 0, null))\n                .thenReturn(Collections.singletonList(theTask));\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(switchTask)\n                        .withTaskInput(input)\n                        .withRetryCount(0)\n                        .withTaskId(idGenerator.generate())\n                        .withDeciderService(deciderService)\n                        .build();\n\n        // When\n        List<TaskModel> mappedTasks = switchTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(2, mappedTasks.size());\n        assertEquals(\"switchTask\", mappedTasks.get(0).getReferenceTaskName());\n        assertEquals(\"Foo\", mappedTasks.get(1).getReferenceTaskName());\n    }\n\n    @Test\n    public void getMappedTasksWithValueParamEvaluator() {\n\n        // Given\n        // Task Definition\n        TaskDef taskDef = new TaskDef();\n        Map<String, Object> inputMap = new HashMap<>();\n        inputMap.put(\"Id\", \"${workflow.input.Id}\");\n        List<Map<String, Object>> taskDefinitionInput = new LinkedList<>();\n        taskDefinitionInput.add(inputMap);\n\n        // Switch task instance\n        WorkflowTask switchTask = new WorkflowTask();\n        switchTask.setType(TaskType.SWITCH.name());\n        switchTask.setName(\"Switch\");\n        switchTask.setTaskReferenceName(\"switchTask\");\n        switchTask.setDefaultCase(Collections.singletonList(task1));\n        switchTask.getInputParameters().put(\"Id\", \"${workflow.input.Id}\");\n        switchTask.setEvaluatorType(ValueParamEvaluator.NAME);\n        switchTask.setExpression(\"Id\");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"even\", Collections.singletonList(task2));\n        decisionCases.put(\"odd\", Collections.singletonList(task3));\n        switchTask.setDecisionCases(decisionCases);\n        // Workflow instance\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setSchemaVersion(2);\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\"Id\", \"even\");\n        workflowModel.setInput(workflowInput);\n\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input\", taskDefinitionInput);\n        taskDef.getInputTemplate().putAll(body);\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        switchTask.getInputParameters(), workflowModel, null, null);\n\n        TaskModel theTask = new TaskModel();\n        theTask.setReferenceTaskName(\"Foo\");\n        theTask.setTaskId(idGenerator.generate());\n\n        when(deciderService.getTasksToBeScheduled(workflowModel, task2, 0, null))\n                .thenReturn(Collections.singletonList(theTask));\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(switchTask)\n                        .withTaskInput(input)\n                        .withRetryCount(0)\n                        .withTaskId(idGenerator.generate())\n                        .withDeciderService(deciderService)\n                        .build();\n\n        // When\n        List<TaskModel> mappedTasks = switchTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(2, mappedTasks.size());\n        assertEquals(\"switchTask\", mappedTasks.get(0).getReferenceTaskName());\n        assertEquals(\"Foo\", mappedTasks.get(1).getReferenceTaskName());\n    }\n\n    @Test\n    public void getMappedTasksWhenEvaluatorThrowsException() {\n\n        // Given\n        // Task Definition\n        TaskDef taskDef = new TaskDef();\n        Map<String, Object> inputMap = new HashMap<>();\n        List<Map<String, Object>> taskDefinitionInput = new LinkedList<>();\n        taskDefinitionInput.add(inputMap);\n\n        // Switch task instance\n        WorkflowTask switchTask = new WorkflowTask();\n        switchTask.setType(TaskType.SWITCH.name());\n        switchTask.setName(\"Switch\");\n        switchTask.setTaskReferenceName(\"switchTask\");\n        switchTask.setDefaultCase(Collections.singletonList(task1));\n        switchTask.setEvaluatorType(JavascriptEvaluator.NAME);\n        switchTask.setExpression(\"undefinedVariable\");\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        decisionCases.put(\"even\", Collections.singletonList(task2));\n        switchTask.setDecisionCases(decisionCases);\n        // Workflow instance\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setSchemaVersion(2);\n\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowDefinition(workflowDef);\n\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input\", taskDefinitionInput);\n        taskDef.getInputTemplate().putAll(body);\n\n        Map<String, Object> input =\n                parametersUtils.getTaskInput(\n                        switchTask.getInputParameters(), workflowModel, null, null);\n\n        TaskModel theTask = new TaskModel();\n        theTask.setReferenceTaskName(\"Foo\");\n        theTask.setTaskId(idGenerator.generate());\n\n        when(deciderService.getTasksToBeScheduled(workflowModel, task2, 0, null))\n                .thenReturn(Collections.singletonList(theTask));\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflowModel)\n                        .withWorkflowTask(switchTask)\n                        .withTaskInput(input)\n                        .withRetryCount(0)\n                        .withTaskId(idGenerator.generate())\n                        .withDeciderService(deciderService)\n                        .build();\n\n        // When\n        List<TaskModel> mappedTasks = switchTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(\"switchTask\", mappedTasks.get(0).getReferenceTaskName());\n        assertEquals(TaskModel.Status.FAILED, mappedTasks.get(0).getStatus());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/TerminateTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.List;\n\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.mockito.Mockito.mock;\n\npublic class TerminateTaskMapperTest {\n    private ParametersUtils parametersUtils;\n\n    @Before\n    public void setUp() {\n        parametersUtils = mock(ParametersUtils.class);\n    }\n\n    @Test\n    public void getMappedTasks() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setType(TaskType.TASK_TYPE_TERMINATE);\n\n        String taskId = new IDGenerator().generate();\n\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        List<TaskModel> mappedTasks =\n                new TerminateTaskMapper(parametersUtils).getMappedTasks(taskMapperContext);\n\n        Assert.assertNotNull(mappedTasks);\n        Assert.assertEquals(1, mappedTasks.size());\n        Assert.assertEquals(TaskType.TASK_TYPE_TERMINATE, mappedTasks.get(0).getTaskType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/UserDefinedTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\n\npublic class UserDefinedTaskMapperTest {\n\n    private IDGenerator idGenerator;\n\n    private UserDefinedTaskMapper userDefinedTaskMapper;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setUp() {\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        MetadataDAO metadataDAO = mock(MetadataDAO.class);\n        userDefinedTaskMapper = new UserDefinedTaskMapper(parametersUtils, metadataDAO);\n        idGenerator = new IDGenerator();\n    }\n\n    @Test\n    public void getMappedTasks() {\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"user_task\");\n        workflowTask.setType(TaskType.USER_DEFINED.name());\n        workflowTask.setTaskDefinition(new TaskDef(\"user_task\"));\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        // when\n        List<TaskModel> mappedTasks = userDefinedTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(TaskType.USER_DEFINED.name(), mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void getMappedTasksException() {\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"user_task\");\n        workflowTask.setType(TaskType.USER_DEFINED.name());\n        String taskId = idGenerator.generate();\n        String retriedTaskId = idGenerator.generate();\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withRetryTaskId(retriedTaskId)\n                        .withTaskId(taskId)\n                        .build();\n\n        // then\n        expectedException.expect(TerminateWorkflowException.class);\n        expectedException.expectMessage(\n                String.format(\n                        \"Invalid task specified. Cannot find task by name %s in the task definitions\",\n                        workflowTask.getName()));\n        // when\n        userDefinedTaskMapper.getMappedTasks(taskMapperContext);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/mapper/WaitTaskMapperTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.mapper;\n\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.tasks.Wait;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_WAIT;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\n\npublic class WaitTaskMapperTest {\n\n    @Test\n    public void getMappedTasks() {\n\n        // Given\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"Wait_task\");\n        workflowTask.setType(TaskType.WAIT.name());\n        String taskId = new IDGenerator().generate();\n\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        WaitTaskMapper waitTaskMapper = new WaitTaskMapper(parametersUtils);\n        // When\n        List<TaskModel> mappedTasks = waitTaskMapper.getMappedTasks(taskMapperContext);\n\n        // Then\n        assertEquals(1, mappedTasks.size());\n        assertEquals(TASK_TYPE_WAIT, mappedTasks.get(0).getTaskType());\n    }\n\n    @Test\n    public void testWaitForever() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"Wait_task\");\n        workflowTask.setType(TaskType.WAIT.name());\n        String taskId = new IDGenerator().generate();\n\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(new HashMap<>())\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        WaitTaskMapper waitTaskMapper = new WaitTaskMapper(parametersUtils);\n        // When\n        List<TaskModel> mappedTasks = waitTaskMapper.getMappedTasks(taskMapperContext);\n        assertEquals(1, mappedTasks.size());\n        assertEquals(mappedTasks.get(0).getStatus(), TaskModel.Status.IN_PROGRESS);\n        assertTrue(mappedTasks.get(0).getOutputData().isEmpty());\n    }\n\n    @Test\n    public void testWaitUntil() {\n\n        String dateFormat = \"yyyy-MM-dd HH:mm\";\n        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);\n        LocalDateTime now = LocalDateTime.now();\n        String formatted = formatter.format(now);\n        System.out.println(formatted);\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"Wait_task\");\n        workflowTask.setType(TaskType.WAIT.name());\n        String taskId = new IDGenerator().generate();\n        Map<String, Object> input = Map.of(Wait.UNTIL_INPUT, formatted);\n        workflowTask.setInputParameters(input);\n\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        doReturn(input).when(parametersUtils).getTaskInputV2(any(), any(), any(), any());\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(Map.of(Wait.UNTIL_INPUT, formatted))\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        WaitTaskMapper waitTaskMapper = new WaitTaskMapper(parametersUtils);\n        // When\n        List<TaskModel> mappedTasks = waitTaskMapper.getMappedTasks(taskMapperContext);\n        assertEquals(1, mappedTasks.size());\n        assertEquals(mappedTasks.get(0).getStatus(), TaskModel.Status.IN_PROGRESS);\n        assertEquals(mappedTasks.get(0).getCallbackAfterSeconds(), 0L);\n    }\n\n    @Test\n    public void testWaitDuration() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"Wait_task\");\n        workflowTask.setType(TaskType.WAIT.name());\n        String taskId = new IDGenerator().generate();\n        Map<String, Object> input = Map.of(Wait.DURATION_INPUT, \"1s\");\n        workflowTask.setInputParameters(input);\n\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        doReturn(input).when(parametersUtils).getTaskInputV2(any(), any(), any(), any());\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(Map.of(Wait.DURATION_INPUT, \"1s\"))\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        WaitTaskMapper waitTaskMapper = new WaitTaskMapper(parametersUtils);\n        // When\n        List<TaskModel> mappedTasks = waitTaskMapper.getMappedTasks(taskMapperContext);\n        assertEquals(1, mappedTasks.size());\n        assertEquals(mappedTasks.get(0).getStatus(), TaskModel.Status.IN_PROGRESS);\n        assertTrue(mappedTasks.get(0).getCallbackAfterSeconds() <= 1L);\n    }\n\n    @Test\n    public void testInvalidWaitConfig() {\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"Wait_task\");\n        workflowTask.setType(TaskType.WAIT.name());\n        String taskId = new IDGenerator().generate();\n        Map<String, Object> input =\n                Map.of(Wait.DURATION_INPUT, \"1s\", Wait.UNTIL_INPUT, \"2022-12-12\");\n        workflowTask.setInputParameters(input);\n\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        doReturn(input).when(parametersUtils).getTaskInputV2(any(), any(), any(), any());\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow.setWorkflowDefinition(workflowDef);\n\n        TaskMapperContext taskMapperContext =\n                TaskMapperContext.newBuilder()\n                        .withWorkflowModel(workflow)\n                        .withTaskDefinition(new TaskDef())\n                        .withWorkflowTask(workflowTask)\n                        .withTaskInput(\n                                Map.of(Wait.DURATION_INPUT, \"1s\", Wait.UNTIL_INPUT, \"2022-12-12\"))\n                        .withRetryCount(0)\n                        .withTaskId(taskId)\n                        .build();\n\n        WaitTaskMapper waitTaskMapper = new WaitTaskMapper(parametersUtils);\n        // When\n        List<TaskModel> mappedTasks = waitTaskMapper.getMappedTasks(taskMapperContext);\n        assertEquals(1, mappedTasks.size());\n        assertEquals(mappedTasks.get(0).getStatus(), TaskModel.Status.FAILED_WITH_TERMINAL_ERROR);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/EventQueueResolutionTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.events.EventQueueProvider;\nimport com.netflix.conductor.core.events.EventQueues;\nimport com.netflix.conductor.core.events.MockQueueProvider;\nimport com.netflix.conductor.core.events.queue.ObservableQueue;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n/**\n * Tests the {@link Event#computeQueueName(WorkflowModel, TaskModel)} and {@link\n * Event#getQueue(String, String)} methods with a real {@link ParametersUtils} object.\n */\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class EventQueueResolutionTest {\n\n    private WorkflowDef testWorkflowDefinition;\n    private EventQueues eventQueues;\n    private ParametersUtils parametersUtils;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void setup() {\n        Map<String, EventQueueProvider> providers = new HashMap<>();\n        providers.put(\"sqs\", new MockQueueProvider(\"sqs\"));\n        providers.put(\"conductor\", new MockQueueProvider(\"conductor\"));\n\n        parametersUtils = new ParametersUtils(objectMapper);\n        eventQueues = new EventQueues(providers, parametersUtils);\n\n        testWorkflowDefinition = new WorkflowDef();\n        testWorkflowDefinition.setName(\"testWorkflow\");\n        testWorkflowDefinition.setVersion(2);\n    }\n\n    @Test\n    public void testSinkParam() {\n        String sink = \"sqs:queue_name\";\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"wf0\");\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n\n        TaskModel task1 = new TaskModel();\n        task1.setReferenceTaskName(\"t1\");\n        task1.addOutput(\"q\", \"t1_queue\");\n        workflow.getTasks().add(task1);\n\n        TaskModel task2 = new TaskModel();\n        task2.setReferenceTaskName(\"t2\");\n        task2.addOutput(\"q\", \"task2_queue\");\n        workflow.getTasks().add(task2);\n\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(\"event\");\n        task.getInputData().put(\"sink\", sink);\n        task.setTaskType(TaskType.EVENT.name());\n        workflow.getTasks().add(task);\n\n        Event event = new Event(eventQueues, parametersUtils, objectMapper);\n        String queueName = event.computeQueueName(workflow, task);\n        ObservableQueue queue = event.getQueue(queueName, task.getTaskId());\n        assertNotNull(task.getReasonForIncompletion(), queue);\n        assertEquals(\"queue_name\", queue.getName());\n        assertEquals(\"sqs\", queue.getType());\n\n        sink = \"sqs:${t1.output.q}\";\n        task.getInputData().put(\"sink\", sink);\n        queueName = event.computeQueueName(workflow, task);\n        queue = event.getQueue(queueName, task.getTaskId());\n        assertNotNull(queue);\n        assertEquals(\"t1_queue\", queue.getName());\n        assertEquals(\"sqs\", queue.getType());\n\n        sink = \"sqs:${t2.output.q}\";\n        task.getInputData().put(\"sink\", sink);\n        queueName = event.computeQueueName(workflow, task);\n        queue = event.getQueue(queueName, task.getTaskId());\n        assertNotNull(queue);\n        assertEquals(\"task2_queue\", queue.getName());\n        assertEquals(\"sqs\", queue.getType());\n\n        sink = \"conductor\";\n        task.getInputData().put(\"sink\", sink);\n        queueName = event.computeQueueName(workflow, task);\n        queue = event.getQueue(queueName, task.getTaskId());\n        assertNotNull(queue);\n        assertEquals(\n                workflow.getWorkflowName() + \":\" + task.getReferenceTaskName(), queue.getName());\n        assertEquals(\"conductor\", queue.getType());\n\n        sink = \"sqs:static_value\";\n        task.getInputData().put(\"sink\", sink);\n        queueName = event.computeQueueName(workflow, task);\n        queue = event.getQueue(queueName, task.getTaskId());\n        assertNotNull(queue);\n        assertEquals(\"static_value\", queue.getName());\n        assertEquals(\"sqs\", queue.getType());\n    }\n\n    @Test\n    public void testDynamicSinks() {\n        Event event = new Event(eventQueues, parametersUtils, objectMapper);\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(testWorkflowDefinition);\n\n        TaskModel task = new TaskModel();\n        task.setReferenceTaskName(\"task0\");\n        task.setTaskId(\"task_id_0\");\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.getInputData().put(\"sink\", \"conductor:some_arbitary_queue\");\n\n        String queueName = event.computeQueueName(workflow, task);\n        ObservableQueue queue = event.getQueue(queueName, task.getTaskId());\n        assertEquals(TaskModel.Status.IN_PROGRESS, task.getStatus());\n        assertNotNull(queue);\n        assertEquals(\"testWorkflow:some_arbitary_queue\", queue.getName());\n        assertEquals(\"testWorkflow:some_arbitary_queue\", queue.getURI());\n        assertEquals(\"conductor\", queue.getType());\n\n        task.getInputData().put(\"sink\", \"conductor\");\n        queueName = event.computeQueueName(workflow, task);\n        queue = event.getQueue(queueName, task.getTaskId());\n        assertEquals(\n                \"not in progress: \" + task.getReasonForIncompletion(),\n                TaskModel.Status.IN_PROGRESS,\n                task.getStatus());\n        assertNotNull(queue);\n        assertEquals(\"testWorkflow:task0\", queue.getName());\n\n        task.getInputData().put(\"sink\", \"sqs:my_sqs_queue_name\");\n        queueName = event.computeQueueName(workflow, task);\n        queue = event.getQueue(queueName, task.getTaskId());\n        assertEquals(\n                \"not in progress: \" + task.getReasonForIncompletion(),\n                TaskModel.Status.IN_PROGRESS,\n                task.getStatus());\n        assertNotNull(queue);\n        assertEquals(\"my_sqs_queue_name\", queue.getName());\n        assertEquals(\"sqs\", queue.getType());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/InlineTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.evaluators.Evaluator;\nimport com.netflix.conductor.core.execution.evaluators.JavascriptEvaluator;\nimport com.netflix.conductor.core.execution.evaluators.ValueParamEvaluator;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.mock;\n\npublic class InlineTest {\n\n    private final WorkflowModel workflow = new WorkflowModel();\n    private final WorkflowExecutor executor = mock(WorkflowExecutor.class);\n\n    @Test\n    public void testInlineTaskValidationFailures() {\n        Inline inline = new Inline(getStringEvaluatorMap());\n\n        Map<String, Object> inputObj = new HashMap<>();\n        inputObj.put(\"value\", 1);\n        inputObj.put(\"expression\", \"\");\n        inputObj.put(\"evaluatorType\", \"value-param\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(inputObj);\n        inline.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.FAILED_WITH_TERMINAL_ERROR, task.getStatus());\n        assertEquals(\n                \"Empty 'expression' in Inline task's input parameters. A non-empty String value must be provided.\",\n                task.getReasonForIncompletion());\n\n        inputObj = new HashMap<>();\n        inputObj.put(\"value\", 1);\n        inputObj.put(\"expression\", \"value\");\n        inputObj.put(\"evaluatorType\", \"\");\n\n        task = new TaskModel();\n        task.getInputData().putAll(inputObj);\n        inline.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.FAILED_WITH_TERMINAL_ERROR, task.getStatus());\n        assertEquals(\n                \"Empty 'evaluatorType' in INLINE task's input parameters. A non-empty String value must be provided.\",\n                task.getReasonForIncompletion());\n    }\n\n    @Test\n    public void testInlineValueParamExpression() {\n        Inline inline = new Inline(getStringEvaluatorMap());\n\n        Map<String, Object> inputObj = new HashMap<>();\n        inputObj.put(\"value\", 101);\n        inputObj.put(\"expression\", \"value\");\n        inputObj.put(\"evaluatorType\", \"value-param\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(inputObj);\n\n        inline.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n        assertEquals(101, task.getOutputData().get(\"result\"));\n\n        inputObj = new HashMap<>();\n        inputObj.put(\"value\", \"StringValue\");\n        inputObj.put(\"expression\", \"value\");\n        inputObj.put(\"evaluatorType\", \"value-param\");\n\n        task = new TaskModel();\n        task.getInputData().putAll(inputObj);\n\n        inline.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n        assertEquals(\"StringValue\", task.getOutputData().get(\"result\"));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testInlineJavascriptExpression() {\n        Inline inline = new Inline(getStringEvaluatorMap());\n\n        Map<String, Object> inputObj = new HashMap<>();\n        inputObj.put(\"value\", 101);\n        inputObj.put(\n                \"expression\",\n                \"function e() { if ($.value == 101){return {\\\"evalResult\\\": true}} else { return {\\\"evalResult\\\": false}}} e();\");\n        inputObj.put(\"evaluatorType\", \"javascript\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(inputObj);\n\n        inline.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n        assertEquals(\n                true, ((Map<String, Object>) task.getOutputData().get(\"result\")).get(\"evalResult\"));\n\n        inputObj = new HashMap<>();\n        inputObj.put(\"value\", \"StringValue\");\n        inputObj.put(\n                \"expression\",\n                \"function e() { if ($.value == 'StringValue'){return {\\\"evalResult\\\": true}} else { return {\\\"evalResult\\\": false}}} e();\");\n        inputObj.put(\"evaluatorType\", \"javascript\");\n\n        task = new TaskModel();\n        task.getInputData().putAll(inputObj);\n\n        inline.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n        assertEquals(\n                true, ((Map<String, Object>) task.getOutputData().get(\"result\")).get(\"evalResult\"));\n    }\n\n    private Map<String, Evaluator> getStringEvaluatorMap() {\n        Map<String, Evaluator> evaluators = new HashMap<>();\n        evaluators.put(ValueParamEvaluator.NAME, new ValueParamEvaluator());\n        evaluators.put(JavascriptEvaluator.NAME, new JavascriptEvaluator());\n        return evaluators;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/TestLambda.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\n\n/**\n * @author x-ultra\n */\npublic class TestLambda {\n\n    private final WorkflowModel workflow = new WorkflowModel();\n    private final WorkflowExecutor executor = mock(WorkflowExecutor.class);\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    @Test\n    public void start() {\n        Lambda lambda = new Lambda();\n\n        Map inputObj = new HashMap();\n        inputObj.put(\"a\", 1);\n\n        // test for scriptExpression == null\n        TaskModel task = new TaskModel();\n        task.getInputData().put(\"input\", inputObj);\n        lambda.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n\n        // test for normal\n        task = new TaskModel();\n        task.getInputData().put(\"input\", inputObj);\n        task.getInputData().put(\"scriptExpression\", \"if ($.input.a==1){return 1}else{return 0 } \");\n        lambda.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertEquals(task.getOutputData().toString(), \"{result=1}\");\n\n        // test for scriptExpression ScriptException\n        task = new TaskModel();\n        task.getInputData().put(\"input\", inputObj);\n        task.getInputData().put(\"scriptExpression\", \"if ($.a.size==1){return 1}else{return 0 } \");\n        lambda.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/TestNoop.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.*;\n\npublic class TestNoop {\n\n    private final WorkflowExecutor executor = mock(WorkflowExecutor.class);\n\n    @Test\n    public void should_do_nothing() {\n        WorkflowModel workflow = new WorkflowModel();\n        Noop noopTask = new Noop();\n        TaskModel task = new TaskModel();\n        noopTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/TestSubWorkflow.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.execution.StartWorkflowInput;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class TestSubWorkflow {\n\n    private WorkflowExecutor workflowExecutor;\n    private SubWorkflow subWorkflow;\n    private StartWorkflowOperation startWorkflowOperation;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void setup() {\n        workflowExecutor = mock(WorkflowExecutor.class);\n        startWorkflowOperation = mock(StartWorkflowOperation.class);\n        subWorkflow = new SubWorkflow(objectMapper, startWorkflowOperation);\n    }\n\n    @Test\n    public void testStartSubWorkflow() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 3);\n        task.setInputData(inputData);\n\n        String workflowId = \"workflow_1\";\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(workflowId);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(3);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(workflowId);\n\n        when(workflowExecutor.getWorkflow(anyString(), eq(false))).thenReturn(workflow);\n\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertEquals(\"workflow_1\", task.getSubWorkflowId());\n        assertEquals(TaskModel.Status.IN_PROGRESS, task.getStatus());\n\n        workflow.setStatus(WorkflowModel.Status.TERMINATED);\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertEquals(\"workflow_1\", task.getSubWorkflowId());\n        assertEquals(TaskModel.Status.CANCELED, task.getStatus());\n\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertEquals(\"workflow_1\", task.getSubWorkflowId());\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n    }\n\n    @Test\n    public void testStartSubWorkflowQueueFailure() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n        task.setStatus(TaskModel.Status.SCHEDULED);\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 3);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(3);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput))\n                .thenThrow(new TransientException(\"QueueDAO failure\"));\n\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertNull(\"subWorkflowId should be null\", task.getSubWorkflowId());\n        assertEquals(TaskModel.Status.SCHEDULED, task.getStatus());\n        assertTrue(\"Output data should be empty\", task.getOutputData().isEmpty());\n    }\n\n    @Test\n    public void testStartSubWorkflowStartError() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n        task.setStatus(TaskModel.Status.SCHEDULED);\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 3);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(3);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        String failureReason = \"non transient failure\";\n        when(startWorkflowOperation.execute(startWorkflowInput))\n                .thenThrow(new NonTransientException(failureReason));\n\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertNull(\"subWorkflowId should be null\", task.getSubWorkflowId());\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n        assertEquals(failureReason, task.getReasonForIncompletion());\n        assertTrue(\"Output data should be empty\", task.getOutputData().isEmpty());\n    }\n\n    @Test\n    public void testStartSubWorkflowWithEmptyWorkflowInput() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 3);\n\n        Map<String, Object> workflowInput = new HashMap<>();\n        inputData.put(\"workflowInput\", workflowInput);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(3);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertEquals(\"workflow_1\", task.getSubWorkflowId());\n    }\n\n    @Test\n    public void testStartSubWorkflowWithWorkflowInput() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 3);\n\n        Map<String, Object> workflowInput = new HashMap<>();\n        workflowInput.put(\"test\", \"value\");\n        inputData.put(\"workflowInput\", workflowInput);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(3);\n        startWorkflowInput.setWorkflowInput(workflowInput);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertEquals(\"workflow_1\", task.getSubWorkflowId());\n    }\n\n    @Test\n    public void testStartSubWorkflowTaskToDomain() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n        Map<String, String> taskToDomain =\n                new HashMap<>() {\n                    {\n                        put(\"*\", \"unittest\");\n                    }\n                };\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 2);\n        inputData.put(\"subWorkflowTaskToDomain\", taskToDomain);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(2);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(taskToDomain);\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertEquals(\"workflow_1\", task.getSubWorkflowId());\n    }\n\n    @Test\n    public void testExecuteSubWorkflowWithoutId() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 2);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(2);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n\n        assertFalse(subWorkflow.execute(workflowInstance, task, workflowExecutor));\n    }\n\n    @Test\n    public void testExecuteWorkflowStatus() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        WorkflowModel subWorkflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n        Map<String, String> taskToDomain =\n                new HashMap<>() {\n                    {\n                        put(\"*\", \"unittest\");\n                    }\n                };\n\n        TaskModel task = new TaskModel();\n        Map<String, Object> outputData = new HashMap<>();\n        task.setOutputData(outputData);\n        task.setSubWorkflowId(\"sub-workflow-id\");\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 2);\n        inputData.put(\"subWorkflowTaskToDomain\", taskToDomain);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(2);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(taskToDomain);\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n        when(workflowExecutor.getWorkflow(eq(\"sub-workflow-id\"), eq(false)))\n                .thenReturn(subWorkflowInstance);\n\n        subWorkflowInstance.setStatus(WorkflowModel.Status.RUNNING);\n        assertFalse(subWorkflow.execute(workflowInstance, task, workflowExecutor));\n        assertNull(task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n\n        subWorkflowInstance.setStatus(WorkflowModel.Status.PAUSED);\n        assertFalse(subWorkflow.execute(workflowInstance, task, workflowExecutor));\n        assertNull(task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n\n        subWorkflowInstance.setStatus(WorkflowModel.Status.COMPLETED);\n        assertTrue(subWorkflow.execute(workflowInstance, task, workflowExecutor));\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertNull(task.getReasonForIncompletion());\n\n        subWorkflowInstance.setStatus(WorkflowModel.Status.FAILED);\n        subWorkflowInstance.setReasonForIncompletion(\"unit1\");\n        assertTrue(subWorkflow.execute(workflowInstance, task, workflowExecutor));\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n        assertTrue(task.getReasonForIncompletion().contains(\"unit1\"));\n\n        subWorkflowInstance.setStatus(WorkflowModel.Status.TIMED_OUT);\n        subWorkflowInstance.setReasonForIncompletion(\"unit2\");\n        assertTrue(subWorkflow.execute(workflowInstance, task, workflowExecutor));\n        assertEquals(TaskModel.Status.TIMED_OUT, task.getStatus());\n        assertTrue(task.getReasonForIncompletion().contains(\"unit2\"));\n\n        subWorkflowInstance.setStatus(WorkflowModel.Status.TERMINATED);\n        subWorkflowInstance.setReasonForIncompletion(\"unit3\");\n        assertTrue(subWorkflow.execute(workflowInstance, task, workflowExecutor));\n        assertEquals(TaskModel.Status.CANCELED, task.getStatus());\n        assertTrue(task.getReasonForIncompletion().contains(\"unit3\"));\n    }\n\n    @Test\n    public void testCancelWithWorkflowId() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        WorkflowModel subWorkflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        task.setSubWorkflowId(\"sub-workflow-id\");\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 2);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(2);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n        when(workflowExecutor.getWorkflow(eq(\"sub-workflow-id\"), eq(true)))\n                .thenReturn(subWorkflowInstance);\n\n        workflowInstance.setStatus(WorkflowModel.Status.TIMED_OUT);\n        subWorkflow.cancel(workflowInstance, task, workflowExecutor);\n\n        assertEquals(WorkflowModel.Status.TERMINATED, subWorkflowInstance.getStatus());\n    }\n\n    @Test\n    public void testCancelWithoutWorkflowId() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        WorkflowModel subWorkflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        TaskModel task = new TaskModel();\n        Map<String, Object> outputData = new HashMap<>();\n        task.setOutputData(outputData);\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 2);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"UnitWorkFlow\");\n        startWorkflowInput.setVersion(2);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n        when(workflowExecutor.getWorkflow(eq(\"sub-workflow-id\"), eq(false)))\n                .thenReturn(subWorkflowInstance);\n\n        subWorkflow.cancel(workflowInstance, task, workflowExecutor);\n\n        assertEquals(WorkflowModel.Status.RUNNING, subWorkflowInstance.getStatus());\n    }\n\n    @Test\n    public void testIsAsync() {\n        assertTrue(subWorkflow.isAsync());\n    }\n\n    @Test\n    public void testStartSubWorkflowWithSubWorkflowDefinition() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        WorkflowModel workflowInstance = new WorkflowModel();\n        workflowInstance.setWorkflowDefinition(workflowDef);\n\n        WorkflowDef subWorkflowDef = new WorkflowDef();\n        subWorkflowDef.setName(\"subWorkflow_1\");\n\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"subWorkflowName\", \"UnitWorkFlow\");\n        inputData.put(\"subWorkflowVersion\", 2);\n        inputData.put(\"subWorkflowDefinition\", subWorkflowDef);\n        task.setInputData(inputData);\n\n        StartWorkflowInput startWorkflowInput = new StartWorkflowInput();\n        startWorkflowInput.setName(\"subWorkflow_1\");\n        startWorkflowInput.setVersion(2);\n        startWorkflowInput.setWorkflowInput(inputData);\n        startWorkflowInput.setWorkflowDefinition(subWorkflowDef);\n        startWorkflowInput.setTaskToDomain(workflowInstance.getTaskToDomain());\n\n        when(startWorkflowOperation.execute(startWorkflowInput)).thenReturn(\"workflow_1\");\n\n        subWorkflow.start(workflowInstance, task, workflowExecutor);\n        assertEquals(\"workflow_1\", task.getSubWorkflowId());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/TestSystemTaskWorker.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\n\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.execution.AsyncSystemTaskExecutor;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.service.ExecutionService;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class TestSystemTaskWorker {\n\n    private static final String TEST_TASK = \"system_task\";\n    private static final String ISOLATED_TASK = \"system_task-isolated\";\n\n    private AsyncSystemTaskExecutor asyncSystemTaskExecutor;\n    private ExecutionService executionService;\n    private QueueDAO queueDAO;\n    private ConductorProperties properties;\n\n    private SystemTaskWorker systemTaskWorker;\n\n    @Before\n    public void setUp() {\n        asyncSystemTaskExecutor = mock(AsyncSystemTaskExecutor.class);\n        executionService = mock(ExecutionService.class);\n        queueDAO = mock(QueueDAO.class);\n        properties = mock(ConductorProperties.class);\n\n        when(properties.getSystemTaskWorkerThreadCount()).thenReturn(10);\n        when(properties.getIsolatedSystemTaskWorkerThreadCount()).thenReturn(10);\n        when(properties.getSystemTaskWorkerCallbackDuration()).thenReturn(Duration.ofSeconds(30));\n        when(properties.getSystemTaskWorkerPollInterval()).thenReturn(Duration.ofSeconds(30));\n\n        systemTaskWorker =\n                new SystemTaskWorker(\n                        queueDAO, asyncSystemTaskExecutor, properties, executionService);\n        systemTaskWorker.start();\n    }\n\n    @After\n    public void tearDown() {\n        systemTaskWorker.queueExecutionConfigMap.clear();\n        systemTaskWorker.stop();\n    }\n\n    @Test\n    public void testGetExecutionConfigForSystemTask() {\n        when(properties.getSystemTaskWorkerThreadCount()).thenReturn(5);\n        systemTaskWorker =\n                new SystemTaskWorker(\n                        queueDAO, asyncSystemTaskExecutor, properties, executionService);\n        assertEquals(\n                systemTaskWorker.getExecutionConfig(\"\").getSemaphoreUtil().availableSlots(), 5);\n    }\n\n    @Test\n    public void testGetExecutionConfigForIsolatedSystemTask() {\n        when(properties.getIsolatedSystemTaskWorkerThreadCount()).thenReturn(7);\n        systemTaskWorker =\n                new SystemTaskWorker(\n                        queueDAO, asyncSystemTaskExecutor, properties, executionService);\n        assertEquals(\n                systemTaskWorker.getExecutionConfig(\"test-iso\").getSemaphoreUtil().availableSlots(),\n                7);\n    }\n\n    @Test\n    public void testPollAndExecuteSystemTask() throws Exception {\n        when(queueDAO.pop(anyString(), anyInt(), anyInt()))\n                .thenReturn(Collections.singletonList(\"taskId\"));\n\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            latch.countDown();\n                            return null;\n                        })\n                .when(asyncSystemTaskExecutor)\n                .execute(any(), anyString());\n\n        systemTaskWorker.pollAndExecute(new TestTask(), TEST_TASK);\n\n        latch.await();\n\n        verify(asyncSystemTaskExecutor).execute(any(), anyString());\n    }\n\n    @Test\n    public void testBatchPollAndExecuteSystemTask() throws Exception {\n        when(queueDAO.pop(anyString(), anyInt(), anyInt())).thenReturn(List.of(\"t1\", \"t1\"));\n\n        CountDownLatch latch = new CountDownLatch(2);\n        doAnswer(\n                        invocation -> {\n                            latch.countDown();\n                            return null;\n                        })\n                .when(asyncSystemTaskExecutor)\n                .execute(any(), eq(\"t1\"));\n\n        systemTaskWorker.pollAndExecute(new TestTask(), TEST_TASK);\n\n        latch.await();\n\n        verify(asyncSystemTaskExecutor, Mockito.times(2)).execute(any(), eq(\"t1\"));\n    }\n\n    @Test\n    public void testPollAndExecuteIsolatedSystemTask() throws Exception {\n        when(queueDAO.pop(anyString(), anyInt(), anyInt())).thenReturn(List.of(\"isolated_taskId\"));\n\n        CountDownLatch latch = new CountDownLatch(1);\n        doAnswer(\n                        invocation -> {\n                            latch.countDown();\n                            return null;\n                        })\n                .when(asyncSystemTaskExecutor)\n                .execute(any(), eq(\"isolated_taskId\"));\n\n        systemTaskWorker.pollAndExecute(new IsolatedTask(), ISOLATED_TASK);\n\n        latch.await();\n\n        verify(asyncSystemTaskExecutor, Mockito.times(1)).execute(any(), eq(\"isolated_taskId\"));\n    }\n\n    @Test\n    public void testPollException() {\n        when(properties.getSystemTaskWorkerThreadCount()).thenReturn(1);\n        when(queueDAO.pop(anyString(), anyInt(), anyInt())).thenThrow(RuntimeException.class);\n\n        systemTaskWorker.pollAndExecute(new TestTask(), TEST_TASK);\n\n        verify(asyncSystemTaskExecutor, Mockito.never()).execute(any(), anyString());\n    }\n\n    @Test\n    public void testBatchPollException() {\n        when(properties.getSystemTaskWorkerThreadCount()).thenReturn(2);\n        when(queueDAO.pop(anyString(), anyInt(), anyInt())).thenThrow(RuntimeException.class);\n\n        systemTaskWorker.pollAndExecute(new TestTask(), TEST_TASK);\n\n        verify(asyncSystemTaskExecutor, Mockito.never()).execute(any(), anyString());\n    }\n\n    static class TestTask extends WorkflowSystemTask {\n        public TestTask() {\n            super(TEST_TASK);\n        }\n    }\n\n    static class IsolatedTask extends WorkflowSystemTask {\n        public IsolatedTask() {\n            super(ISOLATED_TASK);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/TestSystemTaskWorkerCoordinator.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.time.Duration;\nimport java.util.Collections;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\n\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class TestSystemTaskWorkerCoordinator {\n\n    private static final String TEST_QUEUE = \"test\";\n    private static final String EXECUTION_NAMESPACE_CONSTANT = \"@exeNS\";\n\n    private SystemTaskWorker systemTaskWorker;\n    private ConductorProperties properties;\n\n    @Before\n    public void setUp() {\n        systemTaskWorker = mock(SystemTaskWorker.class);\n        properties = mock(ConductorProperties.class);\n        when(properties.getSystemTaskWorkerPollInterval()).thenReturn(Duration.ofMillis(50));\n        when(properties.getSystemTaskWorkerExecutionNamespace()).thenReturn(\"\");\n    }\n\n    @Test\n    public void testIsFromCoordinatorExecutionNameSpace() {\n        doReturn(\"exeNS\").when(properties).getSystemTaskWorkerExecutionNamespace();\n        SystemTaskWorkerCoordinator systemTaskWorkerCoordinator =\n                new SystemTaskWorkerCoordinator(\n                        systemTaskWorker, properties, Collections.emptySet());\n        assertTrue(\n                systemTaskWorkerCoordinator.isFromCoordinatorExecutionNameSpace(\n                        new TaskWithExecutionNamespace()));\n    }\n\n    static class TaskWithExecutionNamespace extends WorkflowSystemTask {\n        public TaskWithExecutionNamespace() {\n            super(TEST_QUEUE + EXECUTION_NAMESPACE_CONSTANT);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/execution/tasks/TestTerminate.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.execution.tasks;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.core.execution.tasks.Terminate.getTerminationStatusParameter;\nimport static com.netflix.conductor.core.execution.tasks.Terminate.getTerminationWorkflowOutputParameter;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.mock;\n\npublic class TestTerminate {\n\n    private final WorkflowExecutor executor = mock(WorkflowExecutor.class);\n\n    @Test\n    public void should_fail_if_input_status_is_not_valid() {\n        WorkflowModel workflow = new WorkflowModel();\n        Terminate terminateTask = new Terminate();\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(getTerminationStatusParameter(), \"PAUSED\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(input);\n        terminateTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n    }\n\n    @Test\n    public void should_fail_if_input_status_is_empty() {\n        WorkflowModel workflow = new WorkflowModel();\n        Terminate terminateTask = new Terminate();\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(getTerminationStatusParameter(), \"\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(input);\n        terminateTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n    }\n\n    @Test\n    public void should_fail_if_input_status_is_null() {\n        WorkflowModel workflow = new WorkflowModel();\n        Terminate terminateTask = new Terminate();\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(getTerminationStatusParameter(), null);\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(input);\n        terminateTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n    }\n\n    @Test\n    public void should_complete_workflow_on_terminate_task_success() {\n        WorkflowModel workflow = new WorkflowModel();\n        Terminate terminateTask = new Terminate();\n        workflow.setOutput(Collections.singletonMap(\"output\", \"${task1.output.value}\"));\n\n        HashMap<String, Object> expectedOutput =\n                new HashMap<>() {\n                    {\n                        put(\"output\", \"${task0.output.value}\");\n                    }\n                };\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(getTerminationStatusParameter(), \"COMPLETED\");\n        input.put(getTerminationWorkflowOutputParameter(), \"${task0.output.value}\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(input);\n        terminateTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertEquals(expectedOutput, task.getOutputData());\n    }\n\n    @Test\n    public void should_fail_workflow_on_terminate_task_success() {\n        WorkflowModel workflow = new WorkflowModel();\n        Terminate terminateTask = new Terminate();\n        workflow.setOutput(Collections.singletonMap(\"output\", \"${task1.output.value}\"));\n\n        HashMap<String, Object> expectedOutput =\n                new HashMap<>() {\n                    {\n                        put(\"output\", \"${task0.output.value}\");\n                    }\n                };\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(getTerminationStatusParameter(), \"FAILED\");\n        input.put(getTerminationWorkflowOutputParameter(), \"${task0.output.value}\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(input);\n        terminateTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertEquals(expectedOutput, task.getOutputData());\n    }\n\n    @Test\n    public void should_fail_workflow_on_terminate_task_success_with_empty_output() {\n        WorkflowModel workflow = new WorkflowModel();\n        Terminate terminateTask = new Terminate();\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(getTerminationStatusParameter(), \"FAILED\");\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(input);\n        terminateTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertTrue(task.getOutputData().isEmpty());\n    }\n\n    @Test\n    public void should_fail_workflow_on_terminate_task_success_with_resolved_output() {\n        WorkflowModel workflow = new WorkflowModel();\n        Terminate terminateTask = new Terminate();\n\n        HashMap<String, Object> expectedOutput =\n                new HashMap<>() {\n                    {\n                        put(\"result\", 1);\n                    }\n                };\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(getTerminationStatusParameter(), \"FAILED\");\n        input.put(getTerminationWorkflowOutputParameter(), expectedOutput);\n\n        TaskModel task = new TaskModel();\n        task.getInputData().putAll(input);\n        terminateTask.execute(workflow, task, executor);\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/metadata/MetadataMapperServiceTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.metadata;\n\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolationException;\n\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.dao.MetadataDAO;\n\nimport static com.netflix.conductor.TestUtils.getConstraintViolationMessages;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assert.fail;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\n@SuppressWarnings(\"SpringJavaAutowiredMembersInspection\")\n@RunWith(SpringRunner.class)\n@EnableAutoConfiguration\npublic class MetadataMapperServiceTest {\n\n    @TestConfiguration\n    static class TestMetadataMapperServiceConfiguration {\n\n        @Bean\n        public MetadataDAO metadataDAO() {\n            return mock(MetadataDAO.class);\n        }\n\n        @Bean\n        public MetadataMapperService metadataMapperService(MetadataDAO metadataDAO) {\n            return new MetadataMapperService(metadataDAO);\n        }\n    }\n\n    @Autowired private MetadataDAO metadataDAO;\n\n    @Autowired private MetadataMapperService metadataMapperService;\n\n    @After\n    public void cleanUp() {\n        reset(metadataDAO);\n    }\n\n    @Test\n    public void testMetadataPopulationOnSimpleTask() {\n        String nameTaskDefinition = \"task1\";\n        TaskDef taskDefinition = createTaskDefinition(nameTaskDefinition);\n        WorkflowTask workflowTask = createWorkflowTask(nameTaskDefinition);\n\n        when(metadataDAO.getTaskDef(nameTaskDefinition)).thenReturn(taskDefinition);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask));\n\n        metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        assertEquals(1, workflowDefinition.getTasks().size());\n        WorkflowTask populatedWorkflowTask = workflowDefinition.getTasks().get(0);\n        assertNotNull(populatedWorkflowTask.getTaskDefinition());\n        verify(metadataDAO).getTaskDef(nameTaskDefinition);\n    }\n\n    @Test\n    public void testNoMetadataPopulationOnEmbeddedTaskDefinition() {\n        String nameTaskDefinition = \"task2\";\n        TaskDef taskDefinition = createTaskDefinition(nameTaskDefinition);\n        WorkflowTask workflowTask = createWorkflowTask(nameTaskDefinition);\n        workflowTask.setTaskDefinition(taskDefinition);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask));\n\n        metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        assertEquals(1, workflowDefinition.getTasks().size());\n        WorkflowTask populatedWorkflowTask = workflowDefinition.getTasks().get(0);\n        assertNotNull(populatedWorkflowTask.getTaskDefinition());\n        verifyNoInteractions(metadataDAO);\n    }\n\n    @Test\n    public void testMetadataPopulationOnlyOnNecessaryWorkflowTasks() {\n        String nameTaskDefinition1 = \"task4\";\n        TaskDef taskDefinition = createTaskDefinition(nameTaskDefinition1);\n        WorkflowTask workflowTask1 = createWorkflowTask(nameTaskDefinition1);\n        workflowTask1.setTaskDefinition(taskDefinition);\n\n        String nameTaskDefinition2 = \"task5\";\n        WorkflowTask workflowTask2 = createWorkflowTask(nameTaskDefinition2);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask1, workflowTask2));\n\n        when(metadataDAO.getTaskDef(nameTaskDefinition2)).thenReturn(taskDefinition);\n\n        metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        assertEquals(2, workflowDefinition.getTasks().size());\n        List<WorkflowTask> workflowTasks = workflowDefinition.getTasks();\n        assertNotNull(workflowTasks.get(0).getTaskDefinition());\n        assertNotNull(workflowTasks.get(1).getTaskDefinition());\n\n        verify(metadataDAO).getTaskDef(nameTaskDefinition2);\n        verifyNoMoreInteractions(metadataDAO);\n    }\n\n    @Test\n    public void testMetadataPopulationMissingDefinitions() {\n        String nameTaskDefinition1 = \"task4\";\n        WorkflowTask workflowTask1 = createWorkflowTask(nameTaskDefinition1);\n\n        String nameTaskDefinition2 = \"task5\";\n        WorkflowTask workflowTask2 = createWorkflowTask(nameTaskDefinition2);\n\n        TaskDef taskDefinition = createTaskDefinition(nameTaskDefinition1);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask1, workflowTask2));\n\n        when(metadataDAO.getTaskDef(nameTaskDefinition1)).thenReturn(taskDefinition);\n        when(metadataDAO.getTaskDef(nameTaskDefinition2)).thenReturn(null);\n\n        try {\n            metadataMapperService.populateTaskDefinitions(workflowDefinition);\n        } catch (NotFoundException nfe) {\n            fail(\"Missing TaskDefinitions are not defaulted\");\n        }\n    }\n\n    @Test\n    public void testVersionPopulationForSubworkflowTaskIfVersionIsNotAvailable() {\n        String nameTaskDefinition = \"taskSubworkflow6\";\n        String workflowDefinitionName = \"subworkflow\";\n        int version = 3;\n\n        WorkflowDef subWorkflowDefinition = createWorkflowDefinition(\"workflowDefinitionName\");\n        subWorkflowDefinition.setVersion(version);\n\n        WorkflowTask workflowTask = createWorkflowTask(nameTaskDefinition);\n        workflowTask.setWorkflowTaskType(TaskType.SUB_WORKFLOW);\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(workflowDefinitionName);\n        workflowTask.setSubWorkflowParam(subWorkflowParams);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask));\n\n        when(metadataDAO.getLatestWorkflowDef(workflowDefinitionName))\n                .thenReturn(Optional.of(subWorkflowDefinition));\n\n        metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        assertEquals(1, workflowDefinition.getTasks().size());\n        List<WorkflowTask> workflowTasks = workflowDefinition.getTasks();\n        SubWorkflowParams params = workflowTasks.get(0).getSubWorkflowParam();\n\n        assertEquals(workflowDefinitionName, params.getName());\n        assertEquals(version, params.getVersion().intValue());\n\n        verify(metadataDAO).getLatestWorkflowDef(workflowDefinitionName);\n        verify(metadataDAO).getTaskDef(nameTaskDefinition);\n        verifyNoMoreInteractions(metadataDAO);\n    }\n\n    @Test\n    public void testNoVersionPopulationForSubworkflowTaskIfAvailable() {\n        String nameTaskDefinition = \"taskSubworkflow7\";\n        String workflowDefinitionName = \"subworkflow\";\n        Integer version = 2;\n\n        WorkflowTask workflowTask = createWorkflowTask(nameTaskDefinition);\n        workflowTask.setWorkflowTaskType(TaskType.SUB_WORKFLOW);\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(workflowDefinitionName);\n        subWorkflowParams.setVersion(version);\n        workflowTask.setSubWorkflowParam(subWorkflowParams);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask));\n\n        metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        assertEquals(1, workflowDefinition.getTasks().size());\n        List<WorkflowTask> workflowTasks = workflowDefinition.getTasks();\n        SubWorkflowParams params = workflowTasks.get(0).getSubWorkflowParam();\n\n        assertEquals(workflowDefinitionName, params.getName());\n        assertEquals(version, params.getVersion());\n\n        verify(metadataDAO).getTaskDef(nameTaskDefinition);\n        verifyNoMoreInteractions(metadataDAO);\n    }\n\n    @Test(expected = TerminateWorkflowException.class)\n    public void testExceptionWhenWorkflowDefinitionNotAvailable() {\n        String nameTaskDefinition = \"taskSubworkflow8\";\n        String workflowDefinitionName = \"subworkflow\";\n\n        WorkflowTask workflowTask = createWorkflowTask(nameTaskDefinition);\n        workflowTask.setWorkflowTaskType(TaskType.SUB_WORKFLOW);\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n        subWorkflowParams.setName(workflowDefinitionName);\n        workflowTask.setSubWorkflowParam(subWorkflowParams);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask));\n\n        when(metadataDAO.getLatestWorkflowDef(workflowDefinitionName)).thenReturn(Optional.empty());\n\n        metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        verify(metadataDAO).getLatestWorkflowDef(workflowDefinitionName);\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testLookupWorkflowDefinition() {\n        try {\n            String workflowName = \"test\";\n            when(metadataDAO.getWorkflowDef(workflowName, 0))\n                    .thenReturn(Optional.of(new WorkflowDef()));\n            Optional<WorkflowDef> optionalWorkflowDef =\n                    metadataMapperService.lookupWorkflowDefinition(workflowName, 0);\n            assertTrue(optionalWorkflowDef.isPresent());\n            metadataMapperService.lookupWorkflowDefinition(null, 0);\n        } catch (ConstraintViolationException ex) {\n            Assert.assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowIds list cannot be null.\"));\n        }\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testLookupLatestWorkflowDefinition() {\n        String workflowName = \"test\";\n        when(metadataDAO.getLatestWorkflowDef(workflowName))\n                .thenReturn(Optional.of(new WorkflowDef()));\n        Optional<WorkflowDef> optionalWorkflowDef =\n                metadataMapperService.lookupLatestWorkflowDefinition(workflowName);\n        assertTrue(optionalWorkflowDef.isPresent());\n\n        metadataMapperService.lookupLatestWorkflowDefinition(null);\n    }\n\n    @Test\n    public void testShouldNotPopulateTaskDefinition() {\n        WorkflowTask workflowTask = createWorkflowTask(\"\");\n        assertFalse(metadataMapperService.shouldPopulateTaskDefinition(workflowTask));\n    }\n\n    @Test\n    public void testShouldPopulateTaskDefinition() {\n        WorkflowTask workflowTask = createWorkflowTask(\"test\");\n        assertTrue(metadataMapperService.shouldPopulateTaskDefinition(workflowTask));\n    }\n\n    @Test\n    public void testMetadataPopulationOnSimpleTaskDefMissing() {\n        String nameTaskDefinition = \"task1\";\n        WorkflowTask workflowTask = createWorkflowTask(nameTaskDefinition);\n\n        when(metadataDAO.getTaskDef(nameTaskDefinition)).thenReturn(null);\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(\"testMetadataPopulation\");\n        workflowDefinition.setTasks(List.of(workflowTask));\n\n        metadataMapperService.populateTaskDefinitions(workflowDefinition);\n\n        assertEquals(1, workflowDefinition.getTasks().size());\n        WorkflowTask populatedWorkflowTask = workflowDefinition.getTasks().get(0);\n        assertNotNull(populatedWorkflowTask.getTaskDefinition());\n    }\n\n    private WorkflowDef createWorkflowDefinition(String name) {\n        WorkflowDef workflowDefinition = new WorkflowDef();\n        workflowDefinition.setName(name);\n        return workflowDefinition;\n    }\n\n    private WorkflowTask createWorkflowTask(String name) {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(name);\n        workflowTask.setType(TaskType.SIMPLE.name());\n        return workflowTask;\n    }\n\n    private TaskDef createTaskDefinition(String name) {\n        return new TaskDef(name);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/reconciliation/TestWorkflowRepairService.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.reconciliation;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.ArgumentCaptor;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.events.EventQueues;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.tasks.*;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.ExecutionDAO;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.*;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class TestWorkflowRepairService {\n\n    private QueueDAO queueDAO;\n    private ExecutionDAO executionDAO;\n    private ConductorProperties properties;\n    private WorkflowRepairService workflowRepairService;\n    private SystemTaskRegistry systemTaskRegistry;\n\n    @Before\n    public void setUp() {\n        executionDAO = mock(ExecutionDAO.class);\n        queueDAO = mock(QueueDAO.class);\n        properties = mock(ConductorProperties.class);\n        systemTaskRegistry = mock(SystemTaskRegistry.class);\n        workflowRepairService =\n                new WorkflowRepairService(executionDAO, queueDAO, properties, systemTaskRegistry);\n    }\n\n    @Test\n    public void verifyAndRepairSimpleTaskInScheduledState() {\n        TaskModel task = new TaskModel();\n        task.setTaskType(\"SIMPLE\");\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.setTaskId(\"abcd\");\n        task.setCallbackAfterSeconds(60);\n\n        when(queueDAO.containsMessage(anyString(), anyString())).thenReturn(false);\n\n        assertTrue(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that a new queue message is pushed for sync system tasks that fails queue contains\n        // check.\n        verify(queueDAO, times(1)).push(anyString(), anyString(), anyLong());\n    }\n\n    @Test\n    public void verifySimpleTaskInProgressState() {\n        TaskModel task = new TaskModel();\n        task.setTaskType(\"SIMPLE\");\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.setTaskId(\"abcd\");\n        task.setCallbackAfterSeconds(60);\n\n        when(queueDAO.containsMessage(anyString(), anyString())).thenReturn(false);\n\n        assertFalse(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that queue message is never pushed for simple task in IN_PROGRESS state\n        verify(queueDAO, never()).containsMessage(anyString(), anyString());\n        verify(queueDAO, never()).push(anyString(), anyString(), anyLong());\n    }\n\n    @Test\n    public void verifyAndRepairSystemTask() {\n        String taskType = \"TEST_SYS_TASK\";\n        TaskModel task = new TaskModel();\n        task.setTaskType(taskType);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.setTaskId(\"abcd\");\n        task.setCallbackAfterSeconds(60);\n\n        when(systemTaskRegistry.isSystemTask(\"TEST_SYS_TASK\")).thenReturn(true);\n        when(systemTaskRegistry.get(taskType))\n                .thenReturn(\n                        new WorkflowSystemTask(\"TEST_SYS_TASK\") {\n                            @Override\n                            public boolean isAsync() {\n                                return true;\n                            }\n\n                            @Override\n                            public boolean isAsyncComplete(TaskModel task) {\n                                return false;\n                            }\n\n                            @Override\n                            public void start(\n                                    WorkflowModel workflow,\n                                    TaskModel task,\n                                    WorkflowExecutor executor) {\n                                super.start(workflow, task, executor);\n                            }\n                        });\n\n        when(queueDAO.containsMessage(anyString(), anyString())).thenReturn(false);\n\n        assertTrue(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that a new queue message is pushed for tasks that fails queue contains check.\n        verify(queueDAO, times(1)).push(anyString(), anyString(), anyLong());\n\n        // Verify a system task in IN_PROGRESS state can be recovered.\n        reset(queueDAO);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        assertTrue(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that a new queue message is pushed for async System task in IN_PROGRESS state that\n        // fails queue contains check.\n        verify(queueDAO, times(1)).push(anyString(), anyString(), anyLong());\n    }\n\n    @Test\n    public void assertSyncSystemTasksAreNotCheckedAgainstQueue() {\n        // Return a Switch task object to init WorkflowSystemTask registry.\n        when(systemTaskRegistry.get(TASK_TYPE_DECISION)).thenReturn(new Decision());\n        when(systemTaskRegistry.isSystemTask(TASK_TYPE_DECISION)).thenReturn(true);\n        when(systemTaskRegistry.get(TASK_TYPE_SWITCH)).thenReturn(new Switch());\n        when(systemTaskRegistry.isSystemTask(TASK_TYPE_SWITCH)).thenReturn(true);\n\n        TaskModel task = new TaskModel();\n        task.setTaskType(TASK_TYPE_DECISION);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n\n        assertFalse(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that queue contains is never checked for sync system tasks\n        verify(queueDAO, never()).containsMessage(anyString(), anyString());\n        // Verify that queue message is never pushed for sync system tasks\n        verify(queueDAO, never()).push(anyString(), anyString(), anyLong());\n\n        task = new TaskModel();\n        task.setTaskType(TASK_TYPE_SWITCH);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n\n        assertFalse(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that queue contains is never checked for sync system tasks\n        verify(queueDAO, never()).containsMessage(anyString(), anyString());\n        // Verify that queue message is never pushed for sync system tasks\n        verify(queueDAO, never()).push(anyString(), anyString(), anyLong());\n    }\n\n    @Test\n    public void assertAsyncCompleteInProgressSystemTasksAreNotCheckedAgainstQueue() {\n        TaskModel task = new TaskModel();\n        task.setTaskType(TASK_TYPE_EVENT);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.setTaskId(\"abcd\");\n        task.setCallbackAfterSeconds(60);\n        task.setInputData(Map.of(\"asyncComplete\", true));\n\n        WorkflowSystemTask workflowSystemTask =\n                new Event(\n                        mock(EventQueues.class),\n                        mock(ParametersUtils.class),\n                        mock(ObjectMapper.class));\n        when(systemTaskRegistry.get(TASK_TYPE_EVENT)).thenReturn(workflowSystemTask);\n\n        assertTrue(workflowSystemTask.isAsyncComplete(task));\n\n        assertFalse(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that queue message is never pushed for async complete system tasks\n        verify(queueDAO, never()).containsMessage(anyString(), anyString());\n        verify(queueDAO, never()).push(anyString(), anyString(), anyLong());\n    }\n\n    @Test\n    public void assertAsyncCompleteScheduledSystemTasksAreCheckedAgainstQueue() {\n        TaskModel task = new TaskModel();\n        task.setTaskType(TASK_TYPE_SUB_WORKFLOW);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.setTaskId(\"abcd\");\n        task.setCallbackAfterSeconds(60);\n\n        WorkflowSystemTask workflowSystemTask =\n                new SubWorkflow(new ObjectMapper(), mock(StartWorkflowOperation.class));\n        when(systemTaskRegistry.get(TASK_TYPE_SUB_WORKFLOW)).thenReturn(workflowSystemTask);\n        when(queueDAO.containsMessage(anyString(), anyString())).thenReturn(false);\n\n        assertTrue(workflowSystemTask.isAsyncComplete(task));\n\n        assertTrue(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that queue message is never pushed for async complete system tasks\n        verify(queueDAO, times(1)).containsMessage(anyString(), anyString());\n        verify(queueDAO, times(1)).push(anyString(), anyString(), anyLong());\n    }\n\n    @Test\n    public void verifyAndRepairParentWorkflow() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowId(\"abcd\");\n        workflow.setParentWorkflowId(\"parentWorkflowId\");\n\n        when(properties.getWorkflowOffsetTimeout()).thenReturn(Duration.ofSeconds(10));\n        when(executionDAO.getWorkflow(\"abcd\", true)).thenReturn(workflow);\n        when(queueDAO.containsMessage(anyString(), anyString())).thenReturn(false);\n\n        workflowRepairService.verifyAndRepairWorkflowTasks(\"abcd\");\n        verify(queueDAO, times(1)).containsMessage(anyString(), anyString());\n        verify(queueDAO, times(1)).push(anyString(), anyString(), anyLong());\n    }\n\n    @Test\n    public void assertInProgressSubWorkflowSystemTasksAreCheckedAndRepaired() {\n        String subWorkflowId = \"subWorkflowId\";\n        String taskId = \"taskId\";\n\n        TaskModel task = new TaskModel();\n        task.setTaskType(TASK_TYPE_SUB_WORKFLOW);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        task.setTaskId(taskId);\n        task.setCallbackAfterSeconds(60);\n        task.setSubWorkflowId(subWorkflowId);\n        Map<String, Object> outputMap = new HashMap<>();\n        outputMap.put(\"subWorkflowId\", subWorkflowId);\n        task.setOutputData(outputMap);\n\n        WorkflowModel subWorkflow = new WorkflowModel();\n        subWorkflow.setWorkflowId(subWorkflowId);\n        subWorkflow.setStatus(WorkflowModel.Status.TERMINATED);\n        subWorkflow.setOutput(Map.of(\"k1\", \"v1\", \"k2\", \"v2\"));\n\n        when(executionDAO.getWorkflow(subWorkflowId, false)).thenReturn(subWorkflow);\n\n        assertTrue(workflowRepairService.verifyAndRepairTask(task));\n        // Verify that queue message is never pushed for async complete system tasks\n        verify(queueDAO, never()).containsMessage(anyString(), anyString());\n        verify(queueDAO, never()).push(anyString(), anyString(), anyLong());\n        // Verify\n        ArgumentCaptor<TaskModel> argumentCaptor = ArgumentCaptor.forClass(TaskModel.class);\n        verify(executionDAO, times(1)).updateTask(argumentCaptor.capture());\n        assertEquals(taskId, argumentCaptor.getValue().getTaskId());\n        assertEquals(subWorkflowId, argumentCaptor.getValue().getSubWorkflowId());\n        assertEquals(TaskModel.Status.CANCELED, argumentCaptor.getValue().getStatus());\n        assertNotNull(argumentCaptor.getValue().getOutputData());\n        assertEquals(subWorkflowId, argumentCaptor.getValue().getOutputData().get(\"subWorkflowId\"));\n        assertEquals(\"v1\", argumentCaptor.getValue().getOutputData().get(\"k1\"));\n        assertEquals(\"v2\", argumentCaptor.getValue().getOutputData().get(\"k2\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/reconciliation/TestWorkflowSweeper.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.reconciliation;\n\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Optional;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.TaskModel.Status;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static com.netflix.conductor.core.utils.Utils.DECIDER_QUEUE;\n\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class TestWorkflowSweeper {\n\n    private ConductorProperties properties;\n    private WorkflowExecutor workflowExecutor;\n    private WorkflowRepairService workflowRepairService;\n    private QueueDAO queueDAO;\n    private ExecutionDAOFacade executionDAOFacade;\n    private WorkflowSweeper workflowSweeper;\n\n    private int defaultPostPoneOffSetSeconds = 1800;\n\n    @Before\n    public void setUp() {\n        properties = mock(ConductorProperties.class);\n        workflowExecutor = mock(WorkflowExecutor.class);\n        queueDAO = mock(QueueDAO.class);\n        workflowRepairService = mock(WorkflowRepairService.class);\n        executionDAOFacade = mock(ExecutionDAOFacade.class);\n        workflowSweeper =\n                new WorkflowSweeper(\n                        workflowExecutor,\n                        Optional.of(workflowRepairService),\n                        properties,\n                        queueDAO,\n                        executionDAOFacade);\n    }\n\n    @Test\n    public void testPostponeDurationForHumanTaskType() {\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_HUMAN);\n        taskModel.setStatus(Status.IN_PROGRESS);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE,\n                        workflowModel.getWorkflowId(),\n                        defaultPostPoneOffSetSeconds * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForWaitTaskType() {\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_WAIT);\n        taskModel.setStatus(Status.IN_PROGRESS);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE,\n                        workflowModel.getWorkflowId(),\n                        defaultPostPoneOffSetSeconds * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForWaitTaskTypeWithLongWaitTime() {\n        long waitTimeout = 65845;\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_WAIT);\n        taskModel.setStatus(Status.IN_PROGRESS);\n        taskModel.setWaitTimeout(System.currentTimeMillis() + waitTimeout);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE, workflowModel.getWorkflowId(), (waitTimeout / 1000) * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForWaitTaskTypeWithLessOneSecondWaitTime() {\n        long waitTimeout = 180;\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_WAIT);\n        taskModel.setStatus(Status.IN_PROGRESS);\n        taskModel.setWaitTimeout(System.currentTimeMillis() + waitTimeout);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE, workflowModel.getWorkflowId(), (waitTimeout / 1000) * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForWaitTaskTypeWithZeroWaitTime() {\n        long waitTimeout = 0;\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_WAIT);\n        taskModel.setStatus(Status.IN_PROGRESS);\n        taskModel.setWaitTimeout(System.currentTimeMillis() + waitTimeout);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE, workflowModel.getWorkflowId(), (waitTimeout / 1000) * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInProgress() {\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_SIMPLE);\n        taskModel.setStatus(Status.IN_PROGRESS);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE,\n                        workflowModel.getWorkflowId(),\n                        defaultPostPoneOffSetSeconds * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInProgressWithResponseTimeoutSet() {\n        long responseTimeout = 200;\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_SIMPLE);\n        taskModel.setStatus(Status.IN_PROGRESS);\n        taskModel.setResponseTimeoutSeconds(responseTimeout);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE, workflowModel.getWorkflowId(), (responseTimeout + 1) * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInScheduled() {\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_SIMPLE);\n        taskModel.setStatus(Status.SCHEDULED);\n        taskModel.setReferenceTaskName(\"task1\");\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE,\n                        workflowModel.getWorkflowId(),\n                        defaultPostPoneOffSetSeconds * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInScheduledWithWorkflowTimeoutSet() {\n        long workflowTimeout = 1800;\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setTimeoutSeconds(workflowTimeout);\n        workflowModel.setWorkflowDefinition(workflowDef);\n        TaskModel taskModel = new TaskModel();\n        taskModel.setTaskId(\"task1\");\n        taskModel.setTaskType(TaskType.TASK_TYPE_SIMPLE);\n        taskModel.setStatus(Status.SCHEDULED);\n        workflowModel.setTasks(List.of(taskModel));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE, workflowModel.getWorkflowId(), (workflowTimeout + 1) * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInScheduledWithWorkflowTimeoutSetAndNoPollTimeout() {\n        long workflowTimeout = 1800;\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setTimeoutSeconds(workflowTimeout);\n        workflowModel.setWorkflowDefinition(workflowDef);\n        TaskDef taskDef = new TaskDef();\n        TaskModel taskModel = mock(TaskModel.class);\n        workflowModel.setTasks(List.of(taskModel));\n        when(taskModel.getTaskDefinition()).thenReturn(Optional.of(taskDef));\n        when(taskModel.getStatus()).thenReturn(Status.SCHEDULED);\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE, workflowModel.getWorkflowId(), (workflowTimeout + 1) * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInScheduledWithNoWorkflowTimeoutSetAndNoPollTimeout() {\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        TaskDef taskDef = new TaskDef();\n        TaskModel taskModel = mock(TaskModel.class);\n        workflowModel.setTasks(List.of(taskModel));\n        when(taskModel.getTaskDefinition()).thenReturn(Optional.of(taskDef));\n        when(taskModel.getStatus()).thenReturn(Status.SCHEDULED);\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE,\n                        workflowModel.getWorkflowId(),\n                        defaultPostPoneOffSetSeconds * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInScheduledWithNoPollTimeoutSet() {\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskDef taskDef = new TaskDef();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowModel.setWorkflowDefinition(workflowDef);\n        TaskModel taskModel = mock(TaskModel.class);\n        workflowModel.setTasks(List.of(taskModel));\n        when(taskModel.getStatus()).thenReturn(Status.SCHEDULED);\n        when(taskModel.getTaskDefinition()).thenReturn(Optional.of(taskDef));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE,\n                        workflowModel.getWorkflowId(),\n                        defaultPostPoneOffSetSeconds * 1000);\n    }\n\n    @Test\n    public void testPostponeDurationForTaskInScheduledWithPollTimeoutSet() {\n        int pollTimeout = 200;\n        WorkflowModel workflowModel = new WorkflowModel();\n        workflowModel.setWorkflowId(\"1\");\n        TaskDef taskDef = new TaskDef();\n        taskDef.setPollTimeoutSeconds(pollTimeout);\n        TaskModel taskModel = mock(TaskModel.class);\n        ;\n        workflowModel.setTasks(List.of(taskModel));\n        when(taskModel.getStatus()).thenReturn(Status.SCHEDULED);\n        when(taskModel.getTaskDefinition()).thenReturn(Optional.of(taskDef));\n        when(properties.getWorkflowOffsetTimeout())\n                .thenReturn(Duration.ofSeconds(defaultPostPoneOffSetSeconds));\n        workflowSweeper.unack(workflowModel, defaultPostPoneOffSetSeconds);\n        verify(queueDAO)\n                .setUnackTimeout(\n                        DECIDER_QUEUE, workflowModel.getWorkflowId(), (pollTimeout + 1) * 1000);\n    }\n\n    @Test\n    public void testWorkflowOffsetJitter() {\n        long offset = 45;\n        for (int i = 0; i < 10; i++) {\n            long offsetWithJitter = workflowSweeper.workflowOffsetWithJitter(offset);\n            assertTrue(offsetWithJitter >= 30);\n            assertTrue(offsetWithJitter <= 60);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/storage/DummyPayloadStorageTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.storage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\n\nimport org.apache.commons.io.IOUtils;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class DummyPayloadStorageTest {\n\n    private DummyPayloadStorage dummyPayloadStorage;\n\n    private static final String TEST_STORAGE_PATH = \"test-storage\";\n\n    private ExternalStorageLocation location;\n\n    private ObjectMapper objectMapper;\n\n    public static final String MOCK_PAYLOAD = \"{\\n\" + \"\\\"output\\\": \\\"TEST_OUTPUT\\\",\\n\" + \"}\\n\";\n\n    @Before\n    public void setup() {\n        dummyPayloadStorage = new DummyPayloadStorage();\n        objectMapper = new ObjectMapper();\n        location =\n                dummyPayloadStorage.getLocation(\n                        ExternalPayloadStorage.Operation.WRITE,\n                        PayloadType.TASK_OUTPUT,\n                        TEST_STORAGE_PATH);\n        try {\n            byte[] payloadBytes = MOCK_PAYLOAD.getBytes(\"UTF-8\");\n            dummyPayloadStorage.upload(\n                    location.getPath(),\n                    new ByteArrayInputStream(payloadBytes),\n                    payloadBytes.length);\n        } catch (UnsupportedEncodingException unsupportedEncodingException) {\n        }\n    }\n\n    @Test\n    public void testGetLocationNotNull() {\n        assertNotNull(location);\n    }\n\n    @Test\n    public void testDownloadForValidPath() {\n        try (InputStream inputStream = dummyPayloadStorage.download(location.getPath())) {\n            Map<String, Object> payload =\n                    objectMapper.readValue(\n                            IOUtils.toString(inputStream, StandardCharsets.UTF_8), Map.class);\n            assertTrue(payload.containsKey(\"output\"));\n            assertEquals(payload.get(\"output\"), \"TEST_OUTPUT\");\n        } catch (Exception e) {\n            assertTrue(e instanceof IOException);\n        }\n    }\n\n    @Test\n    public void testDownloadForInvalidPath() {\n        InputStream inputStream = dummyPayloadStorage.download(\"testPath\");\n        assertNull(inputStream);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/sync/local/LocalOnlyLockTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.sync.local;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.After;\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@Ignore\n// Test always times out in CI environment\npublic class LocalOnlyLockTest {\n\n    // Lock can be global since it uses global cache internally\n    private final LocalOnlyLock localOnlyLock = new LocalOnlyLock();\n\n    @After\n    public void tearDown() {\n        // Clean caches between tests as they are shared globally\n        localOnlyLock.cache().invalidateAll();\n        localOnlyLock.scheduledFutures().values().forEach(f -> f.cancel(false));\n        localOnlyLock.scheduledFutures().clear();\n    }\n\n    @Test\n    public void testLockUnlock() {\n        final boolean a = localOnlyLock.acquireLock(\"a\", 100, 10000, TimeUnit.MILLISECONDS);\n        assertTrue(a);\n        assertEquals(localOnlyLock.cache().estimatedSize(), 1);\n        assertEquals(localOnlyLock.cache().get(\"a\").isLocked(), true);\n        assertEquals(localOnlyLock.scheduledFutures().size(), 1);\n        localOnlyLock.releaseLock(\"a\");\n        assertEquals(localOnlyLock.scheduledFutures().size(), 0);\n        assertEquals(localOnlyLock.cache().get(\"a\").isLocked(), false);\n        localOnlyLock.deleteLock(\"a\");\n        assertEquals(localOnlyLock.cache().estimatedSize(), 0);\n    }\n\n    @Test(timeout = 10 * 10_000)\n    public void testLockTimeout() throws InterruptedException, ExecutionException {\n        final ExecutorService executor = Executors.newFixedThreadPool(1);\n        executor.submit(\n                        () -> {\n                            localOnlyLock.acquireLock(\"c\", 100, 1000, TimeUnit.MILLISECONDS);\n                        })\n                .get();\n        assertTrue(localOnlyLock.acquireLock(\"d\", 100, 1000, TimeUnit.MILLISECONDS));\n        assertFalse(localOnlyLock.acquireLock(\"c\", 100, 1000, TimeUnit.MILLISECONDS));\n        assertEquals(localOnlyLock.scheduledFutures().size(), 2);\n        executor.submit(\n                        () -> {\n                            localOnlyLock.releaseLock(\"c\");\n                        })\n                .get();\n        localOnlyLock.releaseLock(\"d\");\n        assertEquals(localOnlyLock.scheduledFutures().size(), 0);\n    }\n\n    @Test(timeout = 10 * 10_000)\n    public void testReleaseFromAnotherThread() throws InterruptedException, ExecutionException {\n        final ExecutorService executor = Executors.newFixedThreadPool(1);\n        executor.submit(\n                        () -> {\n                            localOnlyLock.acquireLock(\"c\", 100, 10000, TimeUnit.MILLISECONDS);\n                        })\n                .get();\n        try {\n            localOnlyLock.releaseLock(\"c\");\n        } catch (IllegalMonitorStateException e) {\n            // expected\n            localOnlyLock.deleteLock(\"c\");\n            return;\n        } finally {\n            executor.submit(\n                            () -> {\n                                localOnlyLock.releaseLock(\"c\");\n                            })\n                    .get();\n        }\n\n        fail();\n    }\n\n    @Test(timeout = 10 * 10_000)\n    public void testLockLeaseWithRelease() throws Exception {\n        localOnlyLock.acquireLock(\"b\", 1000, 1000, TimeUnit.MILLISECONDS);\n        localOnlyLock.releaseLock(\"b\");\n\n        // Wait for lease to run out and also call release\n        Thread.sleep(2000);\n\n        localOnlyLock.acquireLock(\"b\");\n        assertEquals(true, localOnlyLock.cache().get(\"b\").isLocked());\n        localOnlyLock.releaseLock(\"b\");\n    }\n\n    @Test\n    public void testRelease() {\n        localOnlyLock.releaseLock(\"x54as4d2;23'4\");\n        localOnlyLock.releaseLock(\"x54as4d2;23'4\");\n        assertEquals(false, localOnlyLock.cache().get(\"x54as4d2;23'4\").isLocked());\n    }\n\n    @Test(timeout = 10 * 10_000)\n    public void testLockLeaseTime() throws InterruptedException {\n        for (int i = 0; i < 10; i++) {\n            final Thread thread =\n                    new Thread(\n                            () -> {\n                                localOnlyLock.acquireLock(\"a\", 1000, 100, TimeUnit.MILLISECONDS);\n                            });\n            thread.start();\n            thread.join();\n        }\n        localOnlyLock.acquireLock(\"a\");\n        assertTrue(localOnlyLock.cache().get(\"a\").isLocked());\n        localOnlyLock.releaseLock(\"a\");\n        localOnlyLock.deleteLock(\"a\");\n    }\n\n    @Test\n    public void testLockConfiguration() {\n        new ApplicationContextRunner()\n                .withPropertyValues(\"conductor.workflow-execution-lock.type=local_only\")\n                .withUserConfiguration(LocalOnlyLockConfiguration.class)\n                .run(\n                        context -> {\n                            LocalOnlyLock lock = context.getBean(LocalOnlyLock.class);\n                            assertNotNull(lock);\n                        });\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/utils/ExternalPayloadStorageUtilsTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.util.unit.DataSize;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.TerminateWorkflowException;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.model.TaskModel.Status.FAILED_WITH_TERMINAL_ERROR;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class ExternalPayloadStorageUtilsTest {\n\n    private ExternalPayloadStorage externalPayloadStorage;\n    private ExternalStorageLocation location;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    // Subject\n    private ExternalPayloadStorageUtils externalPayloadStorageUtils;\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Before\n    public void setup() {\n        externalPayloadStorage = mock(ExternalPayloadStorage.class);\n        ConductorProperties properties = mock(ConductorProperties.class);\n        location = new ExternalStorageLocation();\n        location.setPath(\"some/test/path\");\n\n        when(properties.getWorkflowInputPayloadSizeThreshold())\n                .thenReturn(DataSize.ofKilobytes(10L));\n        when(properties.getMaxWorkflowInputPayloadSizeThreshold())\n                .thenReturn(DataSize.ofKilobytes(10240L));\n        when(properties.getWorkflowOutputPayloadSizeThreshold())\n                .thenReturn(DataSize.ofKilobytes(10L));\n        when(properties.getMaxWorkflowOutputPayloadSizeThreshold())\n                .thenReturn(DataSize.ofKilobytes(10240L));\n        when(properties.getTaskInputPayloadSizeThreshold()).thenReturn(DataSize.ofKilobytes(10L));\n        when(properties.getMaxTaskInputPayloadSizeThreshold())\n                .thenReturn(DataSize.ofKilobytes(10240L));\n        when(properties.getTaskOutputPayloadSizeThreshold()).thenReturn(DataSize.ofKilobytes(10L));\n        when(properties.getMaxTaskOutputPayloadSizeThreshold())\n                .thenReturn(DataSize.ofKilobytes(10240L));\n\n        externalPayloadStorageUtils =\n                new ExternalPayloadStorageUtils(externalPayloadStorage, properties, objectMapper);\n    }\n\n    @Test\n    public void testDownloadPayload() throws IOException {\n        String path = \"test/payload\";\n\n        Map<String, Object> payload = new HashMap<>();\n        payload.put(\"key1\", \"value1\");\n        payload.put(\"key2\", 200);\n        byte[] payloadBytes = objectMapper.writeValueAsString(payload).getBytes();\n        when(externalPayloadStorage.download(path))\n                .thenReturn(new ByteArrayInputStream(payloadBytes));\n\n        Map<String, Object> result = externalPayloadStorageUtils.downloadPayload(path);\n        assertNotNull(result);\n        assertEquals(payload, result);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testUploadTaskPayload() throws IOException {\n        AtomicInteger uploadCount = new AtomicInteger(0);\n\n        InputStream stream =\n                com.netflix.conductor.core.utils.ExternalPayloadStorageUtilsTest.class\n                        .getResourceAsStream(\"/payload.json\");\n        Map<String, Object> payload = objectMapper.readValue(stream, Map.class);\n\n        byte[] payloadBytes = objectMapper.writeValueAsString(payload).getBytes();\n        when(externalPayloadStorage.getLocation(\n                        ExternalPayloadStorage.Operation.WRITE,\n                        ExternalPayloadStorage.PayloadType.TASK_INPUT,\n                        \"\",\n                        payloadBytes))\n                .thenReturn(location);\n        doAnswer(\n                        invocation -> {\n                            uploadCount.incrementAndGet();\n                            return null;\n                        })\n                .when(externalPayloadStorage)\n                .upload(anyString(), any(), anyLong());\n\n        TaskModel task = new TaskModel();\n        task.setInputData(payload);\n        externalPayloadStorageUtils.verifyAndUpload(\n                task, ExternalPayloadStorage.PayloadType.TASK_INPUT);\n        assertTrue(StringUtils.isNotEmpty(task.getExternalInputPayloadStoragePath()));\n        assertFalse(task.getInputData().isEmpty());\n        assertEquals(1, uploadCount.get());\n        assertNotNull(task.getExternalInputPayloadStoragePath());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testUploadWorkflowPayload() throws IOException {\n        AtomicInteger uploadCount = new AtomicInteger(0);\n\n        InputStream stream =\n                com.netflix.conductor.core.utils.ExternalPayloadStorageUtilsTest.class\n                        .getResourceAsStream(\"/payload.json\");\n        Map<String, Object> payload = objectMapper.readValue(stream, Map.class);\n\n        byte[] payloadBytes = objectMapper.writeValueAsString(payload).getBytes();\n        when(externalPayloadStorage.getLocation(\n                        ExternalPayloadStorage.Operation.WRITE,\n                        ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT,\n                        \"\",\n                        payloadBytes))\n                .thenReturn(location);\n        doAnswer(\n                        invocation -> {\n                            uploadCount.incrementAndGet();\n                            return null;\n                        })\n                .when(externalPayloadStorage)\n                .upload(anyString(), any(), anyLong());\n\n        WorkflowModel workflow = new WorkflowModel();\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"name\");\n        def.setVersion(1);\n        workflow.setOutput(payload);\n        workflow.setWorkflowDefinition(def);\n        externalPayloadStorageUtils.verifyAndUpload(\n                workflow, ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT);\n        assertTrue(StringUtils.isNotEmpty(workflow.getExternalOutputPayloadStoragePath()));\n        assertFalse(workflow.getOutput().isEmpty());\n        assertEquals(1, uploadCount.get());\n        assertNotNull(workflow.getExternalOutputPayloadStoragePath());\n    }\n\n    @Test\n    public void testUploadHelper() {\n        AtomicInteger uploadCount = new AtomicInteger(0);\n        String path = \"some/test/path.json\";\n        ExternalStorageLocation location = new ExternalStorageLocation();\n        location.setPath(path);\n\n        when(externalPayloadStorage.getLocation(any(), any(), any(), any())).thenReturn(location);\n        doAnswer(\n                        invocation -> {\n                            uploadCount.incrementAndGet();\n                            return null;\n                        })\n                .when(externalPayloadStorage)\n                .upload(anyString(), any(), anyLong());\n\n        assertEquals(\n                path,\n                externalPayloadStorageUtils.uploadHelper(\n                        new byte[] {}, 10L, ExternalPayloadStorage.PayloadType.TASK_OUTPUT));\n        assertEquals(1, uploadCount.get());\n    }\n\n    @Test\n    public void testFailTaskWithInputPayload() {\n        TaskModel task = new TaskModel();\n        task.setInputData(new HashMap<>());\n\n        externalPayloadStorageUtils.failTask(\n                task, ExternalPayloadStorage.PayloadType.TASK_INPUT, \"error\");\n        assertNotNull(task);\n        assertTrue(task.getInputData().isEmpty());\n        assertEquals(FAILED_WITH_TERMINAL_ERROR, task.getStatus());\n    }\n\n    @Test\n    public void testFailTaskWithOutputPayload() {\n        TaskModel task = new TaskModel();\n        task.setOutputData(new HashMap<>());\n\n        externalPayloadStorageUtils.failTask(\n                task, ExternalPayloadStorage.PayloadType.TASK_OUTPUT, \"error\");\n        assertNotNull(task);\n        assertTrue(task.getOutputData().isEmpty());\n        assertEquals(FAILED_WITH_TERMINAL_ERROR, task.getStatus());\n    }\n\n    @Test\n    public void testFailWorkflowWithInputPayload() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setInput(new HashMap<>());\n\n        expectedException.expect(TerminateWorkflowException.class);\n        externalPayloadStorageUtils.failWorkflow(\n                workflow, ExternalPayloadStorage.PayloadType.TASK_INPUT, \"error\");\n        assertNotNull(workflow);\n        assertTrue(workflow.getInput().isEmpty());\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getStatus());\n    }\n\n    @Test\n    public void testFailWorkflowWithOutputPayload() {\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setOutput(new HashMap<>());\n\n        expectedException.expect(TerminateWorkflowException.class);\n        externalPayloadStorageUtils.failWorkflow(\n                workflow, ExternalPayloadStorage.PayloadType.TASK_OUTPUT, \"error\");\n        assertNotNull(workflow);\n        assertTrue(workflow.getOutput().isEmpty());\n        assertEquals(WorkflowModel.Status.FAILED, workflow.getStatus());\n    }\n\n    @Test\n    public void testShouldUpload() {\n        Map<String, Object> payload = new HashMap<>();\n        payload.put(\"key1\", \"value1\");\n        payload.put(\"key2\", \"value2\");\n\n        TaskModel task = new TaskModel();\n        task.setInputData(payload);\n        task.setOutputData(payload);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setInput(payload);\n        workflow.setOutput(payload);\n\n        assertTrue(\n                externalPayloadStorageUtils.shouldUpload(\n                        task, ExternalPayloadStorage.PayloadType.TASK_INPUT));\n        assertTrue(\n                externalPayloadStorageUtils.shouldUpload(\n                        task, ExternalPayloadStorage.PayloadType.TASK_OUTPUT));\n        assertTrue(\n                externalPayloadStorageUtils.shouldUpload(\n                        task, ExternalPayloadStorage.PayloadType.WORKFLOW_INPUT));\n        assertTrue(\n                externalPayloadStorageUtils.shouldUpload(\n                        task, ExternalPayloadStorage.PayloadType.WORKFLOW_OUTPUT));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/utils/JsonUtilsTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class JsonUtilsTest {\n\n    private JsonUtils jsonUtils;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void setup() {\n        jsonUtils = new JsonUtils(objectMapper);\n    }\n\n    @Test\n    public void testArray() {\n        List<Object> list = new LinkedList<>();\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"externalId\", \"[{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}]\");\n        map.put(\"name\", \"conductor\");\n        map.put(\"version\", 2);\n        list.add(map);\n\n        //noinspection unchecked\n        map = (Map<String, Object>) list.get(0);\n        assertTrue(map.get(\"externalId\") instanceof String);\n\n        int before = list.size();\n        jsonUtils.expand(list);\n        assertEquals(before, list.size());\n\n        //noinspection unchecked\n        map = (Map<String, Object>) list.get(0);\n        assertTrue(map.get(\"externalId\") instanceof ArrayList);\n    }\n\n    @Test\n    public void testMap() {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"externalId\", \"{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}\");\n        map.put(\"name\", \"conductor\");\n        map.put(\"version\", 2);\n\n        assertTrue(map.get(\"externalId\") instanceof String);\n\n        jsonUtils.expand(map);\n\n        assertTrue(map.get(\"externalId\") instanceof LinkedHashMap);\n    }\n\n    @Test\n    public void testMultiLevelMap() {\n        Map<String, Object> parentMap = new HashMap<>();\n        parentMap.put(\"requestId\", \"abcde\");\n        parentMap.put(\"status\", \"PROCESSED\");\n\n        Map<String, Object> childMap = new HashMap<>();\n        childMap.put(\"path\", \"test/path\");\n        childMap.put(\"type\", \"VIDEO\");\n\n        Map<String, Object> grandChildMap = new HashMap<>();\n        grandChildMap.put(\"duration\", \"370\");\n        grandChildMap.put(\"passed\", \"true\");\n\n        childMap.put(\"metadata\", grandChildMap);\n        parentMap.put(\"asset\", childMap);\n\n        Object jsonObject = jsonUtils.expand(parentMap);\n        assertNotNull(jsonObject);\n    }\n\n    // This test verifies that the types of the elements in the input are maintained upon expanding\n    // the JSON object\n    @Test\n    public void testTypes() throws Exception {\n        String map =\n                \"{\\\"requestId\\\":\\\"1375128656908832001\\\",\\\"workflowId\\\":\\\"fc147e1d-5408-4d41-b066-53cb2e551d0e\\\",\"\n                        + \"\\\"inner\\\":{\\\"num\\\":42,\\\"status\\\":\\\"READY\\\"}}\";\n        jsonUtils.expand(map);\n\n        Object jsonObject = jsonUtils.expand(map);\n        assertNotNull(jsonObject);\n        assertTrue(jsonObject instanceof LinkedHashMap);\n        assertTrue(((LinkedHashMap<?, ?>) jsonObject).get(\"requestId\") instanceof String);\n        assertTrue(((LinkedHashMap<?, ?>) jsonObject).get(\"workflowId\") instanceof String);\n        assertTrue(((LinkedHashMap<?, ?>) jsonObject).get(\"inner\") instanceof LinkedHashMap);\n        assertTrue(\n                ((LinkedHashMap<?, ?>) ((LinkedHashMap<?, ?>) jsonObject).get(\"inner\")).get(\"num\")\n                        instanceof Integer);\n        assertTrue(\n                ((LinkedHashMap<?, ?>) ((LinkedHashMap<?, ?>) jsonObject).get(\"inner\"))\n                                .get(\"status\")\n                        instanceof String);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/utils/ParametersUtilsTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\n@SuppressWarnings(\"rawtypes\")\npublic class ParametersUtilsTest {\n\n    private ParametersUtils parametersUtils;\n    private JsonUtils jsonUtils;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void setup() {\n        parametersUtils = new ParametersUtils(objectMapper);\n        jsonUtils = new JsonUtils(objectMapper);\n    }\n\n    @Test\n    public void testReplace() throws Exception {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"name\", \"conductor\");\n        map.put(\"version\", 2);\n        map.put(\"externalId\", \"{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}\");\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"${$.externalId}\");\n        input.put(\"k4\", \"${name}\");\n        input.put(\"k5\", \"${version}\");\n\n        Object jsonObj = objectMapper.readValue(objectMapper.writeValueAsString(map), Object.class);\n\n        Map<String, Object> replaced = parametersUtils.replace(input, jsonObj);\n        assertNotNull(replaced);\n\n        assertEquals(\"{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}\", replaced.get(\"k1\"));\n        assertEquals(\"conductor\", replaced.get(\"k4\"));\n        assertEquals(2, replaced.get(\"k5\"));\n    }\n\n    @Test\n    public void testReplaceWithArrayExpand() {\n        List<Object> list = new LinkedList<>();\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"externalId\", \"[{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}]\");\n        map.put(\"name\", \"conductor\");\n        map.put(\"version\", 2);\n        list.add(map);\n        jsonUtils.expand(list);\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"${$..externalId}\");\n        input.put(\"k2\", \"${$[0].externalId[0].taskRefName}\");\n        input.put(\"k3\", \"${__json_externalId.taskRefName}\");\n        input.put(\"k4\", \"${$[0].name}\");\n        input.put(\"k5\", \"${$[0].version}\");\n\n        Map<String, Object> replaced = parametersUtils.replace(input, list);\n        assertNotNull(replaced);\n        assertEquals(replaced.get(\"k2\"), \"t001\");\n        assertNull(replaced.get(\"k3\"));\n        assertEquals(replaced.get(\"k4\"), \"conductor\");\n        assertEquals(replaced.get(\"k5\"), 2);\n    }\n\n    @Test\n    public void testReplaceWithMapExpand() {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"externalId\", \"{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}\");\n        map.put(\"name\", \"conductor\");\n        map.put(\"version\", 2);\n        jsonUtils.expand(map);\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"${$.externalId}\");\n        input.put(\"k2\", \"${externalId.taskRefName}\");\n        input.put(\"k4\", \"${name}\");\n        input.put(\"k5\", \"${version}\");\n\n        Map<String, Object> replaced = parametersUtils.replace(input, map);\n        assertNotNull(replaced);\n        assertEquals(\"t001\", replaced.get(\"k2\"));\n        assertNull(replaced.get(\"k3\"));\n        assertEquals(\"conductor\", replaced.get(\"k4\"));\n        assertEquals(2, replaced.get(\"k5\"));\n    }\n\n    @Test\n    public void testReplaceConcurrent() throws ExecutionException, InterruptedException {\n        ExecutorService executorService = Executors.newFixedThreadPool(2);\n\n        AtomicReference<String> generatedId = new AtomicReference<>(\"test-0\");\n        Map<String, Object> input = new HashMap<>();\n        Map<String, Object> payload = new HashMap<>();\n        payload.put(\"event\", \"conductor:TEST_EVENT\");\n        payload.put(\"someId\", generatedId);\n        input.put(\"payload\", payload);\n        input.put(\"name\", \"conductor\");\n        input.put(\"version\", 2);\n\n        Map<String, Object> inputParams = new HashMap<>();\n        inputParams.put(\"k1\", \"${payload.someId}\");\n        inputParams.put(\"k2\", \"${name}\");\n\n        CompletableFuture.runAsync(\n                        () -> {\n                            for (int i = 0; i < 10000; i++) {\n                                generatedId.set(\"test-\" + i);\n                                payload.put(\"someId\", generatedId.get());\n                                Object jsonObj = null;\n                                try {\n                                    jsonObj =\n                                            objectMapper.readValue(\n                                                    objectMapper.writeValueAsString(input),\n                                                    Object.class);\n                                } catch (JsonProcessingException e) {\n                                    e.printStackTrace();\n                                    return;\n                                }\n                                Map<String, Object> replaced =\n                                        parametersUtils.replace(inputParams, jsonObj);\n                                assertNotNull(replaced);\n                                assertEquals(generatedId.get(), replaced.get(\"k1\"));\n                                assertEquals(\"conductor\", replaced.get(\"k2\"));\n                                assertNull(replaced.get(\"k3\"));\n                            }\n                        },\n                        executorService)\n                .get();\n\n        executorService.shutdown();\n    }\n\n    // Tests ParametersUtils with Map and List input values, and verifies input map is not mutated\n    // by ParametersUtils.\n    @Test\n    public void testReplaceInputWithMapAndList() throws Exception {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"name\", \"conductor\");\n        map.put(\"version\", 2);\n        map.put(\"externalId\", \"{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}\");\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"${$.externalId}\");\n        input.put(\"k2\", \"${name}\");\n        input.put(\"k3\", \"${version}\");\n        input.put(\"k4\", \"${}\");\n        input.put(\"k5\", \"${    }\");\n\n        Map<String, String> mapValue = new HashMap<>();\n        mapValue.put(\"name\", \"${name}\");\n        mapValue.put(\"version\", \"${version}\");\n        input.put(\"map\", mapValue);\n\n        List<String> listValue = new ArrayList<>();\n        listValue.add(\"${name}\");\n        listValue.add(\"${version}\");\n        input.put(\"list\", listValue);\n\n        Object jsonObj = objectMapper.readValue(objectMapper.writeValueAsString(map), Object.class);\n\n        Map<String, Object> replaced = parametersUtils.replace(input, jsonObj);\n        assertNotNull(replaced);\n\n        // Verify that values are replaced correctly.\n        assertEquals(\"{\\\"taskRefName\\\":\\\"t001\\\",\\\"workflowId\\\":\\\"w002\\\"}\", replaced.get(\"k1\"));\n        assertEquals(\"conductor\", replaced.get(\"k2\"));\n        assertEquals(2, replaced.get(\"k3\"));\n        assertEquals(\"\", replaced.get(\"k4\"));\n        assertEquals(\"\", replaced.get(\"k5\"));\n\n        Map replacedMap = (Map) replaced.get(\"map\");\n        assertEquals(\"conductor\", replacedMap.get(\"name\"));\n        assertEquals(2, replacedMap.get(\"version\"));\n\n        List replacedList = (List) replaced.get(\"list\");\n        assertEquals(2, replacedList.size());\n        assertEquals(\"conductor\", replacedList.get(0));\n        assertEquals(2, replacedList.get(1));\n\n        // Verify that input map is not mutated\n        assertEquals(\"${$.externalId}\", input.get(\"k1\"));\n        assertEquals(\"${name}\", input.get(\"k2\"));\n        assertEquals(\"${version}\", input.get(\"k3\"));\n\n        Map inputMap = (Map) input.get(\"map\");\n        assertEquals(\"${name}\", inputMap.get(\"name\"));\n        assertEquals(\"${version}\", inputMap.get(\"version\"));\n\n        List inputList = (List) input.get(\"list\");\n        assertEquals(2, inputList.size());\n        assertEquals(\"${name}\", inputList.get(0));\n        assertEquals(\"${version}\", inputList.get(1));\n    }\n\n    @Test\n    public void testNestedPathExpressions() throws Exception {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"name\", \"conductor\");\n        map.put(\"index\", 1);\n        map.put(\"mapValue\", \"a\");\n        map.put(\"recordIds\", List.of(1, 2, 3));\n        map.put(\"map\", Map.of(\"a\", List.of(1, 2, 3), \"b\", List.of(2, 4, 5), \"c\", List.of(3, 7, 8)));\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"${recordIds[${index}]}\");\n        input.put(\"k2\", \"${map.${mapValue}[${index}]}\");\n        input.put(\"k3\", \"${map.b[${map.${mapValue}[${index}]}]}\");\n\n        Object jsonObj = objectMapper.readValue(objectMapper.writeValueAsString(map), Object.class);\n\n        Map<String, Object> replaced = parametersUtils.replace(input, jsonObj);\n        assertNotNull(replaced);\n\n        assertEquals(2, replaced.get(\"k1\"));\n        assertEquals(2, replaced.get(\"k2\"));\n        assertEquals(5, replaced.get(\"k3\"));\n    }\n\n    @Test\n    public void testReplaceWithLineTerminators() throws Exception {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"name\", \"conductor\");\n        map.put(\"version\", 2);\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"k1\", \"Name: ${name}; Version: ${version};\");\n        input.put(\"k2\", \"Name: ${name};\\nVersion: ${version};\");\n        input.put(\"k3\", \"Name: ${name};\\rVersion: ${version};\");\n        input.put(\"k4\", \"Name: ${name};\\r\\nVersion: ${version};\");\n\n        Object jsonObj = objectMapper.readValue(objectMapper.writeValueAsString(map), Object.class);\n\n        Map<String, Object> replaced = parametersUtils.replace(input, jsonObj);\n\n        assertNotNull(replaced);\n\n        assertEquals(\"Name: conductor; Version: 2;\", replaced.get(\"k1\"));\n        assertEquals(\"Name: conductor;\\nVersion: 2;\", replaced.get(\"k2\"));\n        assertEquals(\"Name: conductor;\\rVersion: 2;\", replaced.get(\"k3\"));\n        assertEquals(\"Name: conductor;\\r\\nVersion: 2;\", replaced.get(\"k4\"));\n    }\n\n    @Test\n    public void testReplaceWithEscapedTags() throws Exception {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"someString\", \"conductor\");\n        map.put(\"someNumber\", 2);\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\n                \"k1\",\n                \"${$.someString} $${$.someNumber}${$.someNumber} ${$.someNumber}$${$.someString}\");\n        input.put(\"k2\", \"$${$.someString}afterText\");\n        input.put(\"k3\", \"beforeText$${$.someString}\");\n        input.put(\"k4\", \"$${$.someString} afterText\");\n        input.put(\"k5\", \"beforeText $${$.someString}\");\n\n        Map<String, String> mapValue = new HashMap<>();\n        mapValue.put(\"a\", \"${someString}\");\n        mapValue.put(\"b\", \"${someNumber}\");\n        mapValue.put(\"c\", \"$${someString} ${someNumber}\");\n        input.put(\"map\", mapValue);\n\n        List<String> listValue = new ArrayList<>();\n        listValue.add(\"${someString}\");\n        listValue.add(\"${someNumber}\");\n        listValue.add(\"${someString} $${someNumber}\");\n        input.put(\"list\", listValue);\n\n        Object jsonObj = objectMapper.readValue(objectMapper.writeValueAsString(map), Object.class);\n\n        Map<String, Object> replaced = parametersUtils.replace(input, jsonObj);\n        assertNotNull(replaced);\n\n        // Verify that values are replaced correctly.\n        assertEquals(\"conductor ${$.someNumber}2 2${$.someString}\", replaced.get(\"k1\"));\n        assertEquals(\"${$.someString}afterText\", replaced.get(\"k2\"));\n        assertEquals(\"beforeText${$.someString}\", replaced.get(\"k3\"));\n        assertEquals(\"${$.someString} afterText\", replaced.get(\"k4\"));\n        assertEquals(\"beforeText ${$.someString}\", replaced.get(\"k5\"));\n\n        Map replacedMap = (Map) replaced.get(\"map\");\n        assertEquals(\"conductor\", replacedMap.get(\"a\"));\n        assertEquals(2, replacedMap.get(\"b\"));\n        assertEquals(\"${someString} 2\", replacedMap.get(\"c\"));\n\n        List replacedList = (List) replaced.get(\"list\");\n        assertEquals(3, replacedList.size());\n        assertEquals(\"conductor\", replacedList.get(0));\n        assertEquals(2, replacedList.get(1));\n        assertEquals(\"conductor ${someNumber}\", replacedList.get(2));\n\n        // Verify that input map is not mutated\n        Map inputMap = (Map) input.get(\"map\");\n        assertEquals(\"${someString}\", inputMap.get(\"a\"));\n        assertEquals(\"${someNumber}\", inputMap.get(\"b\"));\n        assertEquals(\"$${someString} ${someNumber}\", inputMap.get(\"c\"));\n\n        // Verify that input list is not mutated\n        List inputList = (List) input.get(\"list\");\n        assertEquals(3, inputList.size());\n        assertEquals(\"${someString}\", inputList.get(0));\n        assertEquals(\"${someNumber}\", inputList.get(1));\n        assertEquals(\"${someString} $${someNumber}\", inputList.get(2));\n    }\n\n    @Test\n    public void getWorkflowInputHandlesNullInputTemplate() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        Map<String, Object> inputParams = Map.of(\"key\", \"value\");\n        Map<String, Object> workflowInput =\n                parametersUtils.getWorkflowInput(workflowDef, inputParams);\n        assertEquals(\"value\", workflowInput.get(\"key\"));\n    }\n\n    @Test\n    public void getWorkflowInputFillsInTemplatedFields() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setInputTemplate(Map.of(\"other_key\", \"other_value\"));\n        Map<String, Object> inputParams = new HashMap<>(Map.of(\"key\", \"value\"));\n        Map<String, Object> workflowInput =\n                parametersUtils.getWorkflowInput(workflowDef, inputParams);\n        assertEquals(\"value\", workflowInput.get(\"key\"));\n        assertEquals(\"other_value\", workflowInput.get(\"other_key\"));\n    }\n\n    @Test\n    public void getWorkflowInputPreservesExistingFieldsIfPopulated() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        String keyName = \"key\";\n        workflowDef.setInputTemplate(Map.of(keyName, \"templated_value\"));\n        Map<String, Object> inputParams = new HashMap<>(Map.of(keyName, \"supplied_value\"));\n        Map<String, Object> workflowInput =\n                parametersUtils.getWorkflowInput(workflowDef, inputParams);\n        assertEquals(\"supplied_value\", workflowInput.get(keyName));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/utils/QueueUtilsTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class QueueUtilsTest {\n\n    @Test\n    public void queueNameWithTypeAndIsolationGroup() {\n        String queueNameGenerated = QueueUtils.getQueueName(\"tType\", null, \"isolationGroup\", null);\n        String queueNameGeneratedOnlyType = QueueUtils.getQueueName(\"tType\", null, null, null);\n        String queueNameGeneratedWithAllValues =\n                QueueUtils.getQueueName(\"tType\", \"domain\", \"iso\", \"eN\");\n\n        Assert.assertEquals(\"tType-isolationGroup\", queueNameGenerated);\n        Assert.assertEquals(\"tType\", queueNameGeneratedOnlyType);\n        Assert.assertEquals(\"domain:tType@eN-iso\", queueNameGeneratedWithAllValues);\n    }\n\n    @Test\n    public void notIsolatedIfSeparatorNotPresent() {\n        String notIsolatedQueue = \"notIsolated\";\n        Assert.assertFalse(QueueUtils.isIsolatedQueue(notIsolatedQueue));\n    }\n\n    @Test\n    public void testGetExecutionNameSpace() {\n        String executionNameSpace = QueueUtils.getExecutionNameSpace(\"domain:queueName@eN-iso\");\n        Assert.assertEquals(executionNameSpace, \"eN\");\n    }\n\n    @Test\n    public void testGetQueueExecutionNameSpaceEmpty() {\n        Assert.assertEquals(QueueUtils.getExecutionNameSpace(\"queueName\"), \"\");\n    }\n\n    @Test\n    public void testGetQueueExecutionNameSpaceWithIsolationGroup() {\n        Assert.assertEquals(\n                QueueUtils.getExecutionNameSpace(\"domain:test@executionNameSpace-isolated\"),\n                \"executionNameSpace\");\n    }\n\n    @Test\n    public void testGetQueueName() {\n        Assert.assertEquals(\n                \"domain:taskType@eN-isolated\",\n                QueueUtils.getQueueName(\"taskType\", \"domain\", \"isolated\", \"eN\"));\n    }\n\n    @Test\n    public void testGetTaskType() {\n        Assert.assertEquals(\"taskType\", QueueUtils.getTaskType(\"domain:taskType-isolated\"));\n    }\n\n    @Test\n    public void testGetTaskTypeWithoutDomain() {\n        Assert.assertEquals(\"taskType\", QueueUtils.getTaskType(\"taskType-isolated\"));\n    }\n\n    @Test\n    public void testGetTaskTypeWithoutDomainAndWithoutIsolationGroup() {\n        Assert.assertEquals(\"taskType\", QueueUtils.getTaskType(\"taskType\"));\n    }\n\n    @Test\n    public void testGetTaskTypeWithoutDomainAndWithExecutionNameSpace() {\n        Assert.assertEquals(\"taskType\", QueueUtils.getTaskType(\"taskType@eN\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/core/utils/SemaphoreUtilTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.core.utils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.stream.IntStream;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\n@SuppressWarnings(\"ToArrayCallWithZeroLengthArrayArgument\")\npublic class SemaphoreUtilTest {\n\n    @Test\n    public void testBlockAfterAvailablePermitsExhausted() throws Exception {\n        int threads = 5;\n        ExecutorService executorService = Executors.newFixedThreadPool(threads);\n        SemaphoreUtil semaphoreUtil = new SemaphoreUtil(threads);\n\n        List<CompletableFuture<Void>> futuresList = new ArrayList<>();\n        IntStream.range(0, threads)\n                .forEach(\n                        t ->\n                                futuresList.add(\n                                        CompletableFuture.runAsync(\n                                                () -> semaphoreUtil.acquireSlots(1),\n                                                executorService)));\n\n        CompletableFuture<Void> allFutures =\n                CompletableFuture.allOf(\n                        futuresList.toArray(new CompletableFuture[futuresList.size()]));\n\n        allFutures.get();\n\n        assertEquals(0, semaphoreUtil.availableSlots());\n        assertFalse(semaphoreUtil.acquireSlots(1));\n\n        executorService.shutdown();\n    }\n\n    @Test\n    public void testAllowsPollingWhenPermitBecomesAvailable() throws Exception {\n        int threads = 5;\n        ExecutorService executorService = Executors.newFixedThreadPool(threads);\n        SemaphoreUtil semaphoreUtil = new SemaphoreUtil(threads);\n\n        List<CompletableFuture<Void>> futuresList = new ArrayList<>();\n        IntStream.range(0, threads)\n                .forEach(\n                        t ->\n                                futuresList.add(\n                                        CompletableFuture.runAsync(\n                                                () -> semaphoreUtil.acquireSlots(1),\n                                                executorService)));\n\n        CompletableFuture<Void> allFutures =\n                CompletableFuture.allOf(\n                        futuresList.toArray(new CompletableFuture[futuresList.size()]));\n        allFutures.get();\n\n        assertEquals(0, semaphoreUtil.availableSlots());\n        semaphoreUtil.completeProcessing(1);\n\n        assertTrue(semaphoreUtil.availableSlots() > 0);\n        assertTrue(semaphoreUtil.acquireSlots(1));\n\n        executorService.shutdown();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/dao/ExecutionDAOTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.lang3.builder.EqualsBuilder;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport static org.junit.Assert.*;\n\npublic abstract class ExecutionDAOTest {\n\n    protected abstract ExecutionDAO getExecutionDAO();\n\n    protected ConcurrentExecutionLimitDAO getConcurrentExecutionLimitDAO() {\n        return (ConcurrentExecutionLimitDAO) getExecutionDAO();\n    }\n\n    @Rule public ExpectedException expectedException = ExpectedException.none();\n\n    @Test\n    public void testTaskExceedsLimit() {\n        TaskDef taskDefinition = new TaskDef();\n        taskDefinition.setName(\"task1\");\n        taskDefinition.setConcurrentExecLimit(1);\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"task1\");\n        workflowTask.setTaskDefinition(taskDefinition);\n        workflowTask.setTaskDefinition(taskDefinition);\n\n        List<TaskModel> tasks = new LinkedList<>();\n        for (int i = 0; i < 15; i++) {\n            TaskModel task = new TaskModel();\n            task.setScheduledTime(1L);\n            task.setSeq(i + 1);\n            task.setTaskId(\"t_\" + i);\n            task.setWorkflowInstanceId(\"workflow_\" + i);\n            task.setReferenceTaskName(\"task1\");\n            task.setTaskDefName(\"task1\");\n            tasks.add(task);\n            task.setStatus(TaskModel.Status.SCHEDULED);\n            task.setWorkflowTask(workflowTask);\n        }\n\n        getExecutionDAO().createTasks(tasks);\n        assertFalse(getConcurrentExecutionLimitDAO().exceedsLimit(tasks.get(0)));\n        tasks.get(0).setStatus(TaskModel.Status.IN_PROGRESS);\n        getExecutionDAO().updateTask(tasks.get(0));\n\n        for (TaskModel task : tasks) {\n            assertTrue(getConcurrentExecutionLimitDAO().exceedsLimit(task));\n        }\n    }\n\n    @Test\n    public void testCreateTaskException() {\n        TaskModel task = new TaskModel();\n        task.setScheduledTime(1L);\n        task.setSeq(1);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setTaskDefName(\"task1\");\n\n        expectedException.expect(IllegalArgumentException.class);\n        expectedException.expectMessage(\"Workflow instance id cannot be null\");\n        getExecutionDAO().createTasks(List.of(task));\n\n        task.setWorkflowInstanceId(UUID.randomUUID().toString());\n        expectedException.expect(IllegalArgumentException.class);\n        expectedException.expectMessage(\"Task reference name cannot be null\");\n        getExecutionDAO().createTasks(List.of(task));\n    }\n\n    @Test\n    public void testCreateTaskException2() {\n        TaskModel task = new TaskModel();\n        task.setScheduledTime(1L);\n        task.setSeq(1);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setTaskDefName(\"task1\");\n        task.setWorkflowInstanceId(UUID.randomUUID().toString());\n\n        expectedException.expect(IllegalArgumentException.class);\n        expectedException.expectMessage(\"Task reference name cannot be null\");\n        getExecutionDAO().createTasks(Collections.singletonList(task));\n    }\n\n    @Test\n    public void testTaskCreateDups() {\n        List<TaskModel> tasks = new LinkedList<>();\n        String workflowId = UUID.randomUUID().toString();\n\n        for (int i = 0; i < 3; i++) {\n            TaskModel task = new TaskModel();\n            task.setScheduledTime(1L);\n            task.setSeq(i + 1);\n            task.setTaskId(workflowId + \"_t\" + i);\n            task.setReferenceTaskName(\"t\" + i);\n            task.setRetryCount(0);\n            task.setWorkflowInstanceId(workflowId);\n            task.setTaskDefName(\"task\" + i);\n            task.setStatus(TaskModel.Status.IN_PROGRESS);\n            tasks.add(task);\n        }\n\n        // Let's insert a retried task\n        TaskModel task = new TaskModel();\n        task.setScheduledTime(1L);\n        task.setSeq(1);\n        task.setTaskId(workflowId + \"_t\" + 2);\n        task.setReferenceTaskName(\"t\" + 2);\n        task.setRetryCount(1);\n        task.setWorkflowInstanceId(workflowId);\n        task.setTaskDefName(\"task\" + 2);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        tasks.add(task);\n\n        // Duplicate task!\n        task = new TaskModel();\n        task.setScheduledTime(1L);\n        task.setSeq(1);\n        task.setTaskId(workflowId + \"_t\" + 1);\n        task.setReferenceTaskName(\"t\" + 1);\n        task.setRetryCount(0);\n        task.setWorkflowInstanceId(workflowId);\n        task.setTaskDefName(\"task\" + 1);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        tasks.add(task);\n\n        List<TaskModel> created = getExecutionDAO().createTasks(tasks);\n        assertEquals(tasks.size() - 1, created.size()); // 1 less\n\n        Set<String> srcIds =\n                tasks.stream()\n                        .map(t -> t.getReferenceTaskName() + \".\" + t.getRetryCount())\n                        .collect(Collectors.toSet());\n        Set<String> createdIds =\n                created.stream()\n                        .map(t -> t.getReferenceTaskName() + \".\" + t.getRetryCount())\n                        .collect(Collectors.toSet());\n\n        assertEquals(srcIds, createdIds);\n\n        List<TaskModel> pending = getExecutionDAO().getPendingTasksByWorkflow(\"task0\", workflowId);\n        assertNotNull(pending);\n        assertEquals(1, pending.size());\n        assertTrue(EqualsBuilder.reflectionEquals(tasks.get(0), pending.get(0)));\n\n        List<TaskModel> found = getExecutionDAO().getTasks(tasks.get(0).getTaskDefName(), null, 1);\n        assertNotNull(found);\n        assertEquals(1, found.size());\n        assertTrue(EqualsBuilder.reflectionEquals(tasks.get(0), found.get(0)));\n    }\n\n    @Test\n    public void testTaskOps() {\n        List<TaskModel> tasks = new LinkedList<>();\n        String workflowId = UUID.randomUUID().toString();\n\n        for (int i = 0; i < 3; i++) {\n            TaskModel task = new TaskModel();\n            task.setScheduledTime(1L);\n            task.setSeq(1);\n            task.setTaskId(workflowId + \"_t\" + i);\n            task.setReferenceTaskName(\"testTaskOps\" + i);\n            task.setRetryCount(0);\n            task.setWorkflowInstanceId(workflowId);\n            task.setTaskDefName(\"testTaskOps\" + i);\n            task.setStatus(TaskModel.Status.IN_PROGRESS);\n            tasks.add(task);\n        }\n\n        for (int i = 0; i < 3; i++) {\n            TaskModel task = new TaskModel();\n            task.setScheduledTime(1L);\n            task.setSeq(1);\n            task.setTaskId(\"x\" + workflowId + \"_t\" + i);\n            task.setReferenceTaskName(\"testTaskOps\" + i);\n            task.setRetryCount(0);\n            task.setWorkflowInstanceId(\"x\" + workflowId);\n            task.setTaskDefName(\"testTaskOps\" + i);\n            task.setStatus(TaskModel.Status.IN_PROGRESS);\n            getExecutionDAO().createTasks(Collections.singletonList(task));\n        }\n\n        List<TaskModel> created = getExecutionDAO().createTasks(tasks);\n        assertEquals(tasks.size(), created.size());\n\n        List<TaskModel> pending =\n                getExecutionDAO().getPendingTasksForTaskType(tasks.get(0).getTaskDefName());\n        assertNotNull(pending);\n        assertEquals(2, pending.size());\n        // Pending list can come in any order.  finding the one we are looking for and then\n        // comparing\n        TaskModel matching =\n                pending.stream()\n                        .filter(task -> task.getTaskId().equals(tasks.get(0).getTaskId()))\n                        .findAny()\n                        .get();\n        assertTrue(EqualsBuilder.reflectionEquals(matching, tasks.get(0)));\n\n        for (int i = 0; i < 3; i++) {\n            TaskModel found = getExecutionDAO().getTask(workflowId + \"_t\" + i);\n            assertNotNull(found);\n            found.addOutput(\"updated\", true);\n            found.setStatus(TaskModel.Status.COMPLETED);\n            getExecutionDAO().updateTask(found);\n        }\n\n        List<String> taskIds =\n                tasks.stream().map(TaskModel::getTaskId).collect(Collectors.toList());\n        List<TaskModel> found = getExecutionDAO().getTasks(taskIds);\n        assertEquals(taskIds.size(), found.size());\n        found.forEach(\n                task -> {\n                    assertTrue(task.getOutputData().containsKey(\"updated\"));\n                    assertEquals(true, task.getOutputData().get(\"updated\"));\n                    boolean removed = getExecutionDAO().removeTask(task.getTaskId());\n                    assertTrue(removed);\n                });\n\n        found = getExecutionDAO().getTasks(taskIds);\n        assertTrue(found.isEmpty());\n    }\n\n    @Test\n    public void testPending() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"pending_count_test\");\n\n        WorkflowModel workflow = createTestWorkflow();\n        workflow.setWorkflowDefinition(def);\n\n        List<String> workflowIds = generateWorkflows(workflow, 10);\n        long count = getExecutionDAO().getPendingWorkflowCount(def.getName());\n        assertEquals(10, count);\n\n        for (int i = 0; i < 10; i++) {\n            getExecutionDAO().removeFromPendingWorkflow(def.getName(), workflowIds.get(i));\n        }\n\n        count = getExecutionDAO().getPendingWorkflowCount(def.getName());\n        assertEquals(0, count);\n    }\n\n    @Test\n    public void complexExecutionTest() {\n        WorkflowModel workflow = createTestWorkflow();\n        int numTasks = workflow.getTasks().size();\n\n        String workflowId = getExecutionDAO().createWorkflow(workflow);\n        assertEquals(workflow.getWorkflowId(), workflowId);\n\n        List<TaskModel> created = getExecutionDAO().createTasks(workflow.getTasks());\n        assertEquals(workflow.getTasks().size(), created.size());\n\n        WorkflowModel workflowWithTasks =\n                getExecutionDAO().getWorkflow(workflow.getWorkflowId(), true);\n        assertEquals(workflowId, workflowWithTasks.getWorkflowId());\n        assertEquals(numTasks, workflowWithTasks.getTasks().size());\n\n        WorkflowModel found = getExecutionDAO().getWorkflow(workflowId, false);\n        assertTrue(found.getTasks().isEmpty());\n\n        workflow.getTasks().clear();\n        assertEquals(workflow, found);\n\n        workflow.getInput().put(\"updated\", true);\n        getExecutionDAO().updateWorkflow(workflow);\n        found = getExecutionDAO().getWorkflow(workflowId);\n        assertNotNull(found);\n        assertTrue(found.getInput().containsKey(\"updated\"));\n        assertEquals(true, found.getInput().get(\"updated\"));\n\n        List<String> running =\n                getExecutionDAO()\n                        .getRunningWorkflowIds(\n                                workflow.getWorkflowName(), workflow.getWorkflowVersion());\n        assertNotNull(running);\n        assertTrue(running.isEmpty());\n\n        workflow.setStatus(WorkflowModel.Status.RUNNING);\n        getExecutionDAO().updateWorkflow(workflow);\n\n        running =\n                getExecutionDAO()\n                        .getRunningWorkflowIds(\n                                workflow.getWorkflowName(), workflow.getWorkflowVersion());\n        assertNotNull(running);\n        assertEquals(1, running.size());\n        assertEquals(workflow.getWorkflowId(), running.get(0));\n\n        List<WorkflowModel> pending =\n                getExecutionDAO()\n                        .getPendingWorkflowsByType(\n                                workflow.getWorkflowName(), workflow.getWorkflowVersion());\n        assertNotNull(pending);\n        assertEquals(1, pending.size());\n        assertEquals(3, pending.get(0).getTasks().size());\n        pending.get(0).getTasks().clear();\n        assertEquals(workflow, pending.get(0));\n\n        workflow.setStatus(WorkflowModel.Status.COMPLETED);\n        getExecutionDAO().updateWorkflow(workflow);\n        running =\n                getExecutionDAO()\n                        .getRunningWorkflowIds(\n                                workflow.getWorkflowName(), workflow.getWorkflowVersion());\n        assertNotNull(running);\n        assertTrue(running.isEmpty());\n\n        List<WorkflowModel> bytime =\n                getExecutionDAO()\n                        .getWorkflowsByType(\n                                workflow.getWorkflowName(),\n                                System.currentTimeMillis(),\n                                System.currentTimeMillis() + 100);\n        assertNotNull(bytime);\n        assertTrue(bytime.isEmpty());\n\n        bytime =\n                getExecutionDAO()\n                        .getWorkflowsByType(\n                                workflow.getWorkflowName(),\n                                workflow.getCreateTime() - 10,\n                                workflow.getCreateTime() + 10);\n        assertNotNull(bytime);\n        assertEquals(1, bytime.size());\n    }\n\n    protected WorkflowModel createTestWorkflow() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"Junit Workflow\");\n        def.setVersion(3);\n        def.setSchemaVersion(2);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.setCorrelationId(\"correlationX\");\n        workflow.setCreatedBy(\"junit_tester\");\n        workflow.setEndTime(200L);\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"param1\", \"param1 value\");\n        input.put(\"param2\", 100);\n        workflow.setInput(input);\n\n        Map<String, Object> output = new HashMap<>();\n        output.put(\"ouput1\", \"output 1 value\");\n        output.put(\"op2\", 300);\n        workflow.setOutput(output);\n\n        workflow.setOwnerApp(\"workflow\");\n        workflow.setParentWorkflowId(\"parentWorkflowId\");\n        workflow.setParentWorkflowTaskId(\"parentWFTaskId\");\n        workflow.setReasonForIncompletion(\"missing recipe\");\n        workflow.setReRunFromWorkflowId(\"re-run from id1\");\n        workflow.setCreateTime(90L);\n        workflow.setStatus(WorkflowModel.Status.FAILED);\n        workflow.setWorkflowId(UUID.randomUUID().toString());\n\n        List<TaskModel> tasks = new LinkedList<>();\n\n        TaskModel task = new TaskModel();\n        task.setScheduledTime(1L);\n        task.setSeq(1);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setReferenceTaskName(\"t1\");\n        task.setWorkflowInstanceId(workflow.getWorkflowId());\n        task.setTaskDefName(\"task1\");\n\n        TaskModel task2 = new TaskModel();\n        task2.setScheduledTime(2L);\n        task2.setSeq(2);\n        task2.setTaskId(UUID.randomUUID().toString());\n        task2.setReferenceTaskName(\"t2\");\n        task2.setWorkflowInstanceId(workflow.getWorkflowId());\n        task2.setTaskDefName(\"task2\");\n\n        TaskModel task3 = new TaskModel();\n        task3.setScheduledTime(2L);\n        task3.setSeq(3);\n        task3.setTaskId(UUID.randomUUID().toString());\n        task3.setReferenceTaskName(\"t3\");\n        task3.setWorkflowInstanceId(workflow.getWorkflowId());\n        task3.setTaskDefName(\"task3\");\n\n        tasks.add(task);\n        tasks.add(task2);\n        tasks.add(task3);\n\n        workflow.setTasks(tasks);\n\n        workflow.setUpdatedBy(\"junit_tester\");\n        workflow.setUpdatedTime(800L);\n\n        return workflow;\n    }\n\n    protected List<String> generateWorkflows(WorkflowModel base, int count) {\n        List<String> workflowIds = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            String workflowId = UUID.randomUUID().toString();\n            base.setWorkflowId(workflowId);\n            base.setCorrelationId(\"corr001\");\n            base.setStatus(WorkflowModel.Status.RUNNING);\n            getExecutionDAO().createWorkflow(base);\n            workflowIds.add(workflowId);\n        }\n        return workflowIds;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/dao/PollDataDAOTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.dao;\n\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.PollData;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\npublic abstract class PollDataDAOTest {\n\n    protected abstract PollDataDAO getPollDataDAO();\n\n    @Test\n    public void testPollData() {\n        getPollDataDAO().updateLastPollData(\"taskDef\", null, \"workerId1\");\n        PollData pollData = getPollDataDAO().getPollData(\"taskDef\", null);\n        assertNotNull(pollData);\n        assertTrue(pollData.getLastPollTime() > 0);\n        assertEquals(pollData.getQueueName(), \"taskDef\");\n        assertNull(pollData.getDomain());\n        assertEquals(pollData.getWorkerId(), \"workerId1\");\n\n        getPollDataDAO().updateLastPollData(\"taskDef\", \"domain1\", \"workerId1\");\n        pollData = getPollDataDAO().getPollData(\"taskDef\", \"domain1\");\n        assertNotNull(pollData);\n        assertTrue(pollData.getLastPollTime() > 0);\n        assertEquals(pollData.getQueueName(), \"taskDef\");\n        assertEquals(pollData.getDomain(), \"domain1\");\n        assertEquals(pollData.getWorkerId(), \"workerId1\");\n\n        List<PollData> pData = getPollDataDAO().getPollData(\"taskDef\");\n        assertEquals(pData.size(), 2);\n\n        pollData = getPollDataDAO().getPollData(\"taskDef\", \"domain2\");\n        assertNull(pollData);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/metrics/WorkflowMonitorTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.metrics;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.service.MetadataService;\n\n@RunWith(SpringRunner.class)\npublic class WorkflowMonitorTest {\n\n    @Mock private MetadataService metadataService;\n    @Mock private QueueDAO queueDAO;\n    @Mock private ExecutionDAOFacade executionDAOFacade;\n\n    private WorkflowMonitor workflowMonitor;\n\n    @Before\n    public void beforeEach() {\n        workflowMonitor =\n                new WorkflowMonitor(metadataService, queueDAO, executionDAOFacade, 1000, Set.of());\n    }\n\n    private WorkflowDef makeDef(String name, int version, String ownerApp) {\n        WorkflowDef wd = new WorkflowDef();\n        wd.setName(name);\n        wd.setVersion(version);\n        wd.setOwnerApp(ownerApp);\n        return wd;\n    }\n\n    @Test\n    public void testPendingWorkflowDataMap() {\n        WorkflowDef test1_1 = makeDef(\"test1\", 1, null);\n        WorkflowDef test1_2 = makeDef(\"test1\", 2, \"name1\");\n\n        WorkflowDef test2_1 = makeDef(\"test2\", 1, \"first\");\n        WorkflowDef test2_2 = makeDef(\"test2\", 2, \"mid\");\n        WorkflowDef test2_3 = makeDef(\"test2\", 3, \"last\");\n\n        final Map<String, String> mapping =\n                workflowMonitor.getPendingWorkflowToOwnerAppMap(\n                        List.of(test1_1, test1_2, test2_1, test2_2, test2_3));\n\n        Assert.assertEquals(2, mapping.keySet().size());\n        Assert.assertTrue(mapping.containsKey(\"test1\"));\n        Assert.assertTrue(mapping.containsKey(\"test2\"));\n\n        Assert.assertEquals(\"name1\", mapping.get(\"test1\"));\n        Assert.assertEquals(\"last\", mapping.get(\"test2\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/service/EventServiceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolationException;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.core.events.EventQueues;\n\nimport static com.netflix.conductor.TestUtils.getConstraintViolationMessages;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assert.fail;\nimport static org.mockito.Mockito.mock;\n\n@SuppressWarnings(\"SpringJavaAutowiredMembersInspection\")\n@RunWith(SpringRunner.class)\n@EnableAutoConfiguration\npublic class EventServiceTest {\n\n    @TestConfiguration\n    static class TestEventConfiguration {\n\n        @Bean\n        public EventService eventService() {\n            MetadataService metadataService = mock(MetadataService.class);\n            EventQueues eventQueues = mock(EventQueues.class);\n            return new EventServiceImpl(metadataService, eventQueues);\n        }\n    }\n\n    @Autowired private EventService eventService;\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testAddEventHandler() {\n        try {\n            eventService.addEventHandler(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"EventHandler cannot be null.\"));\n            throw ex;\n        }\n        fail(\"eventService.addEventHandler did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateEventHandler() {\n        try {\n            eventService.updateEventHandler(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"EventHandler cannot be null.\"));\n            throw ex;\n        }\n        fail(\"eventService.updateEventHandler did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRemoveEventHandlerStatus() {\n        try {\n            eventService.removeEventHandlerStatus(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"EventHandler name cannot be null or empty.\"));\n            throw ex;\n        }\n        fail(\"eventService.removeEventHandlerStatus did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetEventHandlersForEvent() {\n        try {\n            eventService.getEventHandlersForEvent(null, false);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"Event cannot be null or empty.\"));\n            throw ex;\n        }\n        fail(\"eventService.getEventHandlersForEvent did not throw ConstraintViolationException !\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/service/ExecutionServiceTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.dal.ExecutionDAOFacade;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.dao.QueueDAO;\n\nimport static junit.framework.TestCase.assertEquals;\nimport static org.mockito.Mockito.when;\n\n@RunWith(SpringRunner.class)\npublic class ExecutionServiceTest {\n\n    @Mock private WorkflowExecutor workflowExecutor;\n    @Mock private ExecutionDAOFacade executionDAOFacade;\n    @Mock private QueueDAO queueDAO;\n    @Mock private ConductorProperties conductorProperties;\n    @Mock private ExternalPayloadStorage externalPayloadStorage;\n    @Mock private SystemTaskRegistry systemTaskRegistry;\n\n    private ExecutionService executionService;\n\n    private Workflow workflow1;\n    private Workflow workflow2;\n    private Task taskWorkflow1;\n    private Task taskWorkflow2;\n    private final List<String> sort = Collections.singletonList(\"Sort\");\n\n    @Before\n    public void setup() {\n        when(conductorProperties.getTaskExecutionPostponeDuration())\n                .thenReturn(Duration.ofSeconds(60));\n        executionService =\n                new ExecutionService(\n                        workflowExecutor,\n                        executionDAOFacade,\n                        queueDAO,\n                        conductorProperties,\n                        externalPayloadStorage,\n                        systemTaskRegistry);\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflow1 = new Workflow();\n        workflow1.setWorkflowId(\"wf1\");\n        workflow1.setWorkflowDefinition(workflowDef);\n        workflow2 = new Workflow();\n        workflow2.setWorkflowId(\"wf2\");\n        workflow2.setWorkflowDefinition(workflowDef);\n        taskWorkflow1 = new Task();\n        taskWorkflow1.setTaskId(\"task1\");\n        taskWorkflow1.setWorkflowInstanceId(\"wf1\");\n        taskWorkflow2 = new Task();\n        taskWorkflow2.setTaskId(\"task2\");\n        taskWorkflow2.setWorkflowInstanceId(\"wf2\");\n    }\n\n    @Test\n    public void workflowSearchTest() {\n        when(executionDAOFacade.searchWorkflowSummary(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        new WorkflowSummary(workflow1),\n                                        new WorkflowSummary(workflow2))));\n        when(executionDAOFacade.getWorkflow(workflow1.getWorkflowId(), false))\n                .thenReturn(workflow1);\n        when(executionDAOFacade.getWorkflow(workflow2.getWorkflowId(), false))\n                .thenReturn(workflow2);\n        SearchResult<WorkflowSummary> searchResult =\n                executionService.search(\"query\", \"*\", 0, 2, sort);\n        assertEquals(2, searchResult.getTotalHits());\n        assertEquals(2, searchResult.getResults().size());\n        assertEquals(workflow1.getWorkflowId(), searchResult.getResults().get(0).getWorkflowId());\n        assertEquals(workflow2.getWorkflowId(), searchResult.getResults().get(1).getWorkflowId());\n    }\n\n    @Test\n    public void workflowSearchV2Test() {\n        when(executionDAOFacade.searchWorkflows(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        workflow1.getWorkflowId(), workflow2.getWorkflowId())));\n        when(executionDAOFacade.getWorkflow(workflow1.getWorkflowId(), false))\n                .thenReturn(workflow1);\n        when(executionDAOFacade.getWorkflow(workflow2.getWorkflowId(), false))\n                .thenReturn(workflow2);\n        SearchResult<Workflow> searchResult = executionService.searchV2(\"query\", \"*\", 0, 2, sort);\n        assertEquals(2, searchResult.getTotalHits());\n        assertEquals(Arrays.asList(workflow1, workflow2), searchResult.getResults());\n    }\n\n    @Test\n    public void workflowSearchV2ExceptionTest() {\n        when(executionDAOFacade.searchWorkflows(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        workflow1.getWorkflowId(), workflow2.getWorkflowId())));\n        when(executionDAOFacade.getWorkflow(workflow1.getWorkflowId(), false))\n                .thenReturn(workflow1);\n        when(executionDAOFacade.getWorkflow(workflow2.getWorkflowId(), false))\n                .thenThrow(new RuntimeException());\n        SearchResult<Workflow> searchResult = executionService.searchV2(\"query\", \"*\", 0, 2, sort);\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(Collections.singletonList(workflow1), searchResult.getResults());\n    }\n\n    @Test\n    public void workflowSearchByTasksTest() {\n        when(executionDAOFacade.searchTaskSummary(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        new TaskSummary(taskWorkflow1),\n                                        new TaskSummary(taskWorkflow2))));\n        when(executionDAOFacade.getWorkflow(workflow1.getWorkflowId(), false))\n                .thenReturn(workflow1);\n        when(executionDAOFacade.getWorkflow(workflow2.getWorkflowId(), false))\n                .thenReturn(workflow2);\n        SearchResult<WorkflowSummary> searchResult =\n                executionService.searchWorkflowByTasks(\"query\", \"*\", 0, 2, sort);\n        assertEquals(2, searchResult.getTotalHits());\n        assertEquals(2, searchResult.getResults().size());\n        assertEquals(workflow1.getWorkflowId(), searchResult.getResults().get(0).getWorkflowId());\n        assertEquals(workflow2.getWorkflowId(), searchResult.getResults().get(1).getWorkflowId());\n    }\n\n    @Test\n    public void workflowSearchByTasksExceptionTest() {\n        when(executionDAOFacade.searchTaskSummary(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        new TaskSummary(taskWorkflow1),\n                                        new TaskSummary(taskWorkflow2))));\n        when(executionDAOFacade.getWorkflow(workflow1.getWorkflowId(), false))\n                .thenReturn(workflow1);\n        when(executionDAOFacade.getTask(workflow2.getWorkflowId()))\n                .thenThrow(new RuntimeException());\n        SearchResult<WorkflowSummary> searchResult =\n                executionService.searchWorkflowByTasks(\"query\", \"*\", 0, 2, sort);\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(1, searchResult.getResults().size());\n        assertEquals(workflow1.getWorkflowId(), searchResult.getResults().get(0).getWorkflowId());\n    }\n\n    @Test\n    public void workflowSearchByTasksV2Test() {\n        when(executionDAOFacade.searchTasks(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        taskWorkflow1.getTaskId(), taskWorkflow2.getTaskId())));\n        when(executionDAOFacade.getTask(taskWorkflow1.getTaskId())).thenReturn(taskWorkflow1);\n        when(executionDAOFacade.getTask(taskWorkflow2.getTaskId())).thenReturn(taskWorkflow2);\n        when(executionDAOFacade.getWorkflow(workflow1.getWorkflowId(), false))\n                .thenReturn(workflow1);\n        when(executionDAOFacade.getWorkflow(workflow2.getWorkflowId(), false))\n                .thenReturn(workflow2);\n        SearchResult<Workflow> searchResult =\n                executionService.searchWorkflowByTasksV2(\"query\", \"*\", 0, 2, sort);\n        assertEquals(2, searchResult.getTotalHits());\n        assertEquals(Arrays.asList(workflow1, workflow2), searchResult.getResults());\n    }\n\n    @Test\n    public void workflowSearchByTasksV2ExceptionTest() {\n        when(executionDAOFacade.searchTasks(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        taskWorkflow1.getTaskId(), taskWorkflow2.getTaskId())));\n        when(executionDAOFacade.getTask(taskWorkflow1.getTaskId())).thenReturn(taskWorkflow1);\n        when(executionDAOFacade.getTask(taskWorkflow2.getTaskId()))\n                .thenThrow(new RuntimeException());\n        when(executionDAOFacade.getWorkflow(workflow1.getWorkflowId(), false))\n                .thenReturn(workflow1);\n        SearchResult<Workflow> searchResult =\n                executionService.searchWorkflowByTasksV2(\"query\", \"*\", 0, 2, sort);\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(Collections.singletonList(workflow1), searchResult.getResults());\n    }\n\n    @Test\n    public void TaskSearchTest() {\n        List<TaskSummary> taskList =\n                Arrays.asList(new TaskSummary(taskWorkflow1), new TaskSummary(taskWorkflow2));\n        when(executionDAOFacade.searchTaskSummary(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(new SearchResult<>(2, taskList));\n        SearchResult<TaskSummary> searchResult =\n                executionService.getSearchTasks(\"query\", \"*\", 0, 2, \"Sort\");\n        assertEquals(2, searchResult.getTotalHits());\n        assertEquals(2, searchResult.getResults().size());\n        assertEquals(taskWorkflow1.getTaskId(), searchResult.getResults().get(0).getTaskId());\n        assertEquals(taskWorkflow2.getTaskId(), searchResult.getResults().get(1).getTaskId());\n    }\n\n    @Test\n    public void TaskSearchV2Test() {\n        when(executionDAOFacade.searchTasks(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        taskWorkflow1.getTaskId(), taskWorkflow2.getTaskId())));\n        when(executionDAOFacade.getTask(taskWorkflow1.getTaskId())).thenReturn(taskWorkflow1);\n        when(executionDAOFacade.getTask(taskWorkflow2.getTaskId())).thenReturn(taskWorkflow2);\n        SearchResult<Task> searchResult =\n                executionService.getSearchTasksV2(\"query\", \"*\", 0, 2, \"Sort\");\n        assertEquals(2, searchResult.getTotalHits());\n        assertEquals(Arrays.asList(taskWorkflow1, taskWorkflow2), searchResult.getResults());\n    }\n\n    @Test\n    public void TaskSearchV2ExceptionTest() {\n        when(executionDAOFacade.searchTasks(\"query\", \"*\", 0, 2, sort))\n                .thenReturn(\n                        new SearchResult<>(\n                                2,\n                                Arrays.asList(\n                                        taskWorkflow1.getTaskId(), taskWorkflow2.getTaskId())));\n        when(executionDAOFacade.getTask(taskWorkflow1.getTaskId())).thenReturn(taskWorkflow1);\n        when(executionDAOFacade.getTask(taskWorkflow2.getTaskId()))\n                .thenThrow(new RuntimeException());\n        SearchResult<Task> searchResult =\n                executionService.getSearchTasksV2(\"query\", \"*\", 0, 2, \"Sort\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(Collections.singletonList(taskWorkflow1), searchResult.getResults());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/service/MetadataServiceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.*;\n\nimport javax.validation.ConstraintViolationException;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDefSummary;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.model.BulkResponse;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.dao.EventHandlerDAO;\nimport com.netflix.conductor.dao.MetadataDAO;\n\nimport static com.netflix.conductor.TestUtils.getConstraintViolationMessages;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assert.fail;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@SuppressWarnings(\"SpringJavaAutowiredMembersInspection\")\n@RunWith(SpringRunner.class)\n@EnableAutoConfiguration\npublic class MetadataServiceTest {\n\n    @TestConfiguration\n    static class TestMetadataConfiguration {\n\n        @Bean\n        public MetadataDAO metadataDAO() {\n            return mock(MetadataDAO.class);\n        }\n\n        @Bean\n        public ConductorProperties properties() {\n            ConductorProperties properties = mock(ConductorProperties.class);\n            when(properties.isOwnerEmailMandatory()).thenReturn(true);\n            return properties;\n        }\n\n        @Bean\n        public MetadataService metadataService(\n                MetadataDAO metadataDAO, ConductorProperties properties) {\n            EventHandlerDAO eventHandlerDAO = mock(EventHandlerDAO.class);\n\n            when(metadataDAO.getAllWorkflowDefs()).thenReturn(mockWorkflowDefs());\n\n            return new MetadataServiceImpl(metadataDAO, eventHandlerDAO, properties);\n        }\n\n        private List<WorkflowDef> mockWorkflowDefs() {\n            // Returns list of workflowDefs in reverse version order.\n            List<WorkflowDef> retval = new ArrayList<>();\n            for (int i = 5; i > 0; i--) {\n                WorkflowDef def = new WorkflowDef();\n                def.setCreateTime(new Date().getTime());\n                def.setVersion(i);\n                def.setName(\"test_workflow_def\");\n                retval.add(def);\n            }\n            return retval;\n        }\n    }\n\n    @Autowired private MetadataDAO metadataDAO;\n\n    @Autowired private MetadataService metadataService;\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRegisterTaskDefNoName() {\n        TaskDef taskDef = new TaskDef();\n        try {\n            metadataService.registerTaskDef(Collections.singletonList(taskDef));\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskDef name cannot be null or empty\"));\n            assertTrue(messages.contains(\"ownerEmail cannot be empty\"));\n            throw ex;\n        }\n        fail(\"metadataService.registerTaskDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRegisterTaskDefNull() {\n        try {\n            metadataService.registerTaskDef(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskDefList cannot be empty or null\"));\n            throw ex;\n        }\n        fail(\"metadataService.registerTaskDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRegisterTaskDefNoResponseTimeout() {\n        try {\n            TaskDef taskDef = new TaskDef();\n            taskDef.setName(\"somename\");\n            taskDef.setOwnerEmail(\"sample@test.com\");\n            taskDef.setResponseTimeoutSeconds(0);\n            metadataService.registerTaskDef(Collections.singletonList(taskDef));\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(\n                    messages.contains(\n                            \"TaskDef responseTimeoutSeconds: 0 should be minimum 1 second\"));\n            throw ex;\n        }\n        fail(\"metadataService.registerTaskDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateTaskDefNameNull() {\n        try {\n            TaskDef taskDef = new TaskDef();\n            metadataService.updateTaskDef(taskDef);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskDef name cannot be null or empty\"));\n            assertTrue(messages.contains(\"ownerEmail cannot be empty\"));\n            throw ex;\n        }\n        fail(\"metadataService.updateTaskDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateTaskDefNull() {\n        try {\n            metadataService.updateTaskDef(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskDef cannot be null\"));\n            throw ex;\n        }\n        fail(\"metadataService.updateTaskDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = NotFoundException.class)\n    public void testUpdateTaskDefNotExisting() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test\");\n        taskDef.setOwnerEmail(\"sample@test.com\");\n        when(metadataDAO.getTaskDef(any())).thenReturn(null);\n        metadataService.updateTaskDef(taskDef);\n    }\n\n    @Test(expected = NotFoundException.class)\n    public void testUpdateTaskDefDaoException() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test\");\n        taskDef.setOwnerEmail(\"sample@test.com\");\n        when(metadataDAO.getTaskDef(any())).thenReturn(null);\n        metadataService.updateTaskDef(taskDef);\n    }\n\n    @Test\n    public void testRegisterTaskDef() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"somename\");\n        taskDef.setOwnerEmail(\"sample@test.com\");\n        taskDef.setResponseTimeoutSeconds(60 * 60);\n        metadataService.registerTaskDef(Collections.singletonList(taskDef));\n        verify(metadataDAO, times(1)).createTaskDef(any(TaskDef.class));\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateWorkflowDefNull() {\n        try {\n            List<WorkflowDef> workflowDefList = null;\n            metadataService.updateWorkflowDef(workflowDefList);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowDef list name cannot be null or empty\"));\n            throw ex;\n        }\n        fail(\"metadataService.updateWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateWorkflowDefEmptyList() {\n        try {\n            List<WorkflowDef> workflowDefList = new ArrayList<>();\n            metadataService.updateWorkflowDef(workflowDefList);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowDefList is empty\"));\n            throw ex;\n        }\n        fail(\"metadataService.updateWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateWorkflowDefWithNullWorkflowDef() {\n        try {\n            List<WorkflowDef> workflowDefList = new ArrayList<>();\n            workflowDefList.add(null);\n            metadataService.updateWorkflowDef(workflowDefList);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowDef cannot be null\"));\n            throw ex;\n        }\n        fail(\"metadataService.updateWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateWorkflowDefWithEmptyWorkflowDefName() {\n        try {\n            List<WorkflowDef> workflowDefList = new ArrayList<>();\n            WorkflowDef workflowDef = new WorkflowDef();\n            workflowDef.setName(null);\n            workflowDef.setOwnerEmail(null);\n            workflowDefList.add(workflowDef);\n            metadataService.updateWorkflowDef(workflowDefList);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(3, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowDef name cannot be null or empty\"));\n            assertTrue(messages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(messages.contains(\"ownerEmail cannot be empty\"));\n            throw ex;\n        }\n        fail(\"metadataService.updateWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test\n    public void testUpdateWorkflowDef() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"somename\");\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        List<WorkflowTask> tasks = new ArrayList<>();\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setTaskReferenceName(\"hello\");\n        workflowTask.setName(\"hello\");\n        tasks.add(workflowTask);\n        workflowDef.setTasks(tasks);\n        when(metadataDAO.getTaskDef(any())).thenReturn(new TaskDef());\n        metadataService.updateWorkflowDef(Collections.singletonList(workflowDef));\n        verify(metadataDAO, times(1)).updateWorkflowDef(workflowDef);\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateWorkflowDefWithCaseExpression() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"somename\");\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        List<WorkflowTask> tasks = new ArrayList<>();\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setTaskReferenceName(\"hello\");\n        workflowTask.setName(\"hello\");\n        workflowTask.setType(\"DECISION\");\n\n        WorkflowTask caseTask = new WorkflowTask();\n        caseTask.setTaskReferenceName(\"casetrue\");\n        caseTask.setName(\"casetrue\");\n\n        List<WorkflowTask> caseTaskList = new ArrayList<>();\n        caseTaskList.add(caseTask);\n\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap();\n        decisionCases.put(\"true\", caseTaskList);\n\n        workflowTask.setDecisionCases(decisionCases);\n        workflowTask.setCaseExpression(\"1 >0abcd\");\n        tasks.add(workflowTask);\n        workflowDef.setTasks(tasks);\n        when(metadataDAO.getTaskDef(any())).thenReturn(new TaskDef());\n        BulkResponse bulkResponse =\n                metadataService.updateWorkflowDef(Collections.singletonList(workflowDef));\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateWorkflowDefWithJavscriptEvaluator() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"somename\");\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        List<WorkflowTask> tasks = new ArrayList<>();\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setTaskReferenceName(\"hello\");\n        workflowTask.setName(\"hello\");\n        workflowTask.setType(\"SWITCH\");\n        workflowTask.setEvaluatorType(\"javascript\");\n        workflowTask.setExpression(\"1>abcd\");\n        WorkflowTask caseTask = new WorkflowTask();\n        caseTask.setTaskReferenceName(\"casetrue\");\n        caseTask.setName(\"casetrue\");\n\n        List<WorkflowTask> caseTaskList = new ArrayList<>();\n        caseTaskList.add(caseTask);\n\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap();\n        decisionCases.put(\"true\", caseTaskList);\n\n        workflowTask.setDecisionCases(decisionCases);\n        tasks.add(workflowTask);\n        workflowDef.setTasks(tasks);\n        when(metadataDAO.getTaskDef(any())).thenReturn(new TaskDef());\n        BulkResponse bulkResponse =\n                metadataService.updateWorkflowDef(Collections.singletonList(workflowDef));\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRegisterWorkflowDefNoName() {\n        try {\n            WorkflowDef workflowDef = new WorkflowDef();\n            metadataService.registerWorkflowDef(workflowDef);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(3, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowDef name cannot be null or empty\"));\n            assertTrue(messages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(messages.contains(\"ownerEmail cannot be empty\"));\n            throw ex;\n        }\n        fail(\"metadataService.registerWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testValidateWorkflowDefNoName() {\n        try {\n            WorkflowDef workflowDef = new WorkflowDef();\n            metadataService.validateWorkflowDef(workflowDef);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(3, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowDef name cannot be null or empty\"));\n            assertTrue(messages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(messages.contains(\"ownerEmail cannot be empty\"));\n            throw ex;\n        }\n        fail(\"metadataService.validateWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRegisterWorkflowDefInvalidName() {\n        try {\n            WorkflowDef workflowDef = new WorkflowDef();\n            workflowDef.setName(\"invalid:name\");\n            workflowDef.setOwnerEmail(\"inavlid-email\");\n            metadataService.registerWorkflowDef(workflowDef);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(3, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(\n                    messages.contains(\n                            \"Workflow name cannot contain the following set of characters: ':'\"));\n            assertTrue(messages.contains(\"ownerEmail should be valid email address\"));\n            throw ex;\n        }\n        fail(\"metadataService.registerWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testValidateWorkflowDefInvalidName() {\n        try {\n            WorkflowDef workflowDef = new WorkflowDef();\n            workflowDef.setName(\"invalid:name\");\n            workflowDef.setOwnerEmail(\"inavlid-email\");\n            metadataService.validateWorkflowDef(workflowDef);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(3, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(\n                    messages.contains(\n                            \"Workflow name cannot contain the following set of characters: ':'\"));\n            assertTrue(messages.contains(\"ownerEmail should be valid email address\"));\n            throw ex;\n        }\n        fail(\"metadataService.validateWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test\n    public void testRegisterWorkflowDef() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"somename\");\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        List<WorkflowTask> tasks = new ArrayList<>();\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setTaskReferenceName(\"hello\");\n        workflowTask.setName(\"hello\");\n        tasks.add(workflowTask);\n        workflowDef.setTasks(tasks);\n        when(metadataDAO.getTaskDef(any())).thenReturn(new TaskDef());\n        metadataService.registerWorkflowDef(workflowDef);\n        verify(metadataDAO, times(1)).createWorkflowDef(workflowDef);\n        assertEquals(2, workflowDef.getSchemaVersion());\n    }\n\n    @Test\n    public void testValidateWorkflowDef() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"somename\");\n        workflowDef.setSchemaVersion(2);\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        List<WorkflowTask> tasks = new ArrayList<>();\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setTaskReferenceName(\"hello\");\n        workflowTask.setName(\"hello\");\n        tasks.add(workflowTask);\n        workflowDef.setTasks(tasks);\n        when(metadataDAO.getTaskDef(any())).thenReturn(new TaskDef());\n        metadataService.validateWorkflowDef(workflowDef);\n        verify(metadataDAO, times(1)).createWorkflowDef(workflowDef);\n        assertEquals(2, workflowDef.getSchemaVersion());\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUnregisterWorkflowDefNoName() {\n        try {\n            metadataService.unregisterWorkflowDef(\"\", null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"Workflow name cannot be null or empty\"));\n            assertTrue(messages.contains(\"Version cannot be null\"));\n            throw ex;\n        }\n        fail(\"metadataService.unregisterWorkflowDef did not throw ConstraintViolationException !\");\n    }\n\n    @Test\n    public void testUnregisterWorkflowDef() {\n        metadataService.unregisterWorkflowDef(\"somename\", 111);\n        verify(metadataDAO, times(1)).removeWorkflowDef(\"somename\", 111);\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testValidateEventNull() {\n        try {\n            metadataService.addEventHandler(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"EventHandler cannot be null\"));\n            throw ex;\n        }\n        fail(\"metadataService.addEventHandler did not throw ConstraintViolationException !\");\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testValidateEventNoEvent() {\n        try {\n            EventHandler eventHandler = new EventHandler();\n            metadataService.addEventHandler(eventHandler);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(3, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"Missing event handler name\"));\n            assertTrue(messages.contains(\"Missing event location\"));\n            assertTrue(\n                    messages.contains(\"No actions specified. Please specify at-least one action\"));\n            throw ex;\n        }\n        fail(\"metadataService.addEventHandler did not throw ConstraintViolationException !\");\n    }\n\n    @Test\n    public void testWorkflowNamesAndVersions() {\n        Map<String, ? extends Iterable<WorkflowDefSummary>> namesAndVersions =\n                metadataService.getWorkflowNamesAndVersions();\n\n        Iterator<WorkflowDefSummary> versions =\n                namesAndVersions.get(\"test_workflow_def\").iterator();\n\n        for (int i = 1; i <= 5; i++) {\n            WorkflowDefSummary ver = versions.next();\n            assertEquals(i, ver.getVersion());\n            assertNotNull(ver.getCreateTime());\n            assertEquals(\"test_workflow_def\", ver.getName());\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/service/TaskServiceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolationException;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.dao.QueueDAO;\n\nimport static com.netflix.conductor.TestUtils.getConstraintViolationMessages;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@SuppressWarnings(\"SpringJavaAutowiredMembersInspection\")\n@RunWith(SpringRunner.class)\n@EnableAutoConfiguration\npublic class TaskServiceTest {\n\n    @TestConfiguration\n    static class TestTaskConfiguration {\n\n        @Bean\n        public ExecutionService executionService() {\n            return mock(ExecutionService.class);\n        }\n\n        @Bean\n        public TaskService taskService(ExecutionService executionService) {\n            QueueDAO queueDAO = mock(QueueDAO.class);\n            return new TaskServiceImpl(executionService, queueDAO);\n        }\n    }\n\n    @Autowired private TaskService taskService;\n\n    @Autowired private ExecutionService executionService;\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testPoll() {\n        try {\n            taskService.poll(null, null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskType cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testBatchPoll() {\n        try {\n            taskService.batchPoll(null, null, null, null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskType cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetTasks() {\n        try {\n            taskService.getTasks(null, null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskType cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetPendingTaskForWorkflow() {\n        try {\n            taskService.getPendingTaskForWorkflow(null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            assertTrue(messages.contains(\"TaskReferenceName cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateTask() {\n        try {\n            taskService.updateTask(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskResult cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testUpdateTaskInValid() {\n        try {\n            TaskResult taskResult = new TaskResult();\n            taskService.updateTask(taskResult);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"Workflow Id cannot be null or empty\"));\n            assertTrue(messages.contains(\"Task ID cannot be null or empty\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testAckTaskReceived() {\n        try {\n            taskService.ackTaskReceived(null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test\n    public void testAckTaskReceivedMissingWorkerId() {\n        String ack = taskService.ackTaskReceived(\"abc\", null);\n        assertNotNull(ack);\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testLog() {\n        try {\n            taskService.log(null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetTaskLogs() {\n        try {\n            taskService.getTaskLogs(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetTask() {\n        try {\n            taskService.getTask(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRemoveTaskFromQueue() {\n        try {\n            taskService.removeTaskFromQueue(null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskId cannot be null or empty.\"));\n            assertTrue(messages.contains(\"TaskType cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetPollData() {\n        try {\n            taskService.getPollData(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskType cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRequeuePendingTask() {\n        try {\n            taskService.requeuePendingTask(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"TaskType cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test\n    public void testSearch() {\n        SearchResult<TaskSummary> searchResult =\n                new SearchResult<>(2, List.of(mock(TaskSummary.class), mock(TaskSummary.class)));\n        when(executionService.getSearchTasks(\"query\", \"*\", 0, 2, \"Sort\")).thenReturn(searchResult);\n        assertEquals(searchResult, taskService.search(0, 2, \"Sort\", \"*\", \"query\"));\n    }\n\n    @Test\n    public void testSearchV2() {\n        SearchResult<Task> searchResult =\n                new SearchResult<>(2, List.of(mock(Task.class), mock(Task.class)));\n        when(executionService.getSearchTasksV2(\"query\", \"*\", 0, 2, \"Sort\"))\n                .thenReturn(searchResult);\n        assertEquals(searchResult, taskService.searchV2(0, 2, \"Sort\", \"*\", \"query\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/service/WorkflowBulkServiceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolationException;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\n\nimport static com.netflix.conductor.TestUtils.getConstraintViolationMessages;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\n@SuppressWarnings(\"SpringJavaAutowiredMembersInspection\")\n@RunWith(SpringRunner.class)\n@EnableAutoConfiguration\npublic class WorkflowBulkServiceTest {\n\n    @TestConfiguration\n    static class TestWorkflowBulkConfiguration {\n\n        @Bean\n        WorkflowExecutor workflowExecutor() {\n            return mock(WorkflowExecutor.class);\n        }\n\n        @Bean\n        public WorkflowBulkService workflowBulkService(WorkflowExecutor workflowExecutor) {\n            return new WorkflowBulkServiceImpl(workflowExecutor);\n        }\n    }\n\n    @Autowired private WorkflowExecutor workflowExecutor;\n\n    @Autowired private WorkflowBulkService workflowBulkService;\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testPauseWorkflowNull() {\n        try {\n            workflowBulkService.pauseWorkflow(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowIds list cannot be null.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testPauseWorkflowWithInvalidListSize() {\n        try {\n            List<String> list = new ArrayList<>(1001);\n            for (int i = 0; i < 1002; i++) {\n                list.add(\"test\");\n            }\n            workflowBulkService.pauseWorkflow(list);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(\n                    messages.contains(\n                            \"Cannot process more than 1000 workflows. Please use multiple requests.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testResumeWorkflowNull() {\n        try {\n            workflowBulkService.resumeWorkflow(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowIds list cannot be null.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRestartWorkflowNull() {\n        try {\n            workflowBulkService.restart(null, false);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowIds list cannot be null.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRetryWorkflowNull() {\n        try {\n            workflowBulkService.retry(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowIds list cannot be null.\"));\n            throw ex;\n        }\n    }\n\n    @Test\n    public void testRetryWorkflowSuccessful() {\n        // When\n        workflowBulkService.retry(Collections.singletonList(\"anyId\"));\n        // Then\n        verify(workflowExecutor).retry(\"anyId\", false);\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testTerminateNull() {\n        try {\n            workflowBulkService.terminate(null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowIds list cannot be null.\"));\n            throw ex;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/service/WorkflowServiceTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.service;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolationException;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.SkipTaskRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.operation.StartWorkflowOperation;\n\nimport static com.netflix.conductor.TestUtils.getConstraintViolationMessages;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@SuppressWarnings(\"SpringJavaAutowiredMembersInspection\")\n@RunWith(SpringRunner.class)\n@EnableAutoConfiguration\npublic class WorkflowServiceTest {\n\n    @TestConfiguration\n    static class TestWorkflowConfiguration {\n\n        @Bean\n        public WorkflowExecutor workflowExecutor() {\n            return mock(WorkflowExecutor.class);\n        }\n\n        @Bean\n        public StartWorkflowOperation startWorkflowOperation() {\n            return mock(StartWorkflowOperation.class);\n        }\n\n        @Bean\n        public ExecutionService executionService() {\n            return mock(ExecutionService.class);\n        }\n\n        @Bean\n        public MetadataService metadataService() {\n            return mock(MetadataServiceImpl.class);\n        }\n\n        @Bean\n        public WorkflowService workflowService(\n                WorkflowExecutor workflowExecutor,\n                ExecutionService executionService,\n                MetadataService metadataService,\n                StartWorkflowOperation startWorkflowOperation) {\n            return new WorkflowServiceImpl(\n                    workflowExecutor, executionService, metadataService, startWorkflowOperation);\n        }\n    }\n\n    @Autowired private WorkflowExecutor workflowExecutor;\n\n    @Autowired private ExecutionService executionService;\n\n    @Autowired private MetadataService metadataService;\n\n    @Autowired private WorkflowService workflowService;\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testStartWorkflowNull() {\n        try {\n            workflowService.startWorkflow(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"StartWorkflowRequest cannot be null\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetWorkflowsNoName() {\n        try {\n            workflowService.getWorkflows(\"\", \"c123\", true, true);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"Workflow name cannot be null or empty\"));\n            throw ex;\n        }\n    }\n\n    @Test\n    public void testGetWorklfowsSingleCorrelationId() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"c123\");\n\n        List<Workflow> workflowArrayList = Collections.singletonList(workflow);\n\n        when(executionService.getWorkflowInstances(\n                        anyString(), anyString(), anyBoolean(), anyBoolean()))\n                .thenReturn(workflowArrayList);\n        assertEquals(workflowArrayList, workflowService.getWorkflows(\"test\", \"c123\", true, true));\n    }\n\n    @Test\n    public void testGetWorklfowsMultipleCorrelationId() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"c123\");\n\n        List<Workflow> workflowArrayList = Collections.singletonList(workflow);\n\n        List<String> correlationIdList = Collections.singletonList(\"c123\");\n\n        Map<String, List<Workflow>> workflowMap = new HashMap<>();\n        workflowMap.put(\"c123\", workflowArrayList);\n\n        when(executionService.getWorkflowInstances(\n                        anyString(), anyString(), anyBoolean(), anyBoolean()))\n                .thenReturn(workflowArrayList);\n        assertEquals(\n                workflowMap, workflowService.getWorkflows(\"test\", true, true, correlationIdList));\n    }\n\n    @Test\n    public void testGetExecutionStatus() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"c123\");\n\n        when(executionService.getExecutionStatus(anyString(), anyBoolean())).thenReturn(workflow);\n        assertEquals(workflow, workflowService.getExecutionStatus(\"w123\", true));\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testGetExecutionStatusNoWorkflowId() {\n        try {\n            workflowService.getExecutionStatus(\"\", true);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = NotFoundException.class)\n    public void testNotFoundExceptionGetExecutionStatus() {\n        when(executionService.getExecutionStatus(anyString(), anyBoolean())).thenReturn(null);\n        workflowService.getExecutionStatus(\"w123\", true);\n    }\n\n    @Test\n    public void testDeleteWorkflow() {\n        workflowService.deleteWorkflow(\"w123\", false);\n        verify(executionService, times(1)).removeWorkflow(anyString(), eq(false));\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testInvalidDeleteWorkflow() {\n        try {\n            workflowService.deleteWorkflow(null, false);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test\n    public void testArchiveWorkflow() {\n        workflowService.deleteWorkflow(\"w123\", true);\n        verify(executionService, times(1)).removeWorkflow(anyString(), eq(true));\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testInvalidArchiveWorkflow() {\n        try {\n            workflowService.deleteWorkflow(null, true);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testInvalidPauseWorkflow() {\n        try {\n            workflowService.pauseWorkflow(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testInvalidResumeWorkflow() {\n        try {\n            workflowService.resumeWorkflow(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testInvalidSkipTaskFromWorkflow() {\n        try {\n            SkipTaskRequest skipTaskRequest = new SkipTaskRequest();\n            workflowService.skipTaskFromWorkflow(null, null, skipTaskRequest);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId name cannot be null or empty.\"));\n            assertTrue(messages.contains(\"TaskReferenceName cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testInvalidWorkflowNameGetRunningWorkflows() {\n        try {\n            workflowService.getRunningWorkflows(null, 123, null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"Workflow name cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test\n    public void testGetRunningWorkflowsTime() {\n        workflowService.getRunningWorkflows(\"test\", 1, 100L, 120L);\n        verify(workflowExecutor, times(1))\n                .getWorkflows(anyString(), anyInt(), anyLong(), anyLong());\n    }\n\n    @Test\n    public void testGetRunningWorkflows() {\n        workflowService.getRunningWorkflows(\"test\", 1, null, null);\n        verify(workflowExecutor, times(1)).getRunningWorkflowIds(anyString(), anyInt());\n    }\n\n    @Test\n    public void testDecideWorkflow() {\n        workflowService.decideWorkflow(\"test\");\n        verify(workflowExecutor, times(1)).decide(anyString());\n    }\n\n    @Test\n    public void testPauseWorkflow() {\n        workflowService.pauseWorkflow(\"test\");\n        verify(workflowExecutor, times(1)).pauseWorkflow(anyString());\n    }\n\n    @Test\n    public void testResumeWorkflow() {\n        workflowService.resumeWorkflow(\"test\");\n        verify(workflowExecutor, times(1)).resumeWorkflow(anyString());\n    }\n\n    @Test\n    public void testSkipTaskFromWorkflow() {\n        workflowService.skipTaskFromWorkflow(\"test\", \"testTask\", null);\n        verify(workflowExecutor, times(1)).skipTaskFromWorkflow(anyString(), anyString(), isNull());\n    }\n\n    @Test\n    public void testRerunWorkflow() {\n        RerunWorkflowRequest request = new RerunWorkflowRequest();\n        workflowService.rerunWorkflow(\"test\", request);\n        verify(workflowExecutor, times(1)).rerun(any(RerunWorkflowRequest.class));\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRerunWorkflowNull() {\n        try {\n            workflowService.rerunWorkflow(null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(2, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            assertTrue(messages.contains(\"RerunWorkflowRequest cannot be null.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRestartWorkflowNull() {\n        try {\n            workflowService.restartWorkflow(null, false);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testRetryWorkflowNull() {\n        try {\n            workflowService.retryWorkflow(null, false);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testResetWorkflowNull() {\n        try {\n            workflowService.resetWorkflow(null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test(expected = ConstraintViolationException.class)\n    public void testTerminateWorkflowNull() {\n        try {\n            workflowService.terminateWorkflow(null, null);\n        } catch (ConstraintViolationException ex) {\n            assertEquals(1, ex.getConstraintViolations().size());\n            Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n            assertTrue(messages.contains(\"WorkflowId cannot be null or empty.\"));\n            throw ex;\n        }\n    }\n\n    @Test\n    public void testRerunWorkflowReturnWorkflowId() {\n        RerunWorkflowRequest request = new RerunWorkflowRequest();\n        String workflowId = \"w123\";\n        when(workflowExecutor.rerun(any(RerunWorkflowRequest.class))).thenReturn(workflowId);\n        assertEquals(workflowId, workflowService.rerunWorkflow(\"test\", request));\n    }\n\n    @Test\n    public void testRestartWorkflow() {\n        workflowService.restartWorkflow(\"w123\", false);\n        verify(workflowExecutor, times(1)).restart(anyString(), anyBoolean());\n    }\n\n    @Test\n    public void testRetryWorkflow() {\n        workflowService.retryWorkflow(\"w123\", false);\n        verify(workflowExecutor, times(1)).retry(anyString(), anyBoolean());\n    }\n\n    @Test\n    public void testResetWorkflow() {\n        workflowService.resetWorkflow(\"w123\");\n        verify(workflowExecutor, times(1)).resetCallbacksForWorkflow(anyString());\n    }\n\n    @Test\n    public void testTerminateWorkflow() {\n        workflowService.terminateWorkflow(\"w123\", \"test\");\n        verify(workflowExecutor, times(1)).terminateWorkflow(anyString(), anyString());\n    }\n\n    @Test\n    public void testSearchWorkflows() {\n        Workflow workflow = new Workflow();\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"name\");\n        def.setVersion(1);\n        workflow.setWorkflowDefinition(def);\n        workflow.setCorrelationId(\"c123\");\n\n        WorkflowSummary workflowSummary = new WorkflowSummary(workflow);\n        List<WorkflowSummary> listOfWorkflowSummary = Collections.singletonList(workflowSummary);\n\n        SearchResult<WorkflowSummary> searchResult = new SearchResult<>(100, listOfWorkflowSummary);\n\n        when(executionService.search(\"*\", \"*\", 0, 100, Collections.singletonList(\"asc\")))\n                .thenReturn(searchResult);\n        assertEquals(searchResult, workflowService.searchWorkflows(0, 100, \"asc\", \"*\", \"*\"));\n        assertEquals(\n                searchResult,\n                workflowService.searchWorkflows(\n                        0, 100, Collections.singletonList(\"asc\"), \"*\", \"*\"));\n    }\n\n    @Test\n    public void testSearchWorkflowsV2() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"c123\");\n\n        List<Workflow> listOfWorkflow = Collections.singletonList(workflow);\n        SearchResult<Workflow> searchResult = new SearchResult<>(1, listOfWorkflow);\n\n        when(executionService.searchV2(\"*\", \"*\", 0, 100, Collections.singletonList(\"asc\")))\n                .thenReturn(searchResult);\n        assertEquals(searchResult, workflowService.searchWorkflowsV2(0, 100, \"asc\", \"*\", \"*\"));\n        assertEquals(\n                searchResult,\n                workflowService.searchWorkflowsV2(\n                        0, 100, Collections.singletonList(\"asc\"), \"*\", \"*\"));\n    }\n\n    @Test\n    public void testInvalidSizeSearchWorkflows() {\n        ConstraintViolationException ex =\n                assertThrows(\n                        ConstraintViolationException.class,\n                        () -> workflowService.searchWorkflows(0, 6000, \"asc\", \"*\", \"*\"));\n        assertEquals(1, ex.getConstraintViolations().size());\n        Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n        assertTrue(\n                messages.contains(\n                        \"Cannot return more than 5000 workflows. Please use pagination.\"));\n    }\n\n    @Test\n    public void testInvalidSizeSearchWorkflowsV2() {\n        ConstraintViolationException ex =\n                assertThrows(\n                        ConstraintViolationException.class,\n                        () -> workflowService.searchWorkflowsV2(0, 6000, \"asc\", \"*\", \"*\"));\n        assertEquals(1, ex.getConstraintViolations().size());\n        Set<String> messages = getConstraintViolationMessages(ex.getConstraintViolations());\n        assertTrue(\n                messages.contains(\n                        \"Cannot return more than 5000 workflows. Please use pagination.\"));\n    }\n\n    @Test\n    public void testSearchWorkflowsByTasks() {\n        Workflow workflow = new Workflow();\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"name\");\n        def.setVersion(1);\n        workflow.setWorkflowDefinition(def);\n        workflow.setCorrelationId(\"c123\");\n\n        WorkflowSummary workflowSummary = new WorkflowSummary(workflow);\n        List<WorkflowSummary> listOfWorkflowSummary = Collections.singletonList(workflowSummary);\n        SearchResult<WorkflowSummary> searchResult = new SearchResult<>(100, listOfWorkflowSummary);\n\n        when(executionService.searchWorkflowByTasks(\n                        \"*\", \"*\", 0, 100, Collections.singletonList(\"asc\")))\n                .thenReturn(searchResult);\n        assertEquals(searchResult, workflowService.searchWorkflowsByTasks(0, 100, \"asc\", \"*\", \"*\"));\n        assertEquals(\n                searchResult,\n                workflowService.searchWorkflowsByTasks(\n                        0, 100, Collections.singletonList(\"asc\"), \"*\", \"*\"));\n    }\n\n    @Test\n    public void testSearchWorkflowsByTasksV2() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"c123\");\n\n        List<Workflow> listOfWorkflow = Collections.singletonList(workflow);\n        SearchResult<Workflow> searchResult = new SearchResult<>(1, listOfWorkflow);\n\n        when(executionService.searchWorkflowByTasksV2(\n                        \"*\", \"*\", 0, 100, Collections.singletonList(\"asc\")))\n                .thenReturn(searchResult);\n        assertEquals(\n                searchResult, workflowService.searchWorkflowsByTasksV2(0, 100, \"asc\", \"*\", \"*\"));\n        assertEquals(\n                searchResult,\n                workflowService.searchWorkflowsByTasksV2(\n                        0, 100, Collections.singletonList(\"asc\"), \"*\", \"*\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/validations/WorkflowDefConstraintTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.validations;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\n\nimport org.apache.bval.jsr.ApacheValidationProvider;\nimport org.junit.AfterClass;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.dao.MetadataDAO;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.when;\n\npublic class WorkflowDefConstraintTest {\n\n    private static Validator validator;\n    private static ValidatorFactory validatorFactory;\n    private MetadataDAO mockMetadataDao;\n\n    @BeforeClass\n    public static void init() {\n        validatorFactory =\n                Validation.byProvider(ApacheValidationProvider.class)\n                        .configure()\n                        .buildValidatorFactory();\n        validator = validatorFactory.getValidator();\n    }\n\n    @AfterClass\n    public static void close() {\n        validatorFactory.close();\n    }\n\n    @Before\n    public void setUp() {\n        mockMetadataDao = Mockito.mock(MetadataDAO.class);\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n        ValidationContext.initialize(mockMetadataDao);\n    }\n\n    @Test\n    public void testWorkflowTaskName() {\n        TaskDef taskDef = new TaskDef(); // name is null\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        Validator validator = factory.getValidator();\n        Set<ConstraintViolation<Object>> result = validator.validate(taskDef);\n        assertEquals(2, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskSimple() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"sampleWorkflow\");\n        workflowDef.setDescription(\"Sample workflow def\");\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        workflowDef.setVersion(2);\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"fileLocation\", \"${workflow.input.fileLocation}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        Set<ConstraintViolation<WorkflowDef>> result = validator.validate(workflowDef);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    /*Testcase to check inputParam is not valid\n     */\n    public void testWorkflowTaskInvalidInputParam() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"sampleWorkflow\");\n        workflowDef.setDescription(\"Sample workflow def\");\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        workflowDef.setVersion(2);\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"fileLocation\", \"${work.input.fileLocation}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n\n        workflowDef.setTasks(tasks);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        validator = factory.getValidator();\n\n        when(mockMetadataDao.getTaskDef(\"work1\")).thenReturn(new TaskDef());\n        Set<ConstraintViolation<WorkflowDef>> result = validator.validate(workflowDef);\n        assertEquals(1, result.size());\n        assertEquals(\n                result.iterator().next().getMessage(),\n                \"taskReferenceName: work for given task: task_1 input value: fileLocation of input parameter: ${work.input.fileLocation} is not defined in workflow definition.\");\n    }\n\n    @Test\n    public void testWorkflowTaskReferenceNameNotUnique() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"sampleWorkflow\");\n        workflowDef.setDescription(\"Sample workflow def\");\n        workflowDef.setOwnerEmail(\"sample@test.com\");\n        workflowDef.setVersion(2);\n\n        WorkflowTask workflowTask_1 = new WorkflowTask();\n        workflowTask_1.setName(\"task_1\");\n        workflowTask_1.setTaskReferenceName(\"task_1\");\n        workflowTask_1.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"fileLocation\", \"${task_2.input.fileLocation}\");\n\n        workflowTask_1.setInputParameters(inputParam);\n\n        WorkflowTask workflowTask_2 = new WorkflowTask();\n        workflowTask_2.setName(\"task_2\");\n        workflowTask_2.setTaskReferenceName(\"task_1\");\n        workflowTask_2.setType(TaskType.TASK_TYPE_SIMPLE);\n\n        workflowTask_2.setInputParameters(inputParam);\n\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.add(workflowTask_1);\n        tasks.add(workflowTask_2);\n\n        workflowDef.setTasks(tasks);\n\n        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();\n        validator = factory.getValidator();\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n        Set<ConstraintViolation<WorkflowDef>> result = validator.validate(workflowDef);\n        assertEquals(3, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"taskReferenceName: task_2 for given task: task_2 input value: fileLocation of input parameter: ${task_2.input.fileLocation} is not defined in workflow definition.\"));\n        assertTrue(\n                validationErrors.contains(\n                        \"taskReferenceName: task_2 for given task: task_1 input value: fileLocation of input parameter: ${task_2.input.fileLocation} is not defined in workflow definition.\"));\n        assertTrue(\n                validationErrors.contains(\n                        \"taskReferenceName: task_1 should be unique across tasks for a given workflowDefinition: sampleWorkflow\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/com/netflix/conductor/validations/WorkflowTaskTypeConstraintTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.validations;\n\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport javax.validation.ConstraintViolation;\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\nimport javax.validation.executable.ExecutableValidator;\n\nimport org.apache.bval.jsr.ApacheValidationProvider;\nimport org.junit.AfterClass;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.tasks.Terminate;\nimport com.netflix.conductor.dao.MetadataDAO;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.when;\n\npublic class WorkflowTaskTypeConstraintTest {\n\n    private static Validator validator;\n    private static ValidatorFactory validatorFactory;\n    private MetadataDAO mockMetadataDao;\n\n    @BeforeClass\n    public static void init() {\n        validatorFactory =\n                Validation.byProvider(ApacheValidationProvider.class)\n                        .configure()\n                        .buildValidatorFactory();\n        validator = validatorFactory.getValidator();\n    }\n\n    @AfterClass\n    public static void close() {\n        validatorFactory.close();\n    }\n\n    @Before\n    public void setUp() {\n        mockMetadataDao = Mockito.mock(MetadataDAO.class);\n        ValidationContext.initialize(mockMetadataDao);\n    }\n\n    @Test\n    public void testWorkflowTaskMissingReferenceName() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setDynamicForkTasksParam(\"taskList\");\n        workflowTask.setDynamicForkTasksInputParamName(\"ForkTaskInputParam\");\n        workflowTask.setTaskReferenceName(null);\n\n        Set<ConstraintViolation<Object>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        assertEquals(\n                result.iterator().next().getMessage(),\n                \"WorkflowTask taskReferenceName name cannot be empty or null\");\n    }\n\n    @Test\n    public void testWorkflowTaskTestSetType() throws NoSuchMethodException {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n\n        Method method = WorkflowTask.class.getMethod(\"setType\", String.class);\n        Object[] parameterValues = {\"\"};\n\n        ExecutableValidator executableValidator = validator.forExecutables();\n\n        Set<ConstraintViolation<Object>> result =\n                executableValidator.validateParameters(workflowTask, method, parameterValues);\n\n        assertEquals(1, result.size());\n        assertEquals(\n                result.iterator().next().getMessage(), \"WorkTask type cannot be null or empty\");\n    }\n\n    @Test\n    public void testWorkflowTaskTypeEvent() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"EVENT\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n        assertEquals(\n                result.iterator().next().getMessage(),\n                \"sink field is required for taskType: EVENT taskName: encode\");\n    }\n\n    @Test\n    public void testWorkflowTaskTypeDynamic() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"DYNAMIC\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n        assertEquals(\n                result.iterator().next().getMessage(),\n                \"dynamicTaskNameParam field is required for taskType: DYNAMIC taskName: encode\");\n    }\n\n    @Test\n    public void testWorkflowTaskTypeDecision() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"DECISION\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(2, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"decisionCases should have atleast one task for taskType: DECISION taskName: encode\"));\n        assertTrue(\n                validationErrors.contains(\n                        \"caseValueParam or caseExpression field is required for taskType: DECISION taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeDoWhile() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"DO_WHILE\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(2, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"loopExpression field is required for taskType: DO_WHILE taskName: encode\"));\n        assertTrue(\n                validationErrors.contains(\n                        \"loopover field is required for taskType: DO_WHILE taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeWait() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"WAIT\");\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n        workflowTask.setInputParameters(Map.of(\"duration\", \"10s\", \"until\", \"2022-04-16\"));\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"Both 'duration' and 'until' specified. Please provide only one input\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeDecisionWithCaseParam() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"DECISION\");\n        workflowTask.setCaseExpression(\"$.valueCheck == null ? 'true': 'false'\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"decisionCases should have atleast one task for taskType: DECISION taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeForJoinDynamic() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"FORK_JOIN_DYNAMIC\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(2, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"dynamicForkTasksInputParamName field is required for taskType: FORK_JOIN_DYNAMIC taskName: encode\"));\n        assertTrue(\n                validationErrors.contains(\n                        \"dynamicForkTasksParam field is required for taskType: FORK_JOIN_DYNAMIC taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeForJoinDynamicLegacy() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"FORK_JOIN_DYNAMIC\");\n        workflowTask.setDynamicForkJoinTasksParam(\"taskList\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeForJoinDynamicWithForJoinTaskParam() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"FORK_JOIN_DYNAMIC\");\n        workflowTask.setDynamicForkJoinTasksParam(\"taskList\");\n        workflowTask.setDynamicForkTasksInputParamName(\"ForkTaskInputParam\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"dynamicForkJoinTasksParam or combination of dynamicForkTasksInputParamName and dynamicForkTasksParam cam be used for taskType: FORK_JOIN_DYNAMIC taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeForJoinDynamicValid() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"FORK_JOIN_DYNAMIC\");\n        workflowTask.setDynamicForkTasksParam(\"ForkTasksParam\");\n        workflowTask.setDynamicForkTasksInputParamName(\"ForkTaskInputParam\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeForJoinDynamicWithForJoinTaskParamAndInputTaskParam() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"FORK_JOIN_DYNAMIC\");\n        workflowTask.setDynamicForkJoinTasksParam(\"taskList\");\n        workflowTask.setDynamicForkTasksInputParamName(\"ForkTaskInputParam\");\n        workflowTask.setDynamicForkTasksParam(\"ForkTasksParam\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"dynamicForkJoinTasksParam or combination of dynamicForkTasksInputParamName and dynamicForkTasksParam cam be used for taskType: FORK_JOIN_DYNAMIC taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeHTTP() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"HTTP\");\n        workflowTask.getInputParameters().put(\"http_request\", \"http://www.netflix.com\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeHTTPWithHttpParamMissing() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"HTTP\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"inputParameters.http_request field is required for taskType: HTTP taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeHTTPWithHttpParamInTaskDef() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"HTTP\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"encode\");\n        taskDef.getInputTemplate().put(\"http_request\", \"http://www.netflix.com\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(taskDef);\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeHTTPWithHttpParamInTaskDefAndWorkflowTask() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"HTTP\");\n        workflowTask.getInputParameters().put(\"http_request\", \"http://www.netflix.com\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"encode\");\n        taskDef.getInputTemplate().put(\"http_request\", \"http://www.netflix.com\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(taskDef);\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeFork() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"FORK_JOIN\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"forkTasks should have atleast one task for taskType: FORK_JOIN taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeSubworkflowMissingSubworkflowParam() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"SUB_WORKFLOW\");\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"subWorkflowParam field is required for taskType: SUB_WORKFLOW taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeSubworkflow() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"SUB_WORKFLOW\");\n\n        SubWorkflowParams subWorkflowTask = new SubWorkflowParams();\n        workflowTask.setSubWorkflowParam(subWorkflowTask);\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(2, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(validationErrors.contains(\"SubWorkflowParams name cannot be null\"));\n        assertTrue(validationErrors.contains(\"SubWorkflowParams name cannot be empty\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeTerminateWithoutTerminationStatus() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(TaskType.TASK_TYPE_TERMINATE);\n        workflowTask.setName(\"terminate_task\");\n\n        workflowTask.setInputParameters(\n                Collections.singletonMap(\n                        Terminate.getTerminationWorkflowOutputParameter(), \"blah\"));\n        List<String> validationErrors = getErrorMessages(workflowTask);\n\n        Assert.assertEquals(1, validationErrors.size());\n        Assert.assertEquals(\n                \"terminate task must have an terminationStatus parameter and must be set to COMPLETED or FAILED, taskName: terminate_task\",\n                validationErrors.get(0));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeTerminateWithInvalidStatus() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(TaskType.TASK_TYPE_TERMINATE);\n        workflowTask.setName(\"terminate_task\");\n\n        workflowTask.setInputParameters(\n                Collections.singletonMap(Terminate.getTerminationStatusParameter(), \"blah\"));\n\n        List<String> validationErrors = getErrorMessages(workflowTask);\n\n        Assert.assertEquals(1, validationErrors.size());\n        Assert.assertEquals(\n                \"terminate task must have an terminationStatus parameter and must be set to COMPLETED or FAILED, taskName: terminate_task\",\n                validationErrors.get(0));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeTerminateOptional() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(TaskType.TASK_TYPE_TERMINATE);\n        workflowTask.setName(\"terminate_task\");\n\n        workflowTask.setInputParameters(\n                Collections.singletonMap(Terminate.getTerminationStatusParameter(), \"COMPLETED\"));\n        workflowTask.setOptional(true);\n\n        List<String> validationErrors = getErrorMessages(workflowTask);\n\n        Assert.assertEquals(1, validationErrors.size());\n        Assert.assertEquals(\n                \"terminate task cannot be optional, taskName: terminate_task\",\n                validationErrors.get(0));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeTerminateValid() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(TaskType.TASK_TYPE_TERMINATE);\n        workflowTask.setName(\"terminate_task\");\n\n        workflowTask.setInputParameters(\n                Collections.singletonMap(Terminate.getTerminationStatusParameter(), \"COMPLETED\"));\n\n        List<String> validationErrors = getErrorMessages(workflowTask);\n\n        Assert.assertEquals(0, validationErrors.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeKafkaPublish() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"KAFKA_PUBLISH\");\n        workflowTask.getInputParameters().put(\"kafka_request\", \"testInput\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeKafkaPublishWithRequestParamMissing() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"KAFKA_PUBLISH\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"inputParameters.kafka_request field is required for taskType: KAFKA_PUBLISH taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeKafkaPublishWithKafkaParamInTaskDef() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"KAFKA_PUBLISH\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"encode\");\n        taskDef.getInputTemplate().put(\"kafka_request\", \"test_kafka_request\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(taskDef);\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeKafkaPublishWithRequestParamInTaskDefAndWorkflowTask() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"KAFKA_PUBLISH\");\n        workflowTask.getInputParameters().put(\"kafka_request\", \"http://www.netflix.com\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"encode\");\n        taskDef.getInputTemplate().put(\"kafka_request\", \"test Kafka Request\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(taskDef);\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeJSONJQTransform() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"JSON_JQ_TRANSFORM\");\n        workflowTask.getInputParameters().put(\"queryExpression\", \".\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testWorkflowTaskTypeJSONJQTransformWithQueryParamMissing() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"JSON_JQ_TRANSFORM\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(new TaskDef());\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(1, result.size());\n\n        List<String> validationErrors = new ArrayList<>();\n\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        assertTrue(\n                validationErrors.contains(\n                        \"inputParameters.queryExpression field is required for taskType: JSON_JQ_TRANSFORM taskName: encode\"));\n    }\n\n    @Test\n    public void testWorkflowTaskTypeJSONJQTransformWithQueryParamInTaskDef() {\n        WorkflowTask workflowTask = createSampleWorkflowTask();\n        workflowTask.setType(\"JSON_JQ_TRANSFORM\");\n\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"encode\");\n        taskDef.getInputTemplate().put(\"queryExpression\", \".\");\n\n        when(mockMetadataDao.getTaskDef(anyString())).thenReturn(taskDef);\n\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        assertEquals(0, result.size());\n    }\n\n    private List<String> getErrorMessages(WorkflowTask workflowTask) {\n        Set<ConstraintViolation<WorkflowTask>> result = validator.validate(workflowTask);\n        List<String> validationErrors = new ArrayList<>();\n        result.forEach(e -> validationErrors.add(e.getMessage()));\n\n        return validationErrors;\n    }\n\n    private WorkflowTask createSampleWorkflowTask() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"encode\");\n        workflowTask.setTaskReferenceName(\"encode\");\n        workflowTask.setType(\"FORK_JOIN_DYNAMIC\");\n        Map<String, Object> inputParam = new HashMap<>();\n        inputParam.put(\"fileLocation\", \"${workflow.input.fileLocation}\");\n        workflowTask.setInputParameters(inputParam);\n        return workflowTask;\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/completed.json",
    "content": "{\n  \"ownerApp\": \"cpeworkflowtests\",\n  \"createTime\": 1547430586952,\n  \"updateTime\": 1547430613550,\n  \"status\": \"COMPLETED\",\n  \"endTime\": 1547430613550,\n  \"workflowId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n  \"tasks\": [\n    {\n      \"taskType\": \"perf_task_1\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"0\",\n        \"oddEven\": \"0\"\n      },\n      \"referenceTaskName\": \"perf_task_1\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_1\",\n      \"scheduledTime\": 1547430586967,\n      \"startTime\": 1547430589848,\n      \"endTime\": 1547430589873,\n      \"updateTime\": 1547430613560,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"485fdbdf-9f49-4879-9471-4722225e5613\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-0618a1a5e9526c9a1\",\n      \"outputData\": {\n        \"mod\": \"8\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_1\",\n        \"taskReferenceName\": \"perf_task_1\",\n        \"inputParameters\": {\n          \"mod\": \"workflow.input.mod\",\n          \"oddEven\": \"workflow.input.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069389709,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_1\",\n          \"description\": \"perf_task_1\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 2881,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:49:49:867 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_1,1\",\n        \"01/14/19, 01:49:49:867 : Starting to execute perf_task_1, id=485fdbdf-9f49-4879-9471-4722225e5613\",\n        \"01/14/19, 01:49:49:867 : failure probability is 0.3066777 against 0.0\",\n        \"01/14/19, 01:49:49:868 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"perf_task_10\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"taskToExecute\": \"perf_task_10\"\n      },\n      \"referenceTaskName\": \"perf_task_2\",\n      \"retryCount\": 0,\n      \"seq\": 2,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_10\",\n      \"scheduledTime\": 1547430589900,\n      \"startTime\": 1547430590465,\n      \"endTime\": 1547430590499,\n      \"updateTime\": 1547430613572,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"14988072-378d-4b6c-a596-09db9c88c5d1\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-07f2166099c597efe\",\n      \"outputData\": {\n        \"mod\": \"0\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_10\",\n        \"taskReferenceName\": \"perf_task_2\",\n        \"inputParameters\": {\n          \"taskToExecute\": \"workflow.input.task2Name\"\n        },\n        \"type\": \"DYNAMIC\",\n        \"dynamicTaskNameParam\": \"taskToExecute\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069389226,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_10\",\n          \"description\": \"perf_task_10\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 565,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:49:50:489 : Starting to execute perf_task_10, id=14988072-378d-4b6c-a596-09db9c88c5d1\",\n        \"01/14/19, 01:49:50:489 : failure probability is 0.040783882 against 0.0\",\n        \"01/14/19, 01:49:50:489 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_2,1\",\n        \"01/14/19, 01:49:50:490 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"perf_task_3\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"0\",\n        \"oddEven\": \"0\"\n      },\n      \"referenceTaskName\": \"perf_task_3\",\n      \"retryCount\": 0,\n      \"seq\": 3,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_3\",\n      \"scheduledTime\": 1547430590531,\n      \"startTime\": 1547430591460,\n      \"endTime\": 1547430591488,\n      \"updateTime\": 1547430613582,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"91b6ba4c-c414-4cb1-a2e7-18edd7aa22fd\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-0618a1a5e9526c9a1\",\n      \"outputData\": {\n        \"mod\": \"9\",\n        \"oddEven\": \"1\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_3\",\n        \"taskReferenceName\": \"perf_task_3\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_2.output.mod\",\n          \"oddEven\": \"perf_task_2.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069389814,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_3\",\n          \"description\": \"perf_task_3\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 929,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:49:51:477 : Starting to execute perf_task_3, id=91b6ba4c-c414-4cb1-a2e7-18edd7aa22fd\",\n        \"01/14/19, 01:49:51:477 : failure probability is 0.9401053 against 0.0\",\n        \"01/14/19, 01:49:51:477 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_3,1\",\n        \"01/14/19, 01:49:51:479 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"HTTP\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"http_request\": {\n          \"uri\": \"/wfe_perf/workflow/_search?q=status:RUNNING&size=0&devint\",\n          \"method\": \"GET\",\n          \"vipAddress\": \"es_conductor.netflix.com\"\n        }\n      },\n      \"referenceTaskName\": \"get_es_1\",\n      \"retryCount\": 0,\n      \"seq\": 4,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"get_from_es\",\n      \"scheduledTime\": 1547430591524,\n      \"startTime\": 1547430591961,\n      \"endTime\": 1547430592238,\n      \"updateTime\": 1547430613601,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"b8095fef-0028-4fa3-a2a2-6e59c224bb7d\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"i-01815a305a47fb626\",\n      \"outputData\": {\n        \"response\": {\n          \"headers\": {\n            \"Content-Length\": [\n              \"121\"\n            ],\n            \"Content-Type\": [\n              \"application/json; charset=UTF-8\"\n            ]\n          },\n          \"reasonPhrase\": \"OK\",\n          \"body\": {\n            \"took\": 2,\n            \"timed_out\": false,\n            \"_shards\": {\n              \"total\": 6,\n              \"successful\": 6,\n              \"failed\": 0\n            },\n            \"hits\": {\n              \"total\": 0,\n              \"max_score\": 0,\n              \"hits\": []\n            }\n          },\n          \"statusCode\": 200\n        }\n      },\n      \"workflowTask\": {\n        \"name\": \"get_from_es\",\n        \"taskReferenceName\": \"get_es_1\",\n        \"type\": \"HTTP\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 1,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 437,\n      \"taskDefinition\": {\n        \"present\": false\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": []\n    },\n    {\n      \"taskType\": \"DECISION\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"hasChildren\": \"true\",\n        \"case\": \"1\"\n      },\n      \"referenceTaskName\": \"oddEvenDecision\",\n      \"retryCount\": 0,\n      \"seq\": 5,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 0,\n      \"taskDefName\": \"DECISION\",\n      \"scheduledTime\": 1547430592280,\n      \"startTime\": 1547430592292,\n      \"endTime\": 1547430592284,\n      \"updateTime\": 1547430613614,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"5c2d843a-8320-4b6c-9765-e91bff433dba\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"caseOutput\": [\n          \"1\"\n        ]\n      },\n      \"workflowTask\": {\n        \"name\": \"oddEvenDecision\",\n        \"taskReferenceName\": \"oddEvenDecision\",\n        \"inputParameters\": {\n          \"oddEven\": \"perf_task_3.output.oddEven\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"oddEven\",\n        \"decisionCases\": {\n          \"0\": [\n            {\n              \"name\": \"perf_task_4\",\n              \"taskReferenceName\": \"perf_task_4\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_3.output.mod\",\n                \"oddEven\": \"perf_task_3.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390494,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_4\",\n                \"description\": \"perf_task_4\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"dynamic_fanout\",\n              \"taskReferenceName\": \"fanout1\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"perf_task_4.output.dynamicTasks\",\n                \"input\": \"perf_task_4.output.inputs\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"input\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"dynamic_join\",\n              \"taskReferenceName\": \"join1\",\n              \"type\": \"JOIN\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_5\",\n              \"taskReferenceName\": \"perf_task_5\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_4.output.mod\",\n                \"oddEven\": \"perf_task_4.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390611,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_5\",\n                \"description\": \"perf_task_5\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_6\",\n              \"taskReferenceName\": \"perf_task_6\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_5.output.mod\",\n                \"oddEven\": \"perf_task_5.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390789,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_6\",\n                \"description\": \"perf_task_6\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            }\n          ],\n          \"1\": [\n            {\n              \"name\": \"perf_task_7\",\n              \"taskReferenceName\": \"perf_task_7\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_3.output.mod\",\n                \"oddEven\": \"perf_task_3.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390955,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_7\",\n                \"description\": \"perf_task_7\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_8\",\n              \"taskReferenceName\": \"perf_task_8\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_7.output.mod\",\n                \"oddEven\": \"perf_task_7.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069391122,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_8\",\n                \"description\": \"perf_task_8\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_9\",\n              \"taskReferenceName\": \"perf_task_9\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_8.output.mod\",\n                \"oddEven\": \"perf_task_8.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069391291,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_9\",\n                \"description\": \"perf_task_9\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"modDecision\",\n              \"taskReferenceName\": \"modDecision\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_8.output.mod\"\n              },\n              \"type\": \"DECISION\",\n              \"caseValueParam\": \"mod\",\n              \"decisionCases\": {\n                \"0\": [\n                  {\n                    \"name\": \"perf_task_12\",\n                    \"taskReferenceName\": \"perf_task_12\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389427,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_12\",\n                      \"description\": \"perf_task_12\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_13\",\n                    \"taskReferenceName\": \"perf_task_13\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389276,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_13\",\n                      \"description\": \"perf_task_13\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf1\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    },\n                    \"optional\": false,\n                    \"asyncComplete\": false\n                  }\n                ],\n                \"1\": [\n                  {\n                    \"name\": \"perf_task_15\",\n                    \"taskReferenceName\": \"perf_task_15\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069388963,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_15\",\n                      \"description\": \"perf_task_15\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_16\",\n                    \"taskReferenceName\": \"perf_task_16\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_15.output.mod\",\n                      \"oddEven\": \"perf_task_15.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389067,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_16\",\n                      \"description\": \"perf_task_16\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf2\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    },\n                    \"optional\": false,\n                    \"asyncComplete\": false\n                  }\n                ],\n                \"4\": [\n                  {\n                    \"name\": \"perf_task_18\",\n                    \"taskReferenceName\": \"perf_task_18\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069388904,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_18\",\n                      \"description\": \"perf_task_18\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_19\",\n                    \"taskReferenceName\": \"perf_task_19\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_18.output.mod\",\n                      \"oddEven\": \"perf_task_18.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389173,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_19\",\n                      \"description\": \"perf_task_19\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  }\n                ],\n                \"5\": [\n                  {\n                    \"name\": \"perf_task_21\",\n                    \"taskReferenceName\": \"perf_task_21\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069390669,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_21\",\n                      \"description\": \"perf_task_21\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf3\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    },\n                    \"optional\": false,\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_22\",\n                    \"taskReferenceName\": \"perf_task_22\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_21.output.mod\",\n                      \"oddEven\": \"perf_task_21.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069391345,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_22\",\n                      \"description\": \"perf_task_22\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  }\n                ]\n              },\n              \"defaultCase\": [\n                {\n                  \"name\": \"perf_task_24\",\n                  \"taskReferenceName\": \"perf_task_24\",\n                  \"inputParameters\": {\n                    \"mod\": \"perf_task_9.output.mod\",\n                    \"oddEven\": \"perf_task_9.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1547069391074,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"perf_task_24\",\n                    \"description\": \"perf_task_24\",\n                    \"retryCount\": 2,\n                    \"timeoutSeconds\": 600,\n                    \"timeoutPolicy\": \"TIME_OUT_WF\",\n                    \"retryLogic\": \"FIXED\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 300,\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1\n                  },\n                  \"asyncComplete\": false\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf4\",\n                  \"inputParameters\": {\n                    \"mod\": \"perf_task_12.output.mod\",\n                    \"oddEven\": \"perf_task_12.output.oddEven\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"startDelay\": 0,\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  },\n                  \"optional\": false,\n                  \"asyncComplete\": false\n                },\n                {\n                  \"name\": \"perf_task_25\",\n                  \"taskReferenceName\": \"perf_task_25\",\n                  \"inputParameters\": {\n                    \"mod\": \"perf_task_24.output.mod\",\n                    \"oddEven\": \"perf_task_24.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1547069391177,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"perf_task_25\",\n                    \"description\": \"perf_task_25\",\n                    \"retryCount\": 2,\n                    \"timeoutSeconds\": 600,\n                    \"timeoutPolicy\": \"TIME_OUT_WF\",\n                    \"retryLogic\": \"FIXED\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 300,\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1\n                  },\n                  \"asyncComplete\": false\n                }\n              ],\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            }\n          ]\n        },\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 12,\n      \"taskDefinition\": {\n        \"present\": false\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": []\n    },\n    {\n      \"taskType\": \"perf_task_7\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"9\",\n        \"oddEven\": \"1\"\n      },\n      \"referenceTaskName\": \"perf_task_7\",\n      \"retryCount\": 0,\n      \"seq\": 6,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_7\",\n      \"scheduledTime\": 1547430592287,\n      \"startTime\": 1547430593603,\n      \"endTime\": 1547430593641,\n      \"updateTime\": 1547430613624,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"10efe69b-691f-49c6-9bce-42ba08ff4d2e\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-075e5e67066be5d52\",\n      \"outputData\": {\n        \"mod\": \"5\",\n        \"oddEven\": \"1\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_7\",\n        \"taskReferenceName\": \"perf_task_7\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_3.output.mod\",\n          \"oddEven\": \"perf_task_3.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069390955,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_7\",\n          \"description\": \"perf_task_7\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 1316,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:49:53:622 : Starting to execute perf_task_7, id=10efe69b-691f-49c6-9bce-42ba08ff4d2e\",\n        \"01/14/19, 01:49:53:622 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_7,1\",\n        \"01/14/19, 01:49:53:622 : failure probability is 0.62726057 against 0.0\",\n        \"01/14/19, 01:49:53:625 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"perf_task_8\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"5\",\n        \"oddEven\": \"1\"\n      },\n      \"referenceTaskName\": \"perf_task_8\",\n      \"retryCount\": 0,\n      \"seq\": 7,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_8\",\n      \"scheduledTime\": 1547430593685,\n      \"startTime\": 1547430594976,\n      \"endTime\": 1547430595009,\n      \"updateTime\": 1547430613634,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"51020906-8fe0-4993-9020-66a081847bf3\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-075e5e67066be5d52\",\n      \"outputData\": {\n        \"mod\": \"5\",\n        \"oddEven\": \"1\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_8\",\n        \"taskReferenceName\": \"perf_task_8\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_7.output.mod\",\n          \"oddEven\": \"perf_task_7.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069391122,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_8\",\n          \"description\": \"perf_task_8\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 1291,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:49:54:994 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_8,1\",\n        \"01/14/19, 01:49:54:994 : failure probability is 0.017497659 against 0.0\",\n        \"01/14/19, 01:49:54:994 : Starting to execute perf_task_8, id=51020906-8fe0-4993-9020-66a081847bf3\",\n        \"01/14/19, 01:49:54:995 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"perf_task_9\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"5\",\n        \"oddEven\": \"1\"\n      },\n      \"referenceTaskName\": \"perf_task_9\",\n      \"retryCount\": 0,\n      \"seq\": 8,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_9\",\n      \"scheduledTime\": 1547430595069,\n      \"startTime\": 1547430596047,\n      \"endTime\": 1547430596081,\n      \"updateTime\": 1547430613642,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"c82cf62f-9f48-46c0-ae32-9bbfad57e71f\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-075e5e67066be5d52\",\n      \"outputData\": {\n        \"mod\": \"5\",\n        \"oddEven\": \"1\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_9\",\n        \"taskReferenceName\": \"perf_task_9\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_8.output.mod\",\n          \"oddEven\": \"perf_task_8.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069391291,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_9\",\n          \"description\": \"perf_task_9\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 978,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:49:56:065 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_9,1\",\n        \"01/14/19, 01:49:56:065 : Marking task completed\",\n        \"01/14/19, 01:49:56:065 : Starting to execute perf_task_9, id=c82cf62f-9f48-46c0-ae32-9bbfad57e71f\",\n        \"01/14/19, 01:49:56:065 : failure probability is 0.7340754 against 0.0\"\n      ]\n    },\n    {\n      \"taskType\": \"DECISION\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"hasChildren\": \"true\",\n        \"case\": \"5\"\n      },\n      \"referenceTaskName\": \"modDecision\",\n      \"retryCount\": 0,\n      \"seq\": 9,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 0,\n      \"taskDefName\": \"DECISION\",\n      \"scheduledTime\": 1547430596122,\n      \"startTime\": 1547430596133,\n      \"endTime\": 1547430596125,\n      \"updateTime\": 1547430613650,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"597b18b6-6d99-4356-b205-dbe532fc7983\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"caseOutput\": [\n          \"5\"\n        ]\n      },\n      \"workflowTask\": {\n        \"name\": \"modDecision\",\n        \"taskReferenceName\": \"modDecision\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_8.output.mod\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"mod\",\n        \"decisionCases\": {\n          \"0\": [\n            {\n              \"name\": \"perf_task_12\",\n              \"taskReferenceName\": \"perf_task_12\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_9.output.mod\",\n                \"oddEven\": \"perf_task_9.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069389427,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_12\",\n                \"description\": \"perf_task_12\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_13\",\n              \"taskReferenceName\": \"perf_task_13\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_12.output.mod\",\n                \"oddEven\": \"perf_task_12.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069389276,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_13\",\n                \"description\": \"perf_task_13\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"sub_workflow_x\",\n              \"taskReferenceName\": \"wf1\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_12.output.mod\",\n                \"oddEven\": \"perf_task_12.output.oddEven\"\n              },\n              \"type\": \"SUB_WORKFLOW\",\n              \"startDelay\": 0,\n              \"subWorkflowParam\": {\n                \"name\": \"sub_flow_1\",\n                \"version\": 1\n              },\n              \"optional\": false,\n              \"asyncComplete\": false\n            }\n          ],\n          \"1\": [\n            {\n              \"name\": \"perf_task_15\",\n              \"taskReferenceName\": \"perf_task_15\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_9.output.mod\",\n                \"oddEven\": \"perf_task_9.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069388963,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_15\",\n                \"description\": \"perf_task_15\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_16\",\n              \"taskReferenceName\": \"perf_task_16\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_15.output.mod\",\n                \"oddEven\": \"perf_task_15.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069389067,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_16\",\n                \"description\": \"perf_task_16\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"sub_workflow_x\",\n              \"taskReferenceName\": \"wf2\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_12.output.mod\",\n                \"oddEven\": \"perf_task_12.output.oddEven\"\n              },\n              \"type\": \"SUB_WORKFLOW\",\n              \"startDelay\": 0,\n              \"subWorkflowParam\": {\n                \"name\": \"sub_flow_1\",\n                \"version\": 1\n              },\n              \"optional\": false,\n              \"asyncComplete\": false\n            }\n          ],\n          \"4\": [\n            {\n              \"name\": \"perf_task_18\",\n              \"taskReferenceName\": \"perf_task_18\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_9.output.mod\",\n                \"oddEven\": \"perf_task_9.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069388904,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_18\",\n                \"description\": \"perf_task_18\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_19\",\n              \"taskReferenceName\": \"perf_task_19\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_18.output.mod\",\n                \"oddEven\": \"perf_task_18.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069389173,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_19\",\n                \"description\": \"perf_task_19\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            }\n          ],\n          \"5\": [\n            {\n              \"name\": \"perf_task_21\",\n              \"taskReferenceName\": \"perf_task_21\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_9.output.mod\",\n                \"oddEven\": \"perf_task_9.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390669,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_21\",\n                \"description\": \"perf_task_21\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"sub_workflow_x\",\n              \"taskReferenceName\": \"wf3\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_12.output.mod\",\n                \"oddEven\": \"perf_task_12.output.oddEven\"\n              },\n              \"type\": \"SUB_WORKFLOW\",\n              \"startDelay\": 0,\n              \"subWorkflowParam\": {\n                \"name\": \"sub_flow_1\",\n                \"version\": 1\n              },\n              \"optional\": false,\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_22\",\n              \"taskReferenceName\": \"perf_task_22\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_21.output.mod\",\n                \"oddEven\": \"perf_task_21.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069391345,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_22\",\n                \"description\": \"perf_task_22\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            }\n          ]\n        },\n        \"defaultCase\": [\n          {\n            \"name\": \"perf_task_24\",\n            \"taskReferenceName\": \"perf_task_24\",\n            \"inputParameters\": {\n              \"mod\": \"perf_task_9.output.mod\",\n              \"oddEven\": \"perf_task_9.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1547069391074,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"perf_task_24\",\n              \"description\": \"perf_task_24\",\n              \"retryCount\": 2,\n              \"timeoutSeconds\": 600,\n              \"timeoutPolicy\": \"TIME_OUT_WF\",\n              \"retryLogic\": \"FIXED\",\n              \"retryDelaySeconds\": 60,\n              \"responseTimeoutSeconds\": 300,\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1\n            },\n            \"asyncComplete\": false\n          },\n          {\n            \"name\": \"sub_workflow_x\",\n            \"taskReferenceName\": \"wf4\",\n            \"inputParameters\": {\n              \"mod\": \"perf_task_12.output.mod\",\n              \"oddEven\": \"perf_task_12.output.oddEven\"\n            },\n            \"type\": \"SUB_WORKFLOW\",\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": 1\n            },\n            \"optional\": false,\n            \"asyncComplete\": false\n          },\n          {\n            \"name\": \"perf_task_25\",\n            \"taskReferenceName\": \"perf_task_25\",\n            \"inputParameters\": {\n              \"mod\": \"perf_task_24.output.mod\",\n              \"oddEven\": \"perf_task_24.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1547069391177,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"perf_task_25\",\n              \"description\": \"perf_task_25\",\n              \"retryCount\": 2,\n              \"timeoutSeconds\": 600,\n              \"timeoutPolicy\": \"TIME_OUT_WF\",\n              \"retryLogic\": \"FIXED\",\n              \"retryDelaySeconds\": 60,\n              \"responseTimeoutSeconds\": 300,\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1\n            },\n            \"asyncComplete\": false\n          }\n        ],\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 11,\n      \"taskDefinition\": {\n        \"present\": false\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": []\n    },\n    {\n      \"taskType\": \"perf_task_21\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"5\",\n        \"oddEven\": \"1\"\n      },\n      \"referenceTaskName\": \"perf_task_21\",\n      \"retryCount\": 0,\n      \"seq\": 10,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_21\",\n      \"scheduledTime\": 1547430596128,\n      \"startTime\": 1547430597361,\n      \"endTime\": 1547430597400,\n      \"updateTime\": 1547430613663,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"f44f4598-7623-46db-a513-75000ccf39b8\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-075e5e67066be5d52\",\n      \"outputData\": {\n        \"mod\": \"2\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_21\",\n        \"taskReferenceName\": \"perf_task_21\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_9.output.mod\",\n          \"oddEven\": \"perf_task_9.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069390669,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_21\",\n          \"description\": \"perf_task_21\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 1233,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:49:57:378 : Starting to execute perf_task_21, id=f44f4598-7623-46db-a513-75000ccf39b8\",\n        \"01/14/19, 01:49:57:378 : failure probability is 0.88135785 against 0.0\",\n        \"01/14/19, 01:49:57:378 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_21,1\",\n        \"01/14/19, 01:49:57:383 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"workflowInput\": {},\n        \"subWorkflowId\": \"e18f09cb-9b3e-4296-bc77-87339d2eb34c\",\n        \"subWorkflowName\": \"sub_flow_1\",\n        \"subWorkflowVersion\": 1\n      },\n      \"referenceTaskName\": \"wf3\",\n      \"retryCount\": 0,\n      \"seq\": 11,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 0,\n      \"taskDefName\": \"sub_workflow_x\",\n      \"scheduledTime\": 1547430606665,\n      \"startTime\": 1547430597443,\n      \"endTime\": 1547430606672,\n      \"updateTime\": 1547430613674,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"37514448-8b14-4d5e-8483-0eabd89b73f6\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"subWorkflowId\": \"e18f09cb-9b3e-4296-bc77-87339d2eb34c\",\n        \"mod\": null,\n        \"oddEven\": null,\n        \"es2statuses\": []\n      },\n      \"workflowTask\": {\n        \"name\": \"sub_workflow_x\",\n        \"taskReferenceName\": \"wf3\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_12.output.mod\",\n          \"oddEven\": \"perf_task_12.output.oddEven\"\n        },\n        \"type\": \"SUB_WORKFLOW\",\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"sub_flow_1\",\n          \"version\": 1\n        },\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": -9222,\n      \"taskDefinition\": {\n        \"present\": false\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": []\n    },\n    {\n      \"taskType\": \"perf_task_22\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"2\",\n        \"oddEven\": \"0\"\n      },\n      \"referenceTaskName\": \"perf_task_22\",\n      \"retryCount\": 0,\n      \"seq\": 12,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_22\",\n      \"scheduledTime\": 1547430606701,\n      \"startTime\": 1547430607444,\n      \"endTime\": 1547430607481,\n      \"updateTime\": 1547430613684,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"f2448612-4960-4717-84f7-6686434733fe\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-075e5e67066be5d52\",\n      \"outputData\": {\n        \"mod\": \"2\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_22\",\n        \"taskReferenceName\": \"perf_task_22\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_21.output.mod\",\n          \"oddEven\": \"perf_task_21.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069391345,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_22\",\n          \"description\": \"perf_task_22\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 743,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:50:07:462 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_22,1\",\n        \"01/14/19, 01:50:07:462 : Marking task completed\",\n        \"01/14/19, 01:50:07:462 : Starting to execute perf_task_22, id=f2448612-4960-4717-84f7-6686434733fe\",\n        \"01/14/19, 01:50:07:462 : failure probability is 0.6165708 against 0.0\"\n      ]\n    },\n    {\n      \"taskType\": \"perf_task_28\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"9\",\n        \"oddEven\": \"1\"\n      },\n      \"referenceTaskName\": \"perf_task_28\",\n      \"retryCount\": 0,\n      \"seq\": 13,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_28\",\n      \"scheduledTime\": 1547430607541,\n      \"startTime\": 1547430608584,\n      \"endTime\": 1547430608631,\n      \"updateTime\": 1547430613694,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"f44c0a56-ae5b-4aba-ac69-c9f48ad6ecfc\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-0618a1a5e9526c9a1\",\n      \"outputData\": {\n        \"mod\": \"8\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_28\",\n        \"taskReferenceName\": \"perf_task_28\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_3.output.mod\",\n          \"oddEven\": \"perf_task_3.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069390042,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_28\",\n          \"description\": \"perf_task_28\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 1043,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:50:08:605 : Starting to execute perf_task_28, id=f44c0a56-ae5b-4aba-ac69-c9f48ad6ecfc\",\n        \"01/14/19, 01:50:08:605 : failure probability is 0.8953033 against 0.0\",\n        \"01/14/19, 01:50:08:605 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_28,1\",\n        \"01/14/19, 01:50:08:608 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"perf_task_29\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"8\",\n        \"oddEven\": \"0\"\n      },\n      \"referenceTaskName\": \"perf_task_29\",\n      \"retryCount\": 0,\n      \"seq\": 14,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_29\",\n      \"scheduledTime\": 1547430608681,\n      \"startTime\": 1547430611220,\n      \"endTime\": 1547430611262,\n      \"updateTime\": 1547430613702,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"ff3961e9-a7cf-454e-a5a5-31d9582fc3be\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-075e5e67066be5d52\",\n      \"outputData\": {\n        \"mod\": \"0\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_29\",\n        \"taskReferenceName\": \"perf_task_29\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_28.output.mod\",\n          \"oddEven\": \"perf_task_28.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069390098,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_29\",\n          \"description\": \"perf_task_29\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 2539,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:50:11:238 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_29,1\",\n        \"01/14/19, 01:50:11:238 : Starting to execute perf_task_29, id=ff3961e9-a7cf-454e-a5a5-31d9582fc3be\",\n        \"01/14/19, 01:50:11:238 : failure probability is 0.3055073 against 0.0\",\n        \"01/14/19, 01:50:11:240 : Marking task completed\"\n      ]\n    },\n    {\n      \"taskType\": \"perf_task_30\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"0\",\n        \"oddEven\": \"0\"\n      },\n      \"referenceTaskName\": \"perf_task_30\",\n      \"retryCount\": 0,\n      \"seq\": 15,\n      \"correlationId\": \"1547430586940\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_30\",\n      \"scheduledTime\": 1547430611308,\n      \"startTime\": 1547430613454,\n      \"endTime\": 1547430613496,\n      \"updateTime\": 1547430613712,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 300,\n      \"workflowInstanceId\": \"1be75865-00a1-4e2b-95c0-573c444d98d7\",\n      \"workflowType\": \"performance_test_1\",\n      \"taskId\": \"603a164f-3198-40ed-a5b6-7dd439349c25\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"cpeworkflowtests-devint-i-0618a1a5e9526c9a1\",\n      \"outputData\": {\n        \"mod\": \"6\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {},\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            },\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": false,\n            \"taskDefinition\": null,\n            \"rateLimited\": null\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_30\",\n        \"taskReferenceName\": \"perf_task_30\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_29.output.mod\",\n          \"oddEven\": \"perf_task_29.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069392094,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_30\",\n          \"description\": \"perf_task_30\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"queueWaitTime\": 2146,\n      \"taskDefinition\": {\n        \"present\": true\n      },\n      \"taskStatus\": \"COMPLETED\",\n      \"logs\": [\n        \"01/14/19, 01:50:13:473 : Starting to execute perf_task_30, id=603a164f-3198-40ed-a5b6-7dd439349c25\",\n        \"01/14/19, 01:50:13:473 : Attempt 1be75865-00a1-4e2b-95c0-573c444d98d7,perf_task_30,1\",\n        \"01/14/19, 01:50:13:473 : failure probability is 0.4859264 against 0.0\",\n        \"01/14/19, 01:50:13:476 : Marking task completed\"\n      ]\n    }\n  ],\n  \"input\": {\n    \"mod\": \"0\",\n    \"oddEven\": \"0\",\n    \"task2Name\": \"perf_task_10\"\n  },\n  \"output\": {\n    \"mod\": \"6\",\n    \"oddEven\": \"0\",\n    \"inputs\": {\n      \"subflow_0\": {\n        \"mod\": 4,\n        \"oddEven\": 0\n      },\n      \"subflow_4\": {\n        \"mod\": 4,\n        \"oddEven\": 0\n      },\n      \"subflow_2\": {\n        \"mod\": 4,\n        \"oddEven\": 0\n      }\n    },\n    \"dynamicTasks\": [\n      {\n        \"name\": null,\n        \"taskReferenceName\": \"subflow_0\",\n        \"description\": null,\n        \"inputParameters\": null,\n        \"type\": \"SUB_WORKFLOW\",\n        \"dynamicTaskNameParam\": null,\n        \"caseValueParam\": null,\n        \"caseExpression\": null,\n        \"decisionCases\": {},\n        \"dynamicForkJoinTasksParam\": null,\n        \"dynamicForkTasksParam\": null,\n        \"dynamicForkTasksInputParamName\": null,\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"sub_flow_1\",\n          \"version\": null\n        },\n        \"joinOn\": [],\n        \"sink\": null,\n        \"optional\": false,\n        \"taskDefinition\": null,\n        \"rateLimited\": null\n      },\n      {\n        \"name\": null,\n        \"taskReferenceName\": \"subflow_2\",\n        \"description\": null,\n        \"inputParameters\": null,\n        \"type\": \"SUB_WORKFLOW\",\n        \"dynamicTaskNameParam\": null,\n        \"caseValueParam\": null,\n        \"caseExpression\": null,\n        \"decisionCases\": {},\n        \"dynamicForkJoinTasksParam\": null,\n        \"dynamicForkTasksParam\": null,\n        \"dynamicForkTasksInputParamName\": null,\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"sub_flow_1\",\n          \"version\": null\n        },\n        \"joinOn\": [],\n        \"sink\": null,\n        \"optional\": false,\n        \"taskDefinition\": null,\n        \"rateLimited\": null\n      },\n      {\n        \"name\": null,\n        \"taskReferenceName\": \"subflow_4\",\n        \"description\": null,\n        \"inputParameters\": null,\n        \"type\": \"SUB_WORKFLOW\",\n        \"dynamicTaskNameParam\": null,\n        \"caseValueParam\": null,\n        \"caseExpression\": null,\n        \"decisionCases\": {},\n        \"dynamicForkJoinTasksParam\": null,\n        \"dynamicForkTasksParam\": null,\n        \"dynamicForkTasksInputParamName\": null,\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"sub_flow_1\",\n          \"version\": null\n        },\n        \"joinOn\": [],\n        \"sink\": null,\n        \"optional\": false,\n        \"taskDefinition\": null,\n        \"rateLimited\": null\n      }\n    ],\n    \"attempt\": 1\n  },\n  \"workflowType\": \"performance_test_1\",\n  \"version\": 1,\n  \"correlationId\": \"1547430586940\",\n  \"schemaVersion\": 1,\n  \"workflowDefinition\": {\n    \"createTime\": 1477681181098,\n    \"updateTime\": 1484162039528,\n    \"name\": \"performance_test_1\",\n    \"description\": \"performance_test_1\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"perf_task_1\",\n        \"taskReferenceName\": \"perf_task_1\",\n        \"inputParameters\": {\n          \"mod\": \"workflow.input.mod\",\n          \"oddEven\": \"workflow.input.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069389709,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_1\",\n          \"description\": \"perf_task_1\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"perf_task_10\",\n        \"taskReferenceName\": \"perf_task_2\",\n        \"inputParameters\": {\n          \"taskToExecute\": \"workflow.input.task2Name\"\n        },\n        \"type\": \"DYNAMIC\",\n        \"dynamicTaskNameParam\": \"taskToExecute\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069389226,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_10\",\n          \"description\": \"perf_task_10\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"perf_task_3\",\n        \"taskReferenceName\": \"perf_task_3\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_2.output.mod\",\n          \"oddEven\": \"perf_task_2.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069389814,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_3\",\n          \"description\": \"perf_task_3\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"get_from_es\",\n        \"taskReferenceName\": \"get_es_1\",\n        \"type\": \"HTTP\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"oddEvenDecision\",\n        \"taskReferenceName\": \"oddEvenDecision\",\n        \"inputParameters\": {\n          \"oddEven\": \"perf_task_3.output.oddEven\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"oddEven\",\n        \"decisionCases\": {\n          \"0\": [\n            {\n              \"name\": \"perf_task_4\",\n              \"taskReferenceName\": \"perf_task_4\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_3.output.mod\",\n                \"oddEven\": \"perf_task_3.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390494,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_4\",\n                \"description\": \"perf_task_4\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"dynamic_fanout\",\n              \"taskReferenceName\": \"fanout1\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"perf_task_4.output.dynamicTasks\",\n                \"input\": \"perf_task_4.output.inputs\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"input\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"dynamic_join\",\n              \"taskReferenceName\": \"join1\",\n              \"type\": \"JOIN\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_5\",\n              \"taskReferenceName\": \"perf_task_5\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_4.output.mod\",\n                \"oddEven\": \"perf_task_4.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390611,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_5\",\n                \"description\": \"perf_task_5\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_6\",\n              \"taskReferenceName\": \"perf_task_6\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_5.output.mod\",\n                \"oddEven\": \"perf_task_5.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390789,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_6\",\n                \"description\": \"perf_task_6\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            }\n          ],\n          \"1\": [\n            {\n              \"name\": \"perf_task_7\",\n              \"taskReferenceName\": \"perf_task_7\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_3.output.mod\",\n                \"oddEven\": \"perf_task_3.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069390955,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_7\",\n                \"description\": \"perf_task_7\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_8\",\n              \"taskReferenceName\": \"perf_task_8\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_7.output.mod\",\n                \"oddEven\": \"perf_task_7.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069391122,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_8\",\n                \"description\": \"perf_task_8\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"perf_task_9\",\n              \"taskReferenceName\": \"perf_task_9\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_8.output.mod\",\n                \"oddEven\": \"perf_task_8.output.oddEven\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1547069391291,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"perf_task_9\",\n                \"description\": \"perf_task_9\",\n                \"retryCount\": 2,\n                \"timeoutSeconds\": 600,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1\n              },\n              \"asyncComplete\": false\n            },\n            {\n              \"name\": \"modDecision\",\n              \"taskReferenceName\": \"modDecision\",\n              \"inputParameters\": {\n                \"mod\": \"perf_task_8.output.mod\"\n              },\n              \"type\": \"DECISION\",\n              \"caseValueParam\": \"mod\",\n              \"decisionCases\": {\n                \"0\": [\n                  {\n                    \"name\": \"perf_task_12\",\n                    \"taskReferenceName\": \"perf_task_12\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389427,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_12\",\n                      \"description\": \"perf_task_12\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_13\",\n                    \"taskReferenceName\": \"perf_task_13\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389276,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_13\",\n                      \"description\": \"perf_task_13\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf1\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    },\n                    \"optional\": false,\n                    \"asyncComplete\": false\n                  }\n                ],\n                \"1\": [\n                  {\n                    \"name\": \"perf_task_15\",\n                    \"taskReferenceName\": \"perf_task_15\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069388963,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_15\",\n                      \"description\": \"perf_task_15\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_16\",\n                    \"taskReferenceName\": \"perf_task_16\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_15.output.mod\",\n                      \"oddEven\": \"perf_task_15.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389067,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_16\",\n                      \"description\": \"perf_task_16\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf2\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    },\n                    \"optional\": false,\n                    \"asyncComplete\": false\n                  }\n                ],\n                \"4\": [\n                  {\n                    \"name\": \"perf_task_18\",\n                    \"taskReferenceName\": \"perf_task_18\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069388904,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_18\",\n                      \"description\": \"perf_task_18\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_19\",\n                    \"taskReferenceName\": \"perf_task_19\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_18.output.mod\",\n                      \"oddEven\": \"perf_task_18.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069389173,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_19\",\n                      \"description\": \"perf_task_19\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  }\n                ],\n                \"5\": [\n                  {\n                    \"name\": \"perf_task_21\",\n                    \"taskReferenceName\": \"perf_task_21\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_9.output.mod\",\n                      \"oddEven\": \"perf_task_9.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069390669,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_21\",\n                      \"description\": \"perf_task_21\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf3\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_12.output.mod\",\n                      \"oddEven\": \"perf_task_12.output.oddEven\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    },\n                    \"optional\": false,\n                    \"asyncComplete\": false\n                  },\n                  {\n                    \"name\": \"perf_task_22\",\n                    \"taskReferenceName\": \"perf_task_22\",\n                    \"inputParameters\": {\n                      \"mod\": \"perf_task_21.output.mod\",\n                      \"oddEven\": \"perf_task_21.output.oddEven\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"optional\": false,\n                    \"taskDefinition\": {\n                      \"createTime\": 1547069391345,\n                      \"createdBy\": \"CPEWORKFLOW\",\n                      \"name\": \"perf_task_22\",\n                      \"description\": \"perf_task_22\",\n                      \"retryCount\": 2,\n                      \"timeoutSeconds\": 600,\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 300,\n                      \"rateLimitPerFrequency\": 0,\n                      \"rateLimitFrequencyInSeconds\": 1\n                    },\n                    \"asyncComplete\": false\n                  }\n                ]\n              },\n              \"defaultCase\": [\n                {\n                  \"name\": \"perf_task_24\",\n                  \"taskReferenceName\": \"perf_task_24\",\n                  \"inputParameters\": {\n                    \"mod\": \"perf_task_9.output.mod\",\n                    \"oddEven\": \"perf_task_9.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1547069391074,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"perf_task_24\",\n                    \"description\": \"perf_task_24\",\n                    \"retryCount\": 2,\n                    \"timeoutSeconds\": 600,\n                    \"timeoutPolicy\": \"TIME_OUT_WF\",\n                    \"retryLogic\": \"FIXED\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 300,\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1\n                  },\n                  \"asyncComplete\": false\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf4\",\n                  \"inputParameters\": {\n                    \"mod\": \"perf_task_12.output.mod\",\n                    \"oddEven\": \"perf_task_12.output.oddEven\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"startDelay\": 0,\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  },\n                  \"optional\": false,\n                  \"asyncComplete\": false\n                },\n                {\n                  \"name\": \"perf_task_25\",\n                  \"taskReferenceName\": \"perf_task_25\",\n                  \"inputParameters\": {\n                    \"mod\": \"perf_task_24.output.mod\",\n                    \"oddEven\": \"perf_task_24.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1547069391177,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"perf_task_25\",\n                    \"description\": \"perf_task_25\",\n                    \"retryCount\": 2,\n                    \"timeoutSeconds\": 600,\n                    \"timeoutPolicy\": \"TIME_OUT_WF\",\n                    \"retryLogic\": \"FIXED\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 300,\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1\n                  },\n                  \"asyncComplete\": false\n                }\n              ],\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            }\n          ]\n        },\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"perf_task_28\",\n        \"taskReferenceName\": \"perf_task_28\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_3.output.mod\",\n          \"oddEven\": \"perf_task_3.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069390042,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_28\",\n          \"description\": \"perf_task_28\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"perf_task_29\",\n        \"taskReferenceName\": \"perf_task_29\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_28.output.mod\",\n          \"oddEven\": \"perf_task_28.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069390098,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_29\",\n          \"description\": \"perf_task_29\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"perf_task_30\",\n        \"taskReferenceName\": \"perf_task_30\",\n        \"inputParameters\": {\n          \"mod\": \"perf_task_29.output.mod\",\n          \"oddEven\": \"perf_task_29.output.oddEven\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1547069392094,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"perf_task_30\",\n          \"description\": \"perf_task_30\",\n          \"retryCount\": 2,\n          \"timeoutSeconds\": 600,\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1\n        },\n        \"asyncComplete\": false\n      }\n    ],\n    \"schemaVersion\": 1,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": false\n  },\n  \"priority\": 0,\n  \"workflowName\": \"performance_test_1\",\n  \"workflowVersion\": 1,\n  \"startTime\": 1547430586952\n}"
  },
  {
    "path": "core/src/test/resources/conditional_flow.json",
    "content": "{\n    \"name\": \"ConditionalTaskWF\",\n    \"description\": \"ConditionalTaskWF\",\n    \"version\": 1,\n    \"tasks\": [{\n        \"name\": \"conditional\",\n        \"taskReferenceName\": \"conditional\",\n        \"inputParameters\": {\n            \"case\": \"${workflow.input.param1}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"case\",\n        \"decisionCases\": {\n            \"nested\": [{\n                \"name\": \"conditional2\",\n                \"taskReferenceName\": \"conditional2\",\n                \"inputParameters\": {\n                    \"case\": \"${workflow.input.param2}\"\n                },\n                \"type\": \"DECISION\",\n                \"caseValueParam\": \"case\",\n                \"decisionCases\": {\n                    \"one\": [{\n                        \"name\": \"junit_task_1\",\n                        \"taskReferenceName\": \"t1\",\n                        \"inputParameters\": {\n                            \"p1\": \"${workflow.input.param1}\",\n                            \"p2\": \"${workflow.input.param2}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"startDelay\": 0,\n                        \"taskDefinition\": {\n                            \"ownerApp\": null,\n                            \"createTime\": null,\n                            \"updateTime\": null,\n                            \"createdBy\": null,\n                            \"updatedBy\": null,\n                            \"name\": \"junit_task_1\",\n                            \"description\": \"junit_task_1\",\n                            \"retryCount\": 1,\n                            \"timeoutSeconds\": 0,\n                            \"inputKeys\": [],\n                            \"outputKeys\": [],\n                            \"timeoutPolicy\": \"TIME_OUT_WF\",\n                            \"retryLogic\": \"FIXED\",\n                            \"retryDelaySeconds\": 60,\n                            \"responseTimeoutSeconds\": 3600,\n                            \"concurrentExecLimit\": null,\n                            \"inputTemplate\": {}\n                        }\n                    },\n                        {\n                            \"name\": \"junit_task_3\",\n                            \"taskReferenceName\": \"t3\",\n                            \"type\": \"SIMPLE\",\n                            \"startDelay\": 0,\n                            \"taskDefinition\": {\n                                \"ownerApp\": null,\n                                \"createTime\": null,\n                                \"updateTime\": null,\n                                \"createdBy\": null,\n                                \"updatedBy\": null,\n                                \"name\": \"junit_task_3\",\n                                \"description\": \"junit_task_3\",\n                                \"retryCount\": 1,\n                                \"timeoutSeconds\": 0,\n                                \"inputKeys\": [],\n                                \"outputKeys\": [],\n                                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                                \"retryLogic\": \"FIXED\",\n                                \"retryDelaySeconds\": 60,\n                                \"responseTimeoutSeconds\": 3600,\n                                \"concurrentExecLimit\": null,\n                                \"inputTemplate\": {}\n                            }\n                        }\n                    ],\n                    \"two\": [{\n                        \"name\": \"junit_task_2\",\n                        \"taskReferenceName\": \"t2\",\n                        \"inputParameters\": {\n                            \"tp1\": \"${workflow.input.param1}\",\n                            \"tp3\": \"${workflow.input.param2}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"startDelay\": 0,\n                        \"taskDefinition\": {\n                            \"ownerApp\": null,\n                            \"createTime\": null,\n                            \"updateTime\": null,\n                            \"createdBy\": null,\n                            \"updatedBy\": null,\n                            \"name\": \"junit_task_2\",\n                            \"description\": \"junit_task_2\",\n                            \"retryCount\": 1,\n                            \"timeoutSeconds\": 0,\n                            \"inputKeys\": [],\n                            \"outputKeys\": [],\n                            \"timeoutPolicy\": \"TIME_OUT_WF\",\n                            \"retryLogic\": \"FIXED\",\n                            \"retryDelaySeconds\": 60,\n                            \"responseTimeoutSeconds\": 3600,\n                            \"concurrentExecLimit\": null,\n                            \"inputTemplate\": {}\n                        }\n                    }]\n                },\n                \"startDelay\": 0\n            }],\n            \"three\": [{\n                \"name\": \"junit_task_3\",\n                \"taskReferenceName\": \"t31\",\n                \"type\": \"SIMPLE\",\n                \"startDelay\": 0,\n                \"taskDefinition\": {\n                    \"ownerApp\": null,\n                    \"createTime\": null,\n                    \"updateTime\": null,\n                    \"createdBy\": null,\n                    \"updatedBy\": null,\n                    \"name\": \"junit_task_3\",\n                    \"description\": \"junit_task_3\",\n                    \"retryCount\": 1,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [],\n                    \"outputKeys\": [],\n                    \"timeoutPolicy\": \"TIME_OUT_WF\",\n                    \"retryLogic\": \"FIXED\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 3600,\n                    \"concurrentExecLimit\": null,\n                    \"inputTemplate\": {}\n                }\n            }]\n        },\n        \"defaultCase\": [{\n            \"name\": \"junit_task_2\",\n            \"taskReferenceName\": \"t21\",\n            \"inputParameters\": {\n                \"tp1\": \"${workflow.input.param1}\",\n                \"tp3\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"taskDefinition\": {\n                \"ownerApp\": null,\n                \"createTime\": null,\n                \"updateTime\": null,\n                \"createdBy\": null,\n                \"updatedBy\": null,\n                \"name\": \"junit_task_2\",\n                \"description\": \"junit_task_2\",\n                \"retryCount\": 1,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [],\n                \"outputKeys\": [],\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 3600,\n                \"concurrentExecLimit\": null,\n                \"inputTemplate\": {}\n            }\n        }],\n        \"startDelay\": 0\n    },\n        {\n            \"name\": \"finalcondition\",\n            \"taskReferenceName\": \"tf\",\n            \"inputParameters\": {\n                \"finalCase\": \"{workflow.input.finalCase}\"\n            },\n            \"type\": \"DECISION\",\n            \"caseValueParam\": \"finalCase\",\n            \"decisionCases\": {\n                \"notify\": [{\n                    \"name\": \"junit_task_4\",\n                    \"taskReferenceName\": \"junit_task_4\",\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0,\n                    \"taskDefinition\": {\n                        \"ownerApp\": null,\n                        \"createTime\": null,\n                        \"updateTime\": null,\n                        \"createdBy\": null,\n                        \"updatedBy\": null,\n                        \"name\": \"junit_task_4\",\n                        \"description\": \"junit_task_4\",\n                        \"retryCount\": 1,\n                        \"timeoutSeconds\": 0,\n                        \"inputKeys\": [],\n                        \"outputKeys\": [],\n                        \"timeoutPolicy\": \"TIME_OUT_WF\",\n                        \"retryLogic\": \"FIXED\",\n                        \"retryDelaySeconds\": 60,\n                        \"responseTimeoutSeconds\": 3600,\n                        \"concurrentExecLimit\": null,\n                        \"inputTemplate\": {}\n                    }\n                }]\n            },\n            \"startDelay\": 0\n        }\n    ],\n    \"inputParameters\": [\n        \"param1\",\n        \"param2\"\n    ],\n    \"schemaVersion\": 2,\n    \"ownerEmail\": \"unit@test.com\"\n}\n"
  },
  {
    "path": "core/src/test/resources/conditional_flow_with_switch.json",
    "content": "{\n   \"name\": \"ConditionalTaskWF\",\n   \"description\": \"ConditionalTaskWF\",\n   \"version\": 1,\n   \"tasks\": [\n      {\n         \"name\": \"conditional\",\n         \"taskReferenceName\": \"conditional\",\n         \"inputParameters\": {\n            \"case\": \"${workflow.input.param1}\"\n         },\n         \"type\": \"SWITCH\",\n         \"evaluatorType\": \"value-param\",\n         \"expression\": \"case\",\n         \"decisionCases\": {\n            \"nested\": [\n               {\n                  \"name\": \"conditional2\",\n                  \"taskReferenceName\": \"conditional2\",\n                  \"inputParameters\": {\n                     \"case\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SWITCH\",\n                  \"evaluatorType\": \"javascript\",\n                  \"expression\": \"$.case == 'one' ? 'one' : ($.case == 'two' ? 'two' : ($.case == 'three' ? 'three' : 'other'))\",\n                  \"decisionCases\": {\n                     \"one\": [\n                        {\n                           \"name\": \"junit_task_1\",\n                           \"taskReferenceName\": \"t1\",\n                           \"inputParameters\": {\n                              \"p1\": \"${workflow.input.param1}\",\n                              \"p2\": \"${workflow.input.param2}\"\n                           },\n                           \"type\": \"SIMPLE\",\n                           \"startDelay\": 0,\n                           \"taskDefinition\": {\n                              \"ownerApp\": null,\n                              \"createTime\": null,\n                              \"updateTime\": null,\n                              \"createdBy\": null,\n                              \"updatedBy\": null,\n                              \"name\": \"junit_task_1\",\n                              \"description\": \"junit_task_1\",\n                              \"retryCount\": 1,\n                              \"timeoutSeconds\": 0,\n                              \"inputKeys\": [],\n                              \"outputKeys\": [],\n                              \"timeoutPolicy\": \"TIME_OUT_WF\",\n                              \"retryLogic\": \"FIXED\",\n                              \"retryDelaySeconds\": 60,\n                              \"responseTimeoutSeconds\": 3600,\n                              \"concurrentExecLimit\": null,\n                              \"inputTemplate\": {}\n                           }\n                        },\n                        {\n                           \"name\": \"junit_task_3\",\n                           \"taskReferenceName\": \"t3\",\n                           \"type\": \"SIMPLE\",\n                           \"startDelay\": 0,\n                           \"taskDefinition\": {\n                              \"ownerApp\": null,\n                              \"createTime\": null,\n                              \"updateTime\": null,\n                              \"createdBy\": null,\n                              \"updatedBy\": null,\n                              \"name\": \"junit_task_3\",\n                              \"description\": \"junit_task_3\",\n                              \"retryCount\": 1,\n                              \"timeoutSeconds\": 0,\n                              \"inputKeys\": [],\n                              \"outputKeys\": [],\n                              \"timeoutPolicy\": \"TIME_OUT_WF\",\n                              \"retryLogic\": \"FIXED\",\n                              \"retryDelaySeconds\": 60,\n                              \"responseTimeoutSeconds\": 3600,\n                              \"concurrentExecLimit\": null,\n                              \"inputTemplate\": {}\n                           }\n                        }\n                     ],\n                     \"two\": [\n                        {\n                           \"name\": \"junit_task_2\",\n                           \"taskReferenceName\": \"t2\",\n                           \"inputParameters\": {\n                              \"tp1\": \"${workflow.input.param1}\",\n                              \"tp3\": \"${workflow.input.param2}\"\n                           },\n                           \"type\": \"SIMPLE\",\n                           \"startDelay\": 0,\n                           \"taskDefinition\": {\n                              \"ownerApp\": null,\n                              \"createTime\": null,\n                              \"updateTime\": null,\n                              \"createdBy\": null,\n                              \"updatedBy\": null,\n                              \"name\": \"junit_task_2\",\n                              \"description\": \"junit_task_2\",\n                              \"retryCount\": 1,\n                              \"timeoutSeconds\": 0,\n                              \"inputKeys\": [],\n                              \"outputKeys\": [],\n                              \"timeoutPolicy\": \"TIME_OUT_WF\",\n                              \"retryLogic\": \"FIXED\",\n                              \"retryDelaySeconds\": 60,\n                              \"responseTimeoutSeconds\": 3600,\n                              \"concurrentExecLimit\": null,\n                              \"inputTemplate\": {}\n                           }\n                        }\n                     ]\n                  },\n                  \"startDelay\": 0\n               }\n            ],\n            \"three\": [\n               {\n                  \"name\": \"junit_task_3\",\n                  \"taskReferenceName\": \"t31\",\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"taskDefinition\": {\n                     \"ownerApp\": null,\n                     \"createTime\": null,\n                     \"updateTime\": null,\n                     \"createdBy\": null,\n                     \"updatedBy\": null,\n                     \"name\": \"junit_task_3\",\n                     \"description\": \"junit_task_3\",\n                     \"retryCount\": 1,\n                     \"timeoutSeconds\": 0,\n                     \"inputKeys\": [],\n                     \"outputKeys\": [],\n                     \"timeoutPolicy\": \"TIME_OUT_WF\",\n                     \"retryLogic\": \"FIXED\",\n                     \"retryDelaySeconds\": 60,\n                     \"responseTimeoutSeconds\": 3600,\n                     \"concurrentExecLimit\": null,\n                     \"inputTemplate\": {}\n                  }\n               }\n            ]\n         },\n         \"defaultCase\": [\n            {\n               \"name\": \"junit_task_2\",\n               \"taskReferenceName\": \"t21\",\n               \"inputParameters\": {\n                  \"tp1\": \"${workflow.input.param1}\",\n                  \"tp3\": \"${workflow.input.param2}\"\n               },\n               \"type\": \"SIMPLE\",\n               \"startDelay\": 0,\n               \"taskDefinition\": {\n                  \"ownerApp\": null,\n                  \"createTime\": null,\n                  \"updateTime\": null,\n                  \"createdBy\": null,\n                  \"updatedBy\": null,\n                  \"name\": \"junit_task_2\",\n                  \"description\": \"junit_task_2\",\n                  \"retryCount\": 1,\n                  \"timeoutSeconds\": 0,\n                  \"inputKeys\": [],\n                  \"outputKeys\": [],\n                  \"timeoutPolicy\": \"TIME_OUT_WF\",\n                  \"retryLogic\": \"FIXED\",\n                  \"retryDelaySeconds\": 60,\n                  \"responseTimeoutSeconds\": 3600,\n                  \"concurrentExecLimit\": null,\n                  \"inputTemplate\": {}\n               }\n            }\n         ],\n         \"startDelay\": 0\n      },\n      {\n         \"name\": \"finalcondition\",\n         \"taskReferenceName\": \"tf\",\n         \"inputParameters\": {\n            \"finalCase\": \"{workflow.input.finalCase}\"\n         },\n         \"type\": \"SWITCH\",\n         \"evaluatorType\": \"value-param\",\n         \"expression\": \"finalCase\",\n         \"decisionCases\": {\n            \"notify\": [\n               {\n                  \"name\": \"junit_task_4\",\n                  \"taskReferenceName\": \"junit_task_4\",\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"taskDefinition\": {\n                     \"ownerApp\": null,\n                     \"createTime\": null,\n                     \"updateTime\": null,\n                     \"createdBy\": null,\n                     \"updatedBy\": null,\n                     \"name\": \"junit_task_4\",\n                     \"description\": \"junit_task_4\",\n                     \"retryCount\": 1,\n                     \"timeoutSeconds\": 0,\n                     \"inputKeys\": [],\n                     \"outputKeys\": [],\n                     \"timeoutPolicy\": \"TIME_OUT_WF\",\n                     \"retryLogic\": \"FIXED\",\n                     \"retryDelaySeconds\": 60,\n                     \"responseTimeoutSeconds\": 3600,\n                     \"concurrentExecLimit\": null,\n                     \"inputTemplate\": {}\n                  }\n               }\n            ]\n         },\n         \"startDelay\": 0\n      }\n   ],\n   \"inputParameters\": [\n      \"param1\",\n      \"param2\"\n   ],\n   \"schemaVersion\": 2,\n   \"ownerEmail\": \"unit@test.com\"\n}\n"
  },
  {
    "path": "core/src/test/resources/payload.json",
    "content": "{\n  \"imageType\": \"TEST_SAMPLE\",\n  \"filteredSourceList\": {\n    \"TEST_SAMPLE\": [\n      {\n        \"sourceId\": \"1413900_10830\",\n        \"url\": \"file/location/a0bdc4d0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_50241\",\n        \"url\": \"file/location/cd4e00a0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-55ee8663-85c2-42d3-aca2-4076707e6d4e\",\n        \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n      },\n      {\n        \"sourceId\": \"generated-14056154-1544-4350-81db-b3751fe44777\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-0b0ae5ea-d5c5-410c-adc9-bf16d2909c2e\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-08869779-614d-417c-bfea-36a3f8f199da\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-e117db45-1c48-45d0-b751-89386eb2d81d\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f0221421-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/4a009209-002f-4b58-8b96-cb2198f8ba3c\"\n      },\n      {\n        \"sourceId\": \"f0252161-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/55b56298-5e7a-4949-b919-88c5c9557e8e\"\n      },\n      {\n        \"sourceId\": \"f038d070-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/3c4804f4-e826-436f-90c9-52b8d9266d52\"\n      },\n      {\n        \"sourceId\": \"f04e0621-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/689283a1-1816-48ef-83da-7f9ac874bf45\"\n      },\n      {\n        \"sourceId\": \"f04ddf10-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/586666ae-7321-445a-80b6-323c8c241ecd\"\n      },\n      {\n        \"sourceId\": \"f05950c0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/31795cc4-2590-4b20-a617-deaa18301f99\"\n      },\n      {\n        \"sourceId\": \"1413900_46819\",\n        \"url\": \"file/location/c74497a0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_11177\",\n        \"url\": \"file/location/a231c730-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_48713\",\n        \"url\": \"file/location/ca638ae0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_48525\",\n        \"url\": \"file/location/ca0c9140-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_73303\",\n        \"url\": \"file/location/d5943a40-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_55202\",\n        \"url\": \"file/location/d1a4d7a0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-61413adf-3c10-4484-b25d-e238df898f45\",\n        \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n      },\n      {\n        \"sourceId\": \"generated-addca397-f050-4339-ae86-9ba8c4e1b0d5\",\n        \"url\": \"file/sample/location/838a0ddb-a315-453a-8b8a-fa795f9d7691\"\n      },\n      {\n        \"sourceId\": \"generated-e4de9810-0f69-4593-8926-01ed82cbebcb\",\n        \"url\": \"file/sample/location/838a0ddb-a315-453a-8b8a-fa795f9d7691\"\n      },\n      {\n        \"sourceId\": \"generated-e16e2074-7af6-4700-ab05-ca41ba9c9ab4\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-341c86f8-57a5-40e1-8842-3eb41dd9f528\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-88c2ea9b-cef7-4120-8043-b92713d8fade\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-3f6a731f-3c92-4677-9923-f80b8a6be632\",\n        \"url\": \"file/sample/location/3881aea9-a731-4e22-9ead-2d6eccc51140\"\n      },\n      {\n        \"sourceId\": \"generated-1508b871-64de-47ce-8b07-76c5cb3f3e1e\",\n        \"url\": \"file/sample/location/a2e4195f-3900-45b4-9335-45f85fca6467\"\n      },\n      {\n        \"sourceId\": \"generated-1406dce8-7b9c-4956-a7e8-78721c476ce9\",\n        \"url\": \"file/sample/location/a2e4195f-3900-45b4-9335-45f85fca6467\"\n      },\n      {\n        \"sourceId\": \"f0206671-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/35ebee36-3072-44c5-abb5-702a5a3b1a91\"\n      },\n      {\n        \"sourceId\": \"f01f5501-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d3a9133d-c681-4910-a769-8195526ae634\"\n      },\n      {\n        \"sourceId\": \"f022b060-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/8fc1413d-170e-4644-a554-5e0c596b225c\"\n      },\n      {\n        \"sourceId\": \"f02fa8b1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/35bed0a2-7def-457b-bded-4f4d7d94f76e\"\n      },\n      {\n        \"sourceId\": \"f031f2a0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/a5a2ea1f-8d13-429c-a44d-3057d21f608a\"\n      },\n      {\n        \"sourceId\": \"f0424650-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/1c599ffc-4f10-4c0b-8d9a-ae41c7256113\"\n      },\n      {\n        \"sourceId\": \"f04ec970-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/8404a421-e1a6-41cf-af63-a35ccb474457\"\n      },\n      {\n        \"sourceId\": \"1413900_47197\",\n        \"url\": \"file/location/c81b6fa0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-2a63c0c8-62ea-44a4-a33b-f0b3047e8b00\",\n        \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n      },\n      {\n        \"sourceId\": \"generated-b27face7-3589-4209-944a-5153b20c5996\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-144675b3-9321-48d2-8b5b-e19a40d30ef2\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-8cbe821e-b1fb-48ce-beb5-735319af4db6\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-ecc4ea47-9bad-4b91-97c7-35f4ea6fb479\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-c1eb9ed0-8560-4e09-a748-f926edb7cdc2\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-6bed81fd-c777-4c61-8da1-0bb7f7cf0082\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-852e5510-dd5d-4900-a614-854148fcc716\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-f4dedcb7-37c9-4ba9-ab37-64ec9be7c882\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f0259691-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/721bc0de-e75f-4386-8b2e-ca84eb653596\"\n      },\n      {\n        \"sourceId\": \"f02b3be1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d2043b17-8ce5-42ee-a5e4-81c68f0c4838\"\n      },\n      {\n        \"sourceId\": \"f02b62f0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/63931561-3b5b-4ffe-af47-da2c9de94684\"\n      },\n      {\n        \"sourceId\": \"f0315660-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d99ed629-2885-4e4a-8a1b-22e487b875fa\"\n      },\n      {\n        \"sourceId\": \"f0306c00-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/6f8e673a-7003-44aa-96b9-e2ed8a4654ff\"\n      },\n      {\n        \"sourceId\": \"f033c760-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/627c00f9-14b3-4057-b6e2-0f962ad0308e\"\n      },\n      {\n        \"sourceId\": \"f03526f1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/fafabaf9-fe58-4a9a-b555-026521aeb2fe\"\n      },\n      {\n        \"sourceId\": \"f03acc41-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/6c9fed2c-558a-4db3-8360-659b5e8c46e4\"\n      },\n      {\n        \"sourceId\": \"f0463df1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/e9fb83d2-5f14-4442-92b5-67e613f2e35f\"\n      },\n      {\n        \"sourceId\": \"f04fb3d0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/e7a0f82f-be8d-4ada-a4b1-13e8165e08be\"\n      },\n      {\n        \"sourceId\": \"f05272f0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/9aba488a-22b3-4932-85a7-52c461203541\"\n      },\n      {\n        \"sourceId\": \"f0581841-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/457415f6-6d0c-4304-8533-0d5b43fac564\"\n      },\n      {\n        \"sourceId\": \"generated-8fefb48c-6fde-4fd6-8f33-a1f3f3b62105\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-30c61aa5-f5bd-4077-8c32-336b87acbe96\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-d5da37db-d486-46d4-8f7d-1e0710a77eb5\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-77af26fe-9e22-48af-99e3-f63f10fbe6de\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-2e807016-3d11-4b60-bec7-c380a608b67d\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-615d02e9-62c2-43ab-9df7-753b6b8e2c22\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-3e1600fd-a626-4ee6-972b-5f0187e96c38\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"generated-1dcb208c-6a58-4334-a60c-6fb54c8a2af5\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f024ac30-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/0af2107b-4231-4d23-bef3-4e417ac6c5d3\"\n      },\n      {\n        \"sourceId\": \"f0282ea1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/0f592681-fd23-4194-ae43-42f61c664485\"\n      },\n      {\n        \"sourceId\": \"f02c4d50-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/ec46b9a3-99af-410a-af7d-726f8854909f\"\n      },\n      {\n        \"sourceId\": \"f02b8a00-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/aed7e5da-b524-4d41-b264-28ce615ec826\"\n      },\n      {\n        \"sourceId\": \"f02b14d1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/b88c9055-ab0d-4d27-a405-265ba2a15f0c\"\n      },\n      {\n        \"sourceId\": \"f03044f1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/fb8c4df9-d59e-4ac3-880e-4ea94cd880a4\"\n      },\n      {\n        \"sourceId\": \"f034ffe1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/59f3fbe8-b300-4861-9b2f-dac7b15aea7d\"\n      },\n      {\n        \"sourceId\": \"f03c2bd0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/19a06d54-41ed-419d-9947-f10cd5f0d85c\"\n      },\n      {\n        \"sourceId\": \"f03fae41-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/a9a48a62-7d62-4f67-b281-cc6fdc1e722c\"\n      },\n      {\n        \"sourceId\": \"f0455390-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/0aeffc0a-a5ad-46ff-abab-1b3bc6a5840a\"\n      },\n      {\n        \"sourceId\": \"f04b1ff1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/9a08aaed-c125-48f7-9d1d-fd11266c2b12\"\n      },\n      {\n        \"sourceId\": \"f04cf4b1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/17a6e0f9-aa64-411f-9af7-837c84f7443f\"\n      },\n      {\n        \"sourceId\": \"f0511360-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/fb633c73-cb33-4806-bc08-049024644856\"\n      },\n      {\n        \"sourceId\": \"f0538460-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/a7012248-6769-42da-a6c8-d4b831f6efce\"\n      },\n      {\n        \"sourceId\": \"f058db91-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/bcf71522-6168-48c4-86c9-995bca60ae51\"\n      },\n      {\n        \"sourceId\": \"generated-adf005c4-95c1-4904-9968-09cc19a26bfe\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-c4d367a4-4cdc-412e-af79-09b227f2e3ba\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-48dba018-f884-49db-b87e-67274e244c8f\",\n        \"url\": \"file/sample/location/4bce4154-fb4b-4f0a-887d-a0cd12d4d214\"\n      },\n      {\n        \"sourceId\": \"generated-26700b83-4892-420e-8b46-1ee21eba75fb\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-632f3198-c0dc-4348-974f-51684d4e443e\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"generated-86e2dd1d-1aa4-4dbe-b37b-b488f5dd1c70\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f04134e0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/ff8f59bf-7757-4d51-a7e4-619f3e8ffaf2\"\n      },\n      {\n        \"sourceId\": \"f04f65b0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d66467d1-3ac6-4041-8d15-e722ee07231f\"\n      },\n      {\n        \"sourceId\": \"1413900_15255\",\n        \"url\": \"file/location/a9e20260-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-e953493b-cbe3-4319-885e-00c82089c76c\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-65c54676-3adb-4ef0-b65e-8e2a49533cbf\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"f02ac6b0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/21568877-07a5-411f-9715-5e92806c4448\"\n      },\n      {\n        \"sourceId\": \"f02fcfc1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/f3b1f1a2-48d3-475d-a607-2e5a1fe532e7\"\n      },\n      {\n        \"sourceId\": \"f03526f0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/84a40c66-d925-4a4a-ba62-8491d26e29e9\"\n      },\n      {\n        \"sourceId\": \"f03e75c1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/e84c00e8-a148-46cf-9a0b-431c4c2aeb08\"\n      },\n      {\n        \"sourceId\": \"f0429471-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/178de9fa-7cc8-457a-8fb6-5c080e6163ea\"\n      },\n      {\n        \"sourceId\": \"f047eba0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/18d153aa-e13b-4264-ae03-f3da75eb425b\"\n      },\n      {\n        \"sourceId\": \"f04fdae0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/7c843e53-8d87-47cf-bca5-1a02e7f5e33f\"\n      },\n      {\n        \"sourceId\": \"f0553210-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/26bacd65-9082-4d83-9506-90e5f1ccd16a\"\n      },\n      {\n        \"sourceId\": \"1413900_84904\",\n        \"url\": \"file/location/d8f7b090-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-84adc784-8d7d-4088-ba51-16fde57fbc21\",\n        \"url\": \"file/sample/location/3881aea9-a731-4e22-9ead-2d6eccc51140\"\n      },\n      {\n        \"sourceId\": \"generated-9e49c58b-0b33-4daf-a39a-8fc91e302328\",\n        \"url\": \"file/sample/location/4bce4154-fb4b-4f0a-887d-a0cd12d4d214\"\n      },\n      {\n        \"sourceId\": \"f02dd3f1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/8937b328-8f0d-4762-8d1f-7d7bc80c3d2e\"\n      },\n      {\n        \"sourceId\": \"f03240c0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/aab6e386-4d59-4b40-b257-9aed12a45446\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "core/src/test/resources/test.json",
    "content": "{\n  \"ownerApp\": \"cpeworkflowtests\",\n  \"createTime\": 1505587453961,\n  \"updateTime\": 1505588471071,\n  \"status\": \"RUNNING\",\n  \"endTime\": 0,\n  \"workflowId\": \"46e2d0d7-0809-40f2-9f22-bed9d41f6613\",\n  \"tasks\": [\n    {\n      \"taskType\": \"perf_task_1\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"0\",\n        \"oddEven\": \"0\"\n      },\n      \"referenceTaskName\": \"perf_task_1\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"correlationId\": \"1505587453950\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_1\",\n      \"scheduledTime\": 1505587453972,\n      \"startTime\": 1505587455481,\n      \"endTime\": 1505587455539,\n      \"updateTime\": 1505587455539,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 3600,\n      \"workflowInstanceId\": \"46e2d0d7-0809-40f2-9f22-bed9d41f6613\",\n      \"taskId\": \"3a54e268-0054-4eab-aea2-e54d1b89896c\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"mod\": \"5\",\n        \"oddEven\": \"1\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_1\",\n        \"taskReferenceName\": \"perf_task_1\",\n        \"inputParameters\": {\n          \"mod\": \"${workflow.input.mod}\",\n          \"oddEven\": \"${workflow.input.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      },\n      \"queueWaitTime\": 1509,\n      \"taskStatus\": \"COMPLETED\"\n    },\n    {\n      \"taskType\": \"perf_task_10\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"taskToExecute\": \"perf_task_10\"\n      },\n      \"referenceTaskName\": \"perf_task_2\",\n      \"retryCount\": 0,\n      \"seq\": 2,\n      \"correlationId\": \"1505587453950\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_10\",\n      \"scheduledTime\": 1505587455517,\n      \"startTime\": 1505587457017,\n      \"endTime\": 1505587457075,\n      \"updateTime\": 1505587457075,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 3600,\n      \"workflowInstanceId\": \"46e2d0d7-0809-40f2-9f22-bed9d41f6613\",\n      \"taskId\": \"3731c3ee-f918-42b7-8bb3-fb016fc0ecae\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"mod\": \"1\",\n        \"oddEven\": \"1\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_10\",\n        \"taskReferenceName\": \"perf_task_2\",\n        \"inputParameters\": {\n          \"taskToExecute\": \"${workflow.input.task2Name}\"\n        },\n        \"type\": \"DYNAMIC\",\n        \"dynamicTaskNameParam\": \"taskToExecute\",\n        \"startDelay\": 0\n      },\n      \"queueWaitTime\": 1500,\n      \"taskStatus\": \"COMPLETED\"\n    },\n    {\n      \"taskType\": \"perf_task_3\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"1\",\n        \"oddEven\": \"1\"\n      },\n      \"referenceTaskName\": \"perf_task_3\",\n      \"retryCount\": 0,\n      \"seq\": 3,\n      \"correlationId\": \"1505587453950\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_3\",\n      \"scheduledTime\": 1505587457064,\n      \"startTime\": 1505587459498,\n      \"endTime\": 1505587459560,\n      \"updateTime\": 1505587459560,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 3600,\n      \"workflowInstanceId\": \"46e2d0d7-0809-40f2-9f22-bed9d41f6613\",\n      \"taskId\": \"738370d6-596f-4ae5-95bf-ca635c7f10dd\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"mod\": \"6\",\n        \"oddEven\": \"0\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_3\",\n        \"taskReferenceName\": \"perf_task_3\",\n        \"inputParameters\": {\n          \"mod\": \"${perf_task_2.output.mod}\",\n          \"oddEven\": \"${perf_task_2.output.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      },\n      \"queueWaitTime\": 2434,\n      \"taskStatus\": \"COMPLETED\"\n    },\n    {\n      \"taskType\": \"HTTP\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"http_request\": {\n          \"uri\": \"/wfe_perf/workflow/_search?q=status:RUNNING&size=0&beta\",\n          \"method\": \"GET\",\n          \"vipAddress\": \"es_cpe_wfe.us-east-1.cloud.netflix.com\"\n        }\n      },\n      \"referenceTaskName\": \"get_es_1\",\n      \"retryCount\": 0,\n      \"seq\": 4,\n      \"correlationId\": \"1505587453950\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"get_from_es\",\n      \"scheduledTime\": 1505587459547,\n      \"startTime\": 1505587459996,\n      \"endTime\": 1505587460250,\n      \"updateTime\": 1505587460250,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"46e2d0d7-0809-40f2-9f22-bed9d41f6613\",\n      \"taskId\": \"64b49d62-1dfb-4290-94d4-971b4d033f33\",\n      \"callbackAfterSeconds\": 0,\n      \"workerId\": \"i-04c53d07aba5b5e9c\",\n      \"outputData\": {\n        \"response\": {\n          \"headers\": {\n            \"Content-Length\": [\n              \"121\"\n            ],\n            \"Content-Type\": [\n              \"application/json; charset=UTF-8\"\n            ]\n          },\n          \"reasonPhrase\": \"OK\",\n          \"body\": {\n            \"took\": 1,\n            \"timed_out\": false,\n            \"_shards\": {\n              \"total\": 6,\n              \"successful\": 6,\n              \"failed\": 0\n            },\n            \"hits\": {\n              \"total\": 1,\n              \"max_score\": 0.0,\n              \"hits\": []\n            }\n          },\n          \"statusCode\": 200\n        }\n      },\n      \"workflowTask\": {\n        \"name\": \"get_from_es\",\n        \"taskReferenceName\": \"get_es_1\",\n        \"type\": \"HTTP\",\n        \"startDelay\": 0\n      },\n      \"queueWaitTime\": 449,\n      \"taskStatus\": \"COMPLETED\"\n    },\n    {\n      \"taskType\": \"DECISION\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"hasChildren\": \"true\",\n        \"case\": \"0\"\n      },\n      \"referenceTaskName\": \"oddEvenDecision\",\n      \"retryCount\": 0,\n      \"seq\": 5,\n      \"correlationId\": \"1505587453950\",\n      \"pollCount\": 0,\n      \"taskDefName\": \"DECISION\",\n      \"scheduledTime\": 1505587460216,\n      \"startTime\": 1505587460241,\n      \"endTime\": 1505587460274,\n      \"updateTime\": 1505587460274,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"46e2d0d7-0809-40f2-9f22-bed9d41f6613\",\n      \"taskId\": \"5a596a36-09eb-4a11-a952-01ab5a7c362f\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"caseOutput\": [\n          \"0\"\n        ]\n      },\n      \"workflowTask\": {\n        \"name\": \"oddEvenDecision\",\n        \"taskReferenceName\": \"oddEvenDecision\",\n        \"inputParameters\": {\n          \"oddEven\": \"${perf_task_3.output.oddEven}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"oddEven\",\n        \"decisionCases\": {\n          \"0\": [\n            {\n              \"name\": \"perf_task_4\",\n              \"taskReferenceName\": \"perf_task_4\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_3.output.mod}\",\n                \"oddEven\": \"${perf_task_3.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"dynamic_fanout\",\n              \"taskReferenceName\": \"fanout1\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"${perf_task_4.output.dynamicTasks}\",\n                \"input\": \"${perf_task_4.output.inputs}\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"input\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"dynamic_join\",\n              \"taskReferenceName\": \"join1\",\n              \"type\": \"JOIN\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_5\",\n              \"taskReferenceName\": \"perf_task_5\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_4.output.mod}\",\n                \"oddEven\": \"${perf_task_4.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_6\",\n              \"taskReferenceName\": \"perf_task_6\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_5.output.mod}\",\n                \"oddEven\": \"${perf_task_5.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            }\n          ],\n          \"1\": [\n            {\n              \"name\": \"perf_task_7\",\n              \"taskReferenceName\": \"perf_task_7\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_3.output.mod}\",\n                \"oddEven\": \"${perf_task_3.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_8\",\n              \"taskReferenceName\": \"perf_task_8\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_7.output.mod}\",\n                \"oddEven\": \"${perf_task_7.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_9\",\n              \"taskReferenceName\": \"perf_task_9\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_8.output.mod}\",\n                \"oddEven\": \"${perf_task_8.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"modDecision\",\n              \"taskReferenceName\": \"modDecision\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_8.output.mod}\"\n              },\n              \"type\": \"DECISION\",\n              \"caseValueParam\": \"mod\",\n              \"decisionCases\": {\n                \"0\": [\n                  {\n                    \"name\": \"perf_task_12\",\n                    \"taskReferenceName\": \"perf_task_12\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"perf_task_13\",\n                    \"taskReferenceName\": \"perf_task_13\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf1\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ],\n                \"1\": [\n                  {\n                    \"name\": \"perf_task_15\",\n                    \"taskReferenceName\": \"perf_task_15\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"perf_task_16\",\n                    \"taskReferenceName\": \"perf_task_16\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_15.output.mod}\",\n                      \"oddEven\": \"${perf_task_15.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf2\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ],\n                \"4\": [\n                  {\n                    \"name\": \"perf_task_18\",\n                    \"taskReferenceName\": \"perf_task_18\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"perf_task_19\",\n                    \"taskReferenceName\": \"perf_task_19\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_18.output.mod}\",\n                      \"oddEven\": \"${perf_task_18.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  }\n                ],\n                \"5\": [\n                  {\n                    \"name\": \"perf_task_21\",\n                    \"taskReferenceName\": \"perf_task_21\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf3\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  },\n                  {\n                    \"name\": \"perf_task_22\",\n                    \"taskReferenceName\": \"perf_task_22\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_21.output.mod}\",\n                      \"oddEven\": \"${perf_task_21.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  }\n                ]\n              },\n              \"defaultCase\": [\n                {\n                  \"name\": \"perf_task_24\",\n                  \"taskReferenceName\": \"perf_task_24\",\n                  \"inputParameters\": {\n                    \"mod\": \"${perf_task_9.output.mod}\",\n                    \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf4\",\n                  \"inputParameters\": {\n                    \"mod\": \"${perf_task_12.output.mod}\",\n                    \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"startDelay\": 0,\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                },\n                {\n                  \"name\": \"perf_task_25\",\n                  \"taskReferenceName\": \"perf_task_25\",\n                  \"inputParameters\": {\n                    \"mod\": \"${perf_task_24.output.mod}\",\n                    \"oddEven\": \"${perf_task_24.output.oddEven}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0\n                }\n              ],\n              \"startDelay\": 0\n            }\n          ]\n        },\n        \"startDelay\": 0\n      },\n      \"queueWaitTime\": 25,\n      \"taskStatus\": \"COMPLETED\"\n    },\n    {\n      \"taskType\": \"perf_task_4\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"mod\": \"6\",\n        \"oddEven\": \"0\"\n      },\n      \"referenceTaskName\": \"perf_task_4\",\n      \"retryCount\": 0,\n      \"seq\": 6,\n      \"correlationId\": \"1505587453950\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"perf_task_4\",\n      \"scheduledTime\": 1505587460234,\n      \"startTime\": 1505587463699,\n      \"endTime\": 1505587463718,\n      \"updateTime\": 1505587463718,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 3600,\n      \"workflowInstanceId\": \"46e2d0d7-0809-40f2-9f22-bed9d41f6613\",\n      \"taskId\": \"1bf3da08-9d16-4f8a-98c3-4a6efee0e03a\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"mod\": \"9\",\n        \"oddEven\": \"1\",\n        \"inputs\": {\n          \"subflow_0\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_4\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          },\n          \"subflow_2\": {\n            \"mod\": 4,\n            \"oddEven\": 0\n          }\n        },\n        \"dynamicTasks\": [\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_0\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_2\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          },\n          {\n            \"name\": null,\n            \"taskReferenceName\": \"subflow_4\",\n            \"description\": null,\n            \"inputParameters\": null,\n            \"type\": \"SUB_WORKFLOW\",\n            \"dynamicTaskNameParam\": null,\n            \"caseValueParam\": null,\n            \"caseExpression\": null,\n            \"decisionCases\": {\n            },\n            \"dynamicForkJoinTasksParam\": null,\n            \"dynamicForkTasksParam\": null,\n            \"dynamicForkTasksInputParamName\": null,\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"sink\": null,\n            \"optional\": null,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_flow_1\",\n              \"version\": null\n            }\n          }\n        ],\n        \"attempt\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"perf_task_4\",\n        \"taskReferenceName\": \"perf_task_4\",\n        \"inputParameters\": {\n          \"mod\": \"${perf_task_3.output.mod}\",\n          \"oddEven\": \"${perf_task_3.output.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      },\n      \"queueWaitTime\": 3465,\n      \"taskStatus\": \"COMPLETED\"\n    }\n  ],\n  \"input\": {\n    \"mod\": \"0\",\n    \"oddEven\": \"0\",\n    \"task2Name\": \"perf_task_10\"\n  },\n  \"workflowType\": \"performance_test_1\",\n  \"version\": 1,\n  \"correlationId\": \"1505587453950\",\n  \"schemaVersion\": 2,\n  \"taskToDomain\": {\n    \"*\": \"beta\"\n  },\n  \"startTime\": 1505587453961,\n  \"workflowDefinition\": {\n    \"createTime\": 1477681181098,\n    \"updateTime\": 1502738273998,\n    \"name\": \"performance_test_1\",\n    \"description\": \"performance_test_1\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"perf_task_1\",\n        \"taskReferenceName\": \"perf_task_1\",\n        \"inputParameters\": {\n          \"mod\": \"${workflow.input.mod}\",\n          \"oddEven\": \"${workflow.input.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      },\n      {\n        \"name\": \"dyntask\",\n        \"taskReferenceName\": \"perf_task_2\",\n        \"inputParameters\": {\n          \"taskToExecute\": \"${workflow.input.task2Name}\"\n        },\n        \"type\": \"DYNAMIC\",\n        \"dynamicTaskNameParam\": \"taskToExecute\",\n        \"startDelay\": 0\n      },\n      {\n        \"name\": \"perf_task_3\",\n        \"taskReferenceName\": \"perf_task_3\",\n        \"inputParameters\": {\n          \"mod\": \"${perf_task_2.output.mod}\",\n          \"oddEven\": \"${perf_task_2.output.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      },\n      {\n        \"name\": \"get_from_es\",\n        \"taskReferenceName\": \"get_es_1\",\n        \"type\": \"HTTP\",\n        \"startDelay\": 0\n      },\n      {\n        \"name\": \"oddEvenDecision\",\n        \"taskReferenceName\": \"oddEvenDecision\",\n        \"inputParameters\": {\n          \"oddEven\": \"${perf_task_3.output.oddEven}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"oddEven\",\n        \"decisionCases\": {\n          \"0\": [\n            {\n              \"name\": \"perf_task_4\",\n              \"taskReferenceName\": \"perf_task_4\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_3.output.mod}\",\n                \"oddEven\": \"${perf_task_3.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"dynamic_fanout\",\n              \"taskReferenceName\": \"fanout1\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"${perf_task_4.output.dynamicTasks}\",\n                \"input\": \"${perf_task_4.output.inputs}\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"input\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"dynamic_join\",\n              \"taskReferenceName\": \"join1\",\n              \"type\": \"JOIN\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_5\",\n              \"taskReferenceName\": \"perf_task_5\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_4.output.mod}\",\n                \"oddEven\": \"${perf_task_4.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_6\",\n              \"taskReferenceName\": \"perf_task_6\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_5.output.mod}\",\n                \"oddEven\": \"${perf_task_5.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            }\n          ],\n          \"1\": [\n            {\n              \"name\": \"perf_task_7\",\n              \"taskReferenceName\": \"perf_task_7\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_3.output.mod}\",\n                \"oddEven\": \"${perf_task_3.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_8\",\n              \"taskReferenceName\": \"perf_task_8\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_7.output.mod}\",\n                \"oddEven\": \"${perf_task_7.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"perf_task_9\",\n              \"taskReferenceName\": \"perf_task_9\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_8.output.mod}\",\n                \"oddEven\": \"${perf_task_8.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"startDelay\": 0\n            },\n            {\n              \"name\": \"modDecision\",\n              \"taskReferenceName\": \"modDecision\",\n              \"inputParameters\": {\n                \"mod\": \"${perf_task_8.output.mod}\"\n              },\n              \"type\": \"DECISION\",\n              \"caseValueParam\": \"mod\",\n              \"decisionCases\": {\n                \"0\": [\n                  {\n                    \"name\": \"perf_task_12\",\n                    \"taskReferenceName\": \"perf_task_12\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"perf_task_13\",\n                    \"taskReferenceName\": \"perf_task_13\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf1\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ],\n                \"1\": [\n                  {\n                    \"name\": \"perf_task_15\",\n                    \"taskReferenceName\": \"perf_task_15\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"perf_task_16\",\n                    \"taskReferenceName\": \"perf_task_16\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_15.output.mod}\",\n                      \"oddEven\": \"${perf_task_15.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf2\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ],\n                \"4\": [\n                  {\n                    \"name\": \"perf_task_18\",\n                    \"taskReferenceName\": \"perf_task_18\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"perf_task_19\",\n                    \"taskReferenceName\": \"perf_task_19\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_18.output.mod}\",\n                      \"oddEven\": \"${perf_task_18.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  }\n                ],\n                \"5\": [\n                  {\n                    \"name\": \"perf_task_21\",\n                    \"taskReferenceName\": \"perf_task_21\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_9.output.mod}\",\n                      \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf3\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_12.output.mod}\",\n                      \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"startDelay\": 0,\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  },\n                  {\n                    \"name\": \"perf_task_22\",\n                    \"taskReferenceName\": \"perf_task_22\",\n                    \"inputParameters\": {\n                      \"mod\": \"${perf_task_21.output.mod}\",\n                      \"oddEven\": \"${perf_task_21.output.oddEven}\"\n                    },\n                    \"type\": \"SIMPLE\",\n                    \"startDelay\": 0\n                  }\n                ]\n              },\n              \"defaultCase\": [\n                {\n                  \"name\": \"perf_task_24\",\n                  \"taskReferenceName\": \"perf_task_24\",\n                  \"inputParameters\": {\n                    \"mod\": \"${perf_task_9.output.mod}\",\n                    \"oddEven\": \"${perf_task_9.output.oddEven}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf4\",\n                  \"inputParameters\": {\n                    \"mod\": \"${perf_task_12.output.mod}\",\n                    \"oddEven\": \"${perf_task_12.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"startDelay\": 0,\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                },\n                {\n                  \"name\": \"perf_task_25\",\n                  \"taskReferenceName\": \"perf_task_25\",\n                  \"inputParameters\": {\n                    \"mod\": \"${perf_task_24.output.mod}\",\n                    \"oddEven\": \"${perf_task_24.output.oddEven}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0\n                }\n              ],\n              \"startDelay\": 0\n            }\n          ]\n        },\n        \"startDelay\": 0\n      },\n      {\n        \"name\": \"perf_task_28\",\n        \"taskReferenceName\": \"perf_task_28\",\n        \"inputParameters\": {\n          \"mod\": \"${perf_task_3.output.mod}\",\n          \"oddEven\": \"${perf_task_3.output.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      },\n      {\n        \"name\": \"perf_task_29\",\n        \"taskReferenceName\": \"perf_task_29\",\n        \"inputParameters\": {\n          \"mod\": \"${perf_task_28.output.mod}\",\n          \"oddEven\": \"${perf_task_28.output.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      },\n      {\n        \"name\": \"perf_task_30\",\n        \"taskReferenceName\": \"perf_task_30\",\n        \"inputParameters\": {\n          \"mod\": \"${perf_task_29.output.mod}\",\n          \"oddEven\": \"${perf_task_29.output.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"startDelay\": 0\n      }\n    ],\n    \"schemaVersion\": 2\n  }\n}\n"
  },
  {
    "path": "dependencies.gradle",
    "content": "/*\n *  Copyright 2022 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\n/*\n * Common place to define all the version dependencies\n */\next {\n    revActivation = '2.0.0'\n    revAwaitility = '3.1.6'\n    revAwsSdk = '1.12.535'\n    revBval = '2.0.6'\n    revCassandra = '3.10.2'\n    revCassandraUnit = '3.11.2.0'\n    revCommonsCompress = '1.24.0'\n    revCommonsIo = '2.7'\n    revDynoQueues = '2.0.20'\n    revElasticSearch6 = '6.8.17'\n    revEmbeddedRedis = '0.6'\n    revEurekaClient = '1.10.10'\n    revFasterXml = '2.15.0'\n    revGroovy = '3.0.19'\n    revGrpc = '1.57.+'\n    revGuava = '32.1.2-jre'\n    revHamcrestAllMatchers = '1.8'\n    revHealth = '1.1.+'\n    revJAXB = '2.3.3'\n    revJAXRS = '2.1.1'\n    revJedis = '3.3.0'\n    revJersey = '1.19.4'\n    revJerseyCommon = '2.22.2'\n    revJsonPath = '2.4.0'\n    revJq = '0.0.13'\n    revJsr311Api = '1.1.1'\n    revMockServerClient = '5.12.0'\n    revOpenapi = '1.6.+'\n    revOrkesQueues = '1.0.3'\n    revPowerMock = '2.0.9'\n    revProtoBuf = '3.24.3'\n    revProtogenAnnotations = '1.0.0'\n    revProtogenCodegen = '1.4.0'\n    revRarefiedRedis = '0.0.17'\n    revRedisson = '3.13.3'\n    revRxJava = '1.2.2'\n    revSpectator = '0.122.0'\n    revSpock = '2.3-groovy-3.0'\n    revSpotifyCompletableFutures = '0.3.3'\n    revTestContainer = '1.19.1'\n}\n"
  },
  {
    "path": "docker/README.md",
    "content": "\n# Conductor Docker Builds\n\n## Pre-built docker images\n\nConductor server with support for the following backend:\n1. Redis\n2. Postgres\n3. Mysql\n4. Cassandra\n\n### Docker File for Server and UI\n\n[Docker Image Source for Server with UI](server/Dockerfile)\n\n### Configuration Guide for Conductor Server\nConductor uses a persistent store for managing state.  \nThe choice of backend is quite flexible and can be configured at runtime using `conductor.db.type` property.\n\nRefer to the table below for various supported backend and required configurations to enable each of them.\n\n> [!IMPORTANT]\n> \n> See [config.properties](docker/server/config/config.properties) for the required properties for each of the backends.\n>\n> | Backend    | Property                           |\n> |------------|------------------------------------|\n> | postgres   | conductor.db.type=postgres         |\n> | redis      | conductor.db.type=redis_standalone |\n> | mysql      | conductor.db.type=mysql            |\n> | cassandra  | conductor.db.type=cassandra        |    \n>\n\nConductor using Elasticsearch for indexing the workflow data.  \nCurrently, Elasticsearch 6 and 7 are supported.\n\nWe welcome community contributions for other indexing backends.\n\n**Note:** Docker images use Elasticsearch 7.\n\n## Helm Charts\nTODO: Link to the helm charts\n\n## Run Docker Compose Locally\n### Use the docker-compose to bring up the local conductor server.\n\n| Docker Compose                                               | Description                |\n|--------------------------------------------------------------|----------------------------|\n| [docker-compose.yaml](docker-compose.yaml)                   | Redis + Elasticsearch 7    |\n| [docker-compose-postgres.yaml](docker-compose-postgres.yaml) | Postgres + Elasticsearch 7 |\n| [docker-compose-mysql.yaml](docker-compose-mysql.yaml)    | Mysql + Elasticsearch 7    |\n"
  },
  {
    "path": "docker/ci/Dockerfile",
    "content": "FROM openjdk:17-jdk\n\nWORKDIR /workspace/conductor\nCOPY . /workspace/conductor\n\nRUN ./gradlew clean build\n"
  },
  {
    "path": "docker/docker-compose-mysql.yaml",
    "content": "version: '2.3'\n\nservices:\n\n  conductor-server:\n    environment:\n      - CONFIG_PROP=config-mysql.properties\n    image: conductor:server\n    container_name: conductor-server\n    build:\n      context: ../\n      dockerfile: docker/server/Dockerfile\n    networks:\n      - internal\n    ports:\n      - 8080:8080\n      - 5000:5000\n    healthcheck:\n      test: [ \"CMD\", \"curl\",\"-I\" ,\"-XGET\", \"http://localhost:8080/health\" ]\n      interval: 60s\n      timeout: 30s\n      retries: 12\n    links:\n      - conductor-elasticsearch:es\n      - conductor-mysql:mysql\n      - conductor-redis:rs\n    depends_on:\n      conductor-elasticsearch:\n        condition: service_healthy\n      conductor-mysql:\n        condition: service_healthy\n      conductor-redis:\n        condition: service_healthy\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1k\"\n        max-file: \"3\"\n\n  conductor-mysql:\n    image: mysql:latest\n    environment:\n      MYSQL_ROOT_PASSWORD: 12345\n      MYSQL_DATABASE: conductor\n      MYSQL_USER: conductor\n      MYSQL_PASSWORD: conductor\n    volumes:\n      - type: volume\n        source: conductor_mysql\n        target: /var/lib/mysql\n    networks:\n      - internal\n    ports:\n      - 3306:3306\n    healthcheck:\n      test: timeout 5 bash -c 'cat < /dev/null > /dev/tcp/localhost/3306'\n      interval: 5s\n      timeout: 5s\n      retries: 12\n\n  conductor-redis:\n    image: redis:6.2.3-alpine\n    volumes:\n      - ./redis.conf:/usr/local/etc/redis/redis.conf\n    networks:\n      - internal\n    ports:\n      - 7379:6379\n    healthcheck:\n      test: [ \"CMD\", \"redis-cli\",\"ping\" ]\n\n  conductor-elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.11\n    environment:\n      - \"ES_JAVA_OPTS=-Xms512m -Xmx1024m\"\n      - xpack.security.enabled=false\n      - discovery.type=single-node\n    volumes:\n      - esdata-conductor:/usr/share/elasticsearch/data\n    networks:\n      - internal\n    ports:\n      - 9201:9200\n    healthcheck:\n      test: curl http://localhost:9200/_cluster/health -o /dev/null\n      interval: 5s\n      timeout: 5s\n      retries: 12\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1k\"\n        max-file: \"3\"\n\nvolumes:\n  conductor_mysql:\n    driver: local\n  esdata-conductor:\n    driver: local\n\nnetworks:\n  internal:\n"
  },
  {
    "path": "docker/docker-compose-postgres.yaml",
    "content": "version: '2.3'\n\nservices:\n  conductor-server:\n    environment:\n      - CONFIG_PROP=config-postgres.properties\n    image: conductor:server\n    container_name: conductor-server\n    build:\n      context: ../\n      dockerfile: docker/server/Dockerfile\n    networks:\n      - internal\n    ports:\n      - 8080:8080\n      - 5000:5000\n    healthcheck:\n      test: [ \"CMD\", \"curl\",\"-I\" ,\"-XGET\", \"http://localhost:8080/health\" ]\n      interval: 60s\n      timeout: 30s\n      retries: 12\n    links:\n      - conductor-elasticsearch:es\n      - conductor-postgres:postgresdb\n    depends_on:\n      conductor-elasticsearch:\n        condition: service_healthy\n      conductor-postgres:\n        condition: service_healthy\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1k\"\n        max-file: \"3\"\n\n  conductor-postgres:\n    image: postgres\n    environment:\n      - POSTGRES_USER=conductor\n      - POSTGRES_PASSWORD=conductor\n    volumes:\n      - pgdata-conductor:/var/lib/postgresql/data\n    networks:\n      - internal\n    ports:\n      - 6432:5432\n    healthcheck:\n      test: timeout 5 bash -c 'cat < /dev/null > /dev/tcp/localhost/5432'\n      interval: 5s\n      timeout: 5s\n      retries: 12\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1k\"\n        max-file: \"3\"\n\n  conductor-elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.11\n    environment:\n      - \"ES_JAVA_OPTS=-Xms512m -Xmx1024m\"\n      - xpack.security.enabled=false\n      - discovery.type=single-node\n    volumes:\n      - esdata-conductor:/usr/share/elasticsearch/data\n    networks:\n      - internal\n    ports:\n      - 9201:9200\n    healthcheck:\n      test: curl http://localhost:9200/_cluster/health -o /dev/null\n      interval: 5s\n      timeout: 5s\n      retries: 12\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1k\"\n        max-file: \"3\"\n\nvolumes:\n  pgdata-conductor:\n    driver: local\n  esdata-conductor:\n    driver: local\n\nnetworks:\n  internal:\n"
  },
  {
    "path": "docker/docker-compose.yaml",
    "content": "version: '2.3'\n\nservices:\n  conductor-server:\n    environment:\n      - CONFIG_PROP=config-redis.properties\n    image: conductor:server\n    container_name: conductor-server\n    build:\n      context: ../\n      dockerfile: docker/server/Dockerfile\n    networks:\n      - internal\n    ports:\n      - 8080:8080\n      - 5000:5000\n    healthcheck:\n      test: [\"CMD\", \"curl\",\"-I\" ,\"-XGET\", \"http://localhost:8080/health\"]\n      interval: 60s\n      timeout: 30s\n      retries: 12\n    links:\n      - conductor-elasticsearch:es\n      - conductor-redis:rs\n    depends_on:\n      conductor-elasticsearch:\n        condition: service_healthy\n      conductor-redis:\n        condition: service_healthy\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1k\"\n        max-file: \"3\"\n\n  conductor-redis:\n    image: redis:6.2.3-alpine\n    volumes:\n      - ../server/config/redis.conf:/usr/local/etc/redis/redis.conf\n    networks:\n      - internal\n    ports:\n      - 7379:6379\n    healthcheck:\n      test: [ \"CMD\", \"redis-cli\",\"ping\" ]\n\n  conductor-elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.11\n    environment:\n      - \"ES_JAVA_OPTS=-Xms512m -Xmx1024m\"\n      - xpack.security.enabled=false\n      - discovery.type=single-node\n    volumes:\n      - esdata-conductor:/usr/share/elasticsearch/data\n    networks:\n      - internal\n    ports:\n      - 9201:9200\n    healthcheck:\n      test: curl http://localhost:9200/_cluster/health -o /dev/null\n      interval: 5s\n      timeout: 5s\n      retries: 12\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1k\"\n        max-file: \"3\"\n\nvolumes:\n  esdata-conductor:\n    driver: local\n\nnetworks:\n  internal:\n"
  },
  {
    "path": "docker/server/Dockerfile",
    "content": "#\n# conductor:server - Combined Netflix conductor server & UI\n#\n# ===========================================================================================================\n# 0. Builder stage\n# ===========================================================================================================\nFROM alpine:3.18 AS builder\n\nLABEL maintainer=\"Netflix OSS <conductor@netflix.com>\"\n\n# ===========================================================================================================\n# 0. Build Conductor Server\n# ===========================================================================================================\n\n\n# Install dependencies\nRUN apk add openjdk17\nRUN apk add git\nRUN apk add --update nodejs npm yarn\n\nCOPY . /conductor\nWORKDIR /conductor/ui\n# Include monaco sources into bundle (instead of using CDN)\nENV REACT_APP_MONACO_EDITOR_USING_CDN=false\nRUN yarn install && cp -r node_modules/monaco-editor public/ && yarn build\nRUN ls -ltr\nRUN echo \"Done building UI\"\n\n# Checkout the community project\nWORKDIR /\nRUN mkdir server-build\nWORKDIR  server-build\nRUN ls -ltr\n\nRUN git clone https://github.com/Netflix/conductor-community.git\n\n# Copy the project directly onto the image\nWORKDIR conductor-community\nRUN ls -ltr\n\n# Build the server on run\nRUN ./gradlew build -x test --stacktrace\nWORKDIR /server-build\nRUN ls -ltr\nRUN pwd\n\n\n# ===========================================================================================================\n# 1. Bin stage\n# ===========================================================================================================\nFROM alpine:3.18\n\nLABEL maintainer=\"Netflix OSS <conductor@netflix.com>\"\n\nRUN apk add openjdk17\nRUN apk add nginx\n\n# Make app folders\nRUN mkdir -p /app/config /app/logs /app/libs\n\n# Copy the compiled output to new image\nCOPY docker/server/bin /app\nCOPY docker/server/config /app/config\nCOPY --from=builder /server-build/conductor-community/community-server/build/libs/*boot*.jar /app/libs/conductor-server.jar\n\n# Copy compiled UI assets to nginx www directory\nWORKDIR /usr/share/nginx/html\nRUN rm -rf ./*\nCOPY --from=builder /conductor/ui/build .\nCOPY --from=builder /conductor/docker/server/nginx/nginx.conf /etc/nginx/http.d/default.conf\n\n# Copy the files for the server into the app folders\nRUN chmod +x /app/startup.sh\n\nHEALTHCHECK --interval=60s --timeout=30s --retries=10 CMD curl -I -XGET http://localhost:8080/health || exit 1\n\nCMD [ \"/app/startup.sh\" ]\nENTRYPOINT [ \"/bin/sh\"]\n"
  },
  {
    "path": "docker/server/config/config-mysql.properties",
    "content": "# Database persistence type.\nconductor.db.type=mysql\n\n# mysql\nspring.datasource.url=jdbc:mysql://mysql:3306/conductor\nspring.datasource.username=conductor\nspring.datasource.password=conductor\n\n# Use redis queues\nconductor.queue.type=redis_standalone\n\n# Elastic search instance indexing is enabled.\nconductor.indexing.enabled=true\nconductor.elasticsearch.url=http://es:9200\nconductor.elasticsearch.indexName=conductor\nconductor.elasticsearch.version=7\nconductor.elasticsearch.clusterHealthColor=yellow\n\n# Additional modules for metrics collection exposed to Prometheus (optional)\nconductor.metrics-prometheus.enabled=true\nmanagement.endpoints.web.exposure.include=prometheus\n\n# Load sample kitchen-sink workflow\nloadSample=true\n"
  },
  {
    "path": "docker/server/config/config-postgres.properties",
    "content": "# Database persistence type.\nconductor.db.type=postgres\n\n# postgres\nspring.datasource.url=jdbc:postgresql://postgresdb:5432/postgres\nspring.datasource.username=conductor\nspring.datasource.password=conductor\n\n# Elastic search instance indexing is enabled.\nconductor.indexing.enabled=true\nconductor.elasticsearch.url=http://es:9200\nconductor.elasticsearch.indexName=conductor\nconductor.elasticsearch.version=7\nconductor.elasticsearch.clusterHealthColor=yellow\n\n# Additional modules for metrics collection exposed to Prometheus (optional)\nconductor.metrics-prometheus.enabled=true\nmanagement.endpoints.web.exposure.include=prometheus\n\n# Load sample kitchen-sink workflow\nloadSample=true\n"
  },
  {
    "path": "docker/server/config/config-redis.properties",
    "content": "# Database persistence type.\n# Below are the properties for redis\nconductor.db.type=redis_standalone\nconductor.redis.hosts=rs:6379:us-east-1c\nconductor.redis-lock.serverAddress=redis://rs:6379\nconductor.redis.taskDefCacheRefreshInterval=1\nconductor.redis.workflowNamespacePrefix=conductor\nconductor.redis.queueNamespacePrefix=conductor_queues\n\n#Use redis queues\nconductor.queue.type=redis_standalone\n\n# Elastic search instance indexing is enabled.\nconductor.indexing.enabled=true\nconductor.elasticsearch.url=http://es:9200\nconductor.elasticsearch.indexName=conductor\nconductor.elasticsearch.version=7\nconductor.elasticsearch.clusterHealthColor=yellow\n\n# Additional modules for metrics collection exposed to Prometheus (optional)\nconductor.metrics-prometheus.enabled=true\nmanagement.endpoints.web.exposure.include=prometheus\n\n# Load sample kitchen sink workflow\nloadSample=true\n"
  },
  {
    "path": "docker/server/config/config.properties",
    "content": "# See README in the docker for configuration guide\n\n# db.type determines the type of database used\n# See various configurations below for the values\nconductor.db.type=SET_THIS\n\n# =====================================================#\n#              Redis Configuration Properties\n# =====================================================#\n#conductor.db.type=redis_standalone\n\n# The last part MUST be us-east-1c, it is not used and is kept for backwards compatibility\n# conductor.redis.hosts=rs:6379:us-east-1c\n#\n\n# conductor.redis-lock.serverAddress=redis://rs:6379\n# conductor.redis.taskDefCacheRefreshInterval=1\n# conductor.redis.workflowNamespacePrefix=conductor\n# conductor.redis.queueNamespacePrefix=conductor_queues\n\n\n# =====================================================#\n#              Postgres Configuration Properties\n# =====================================================#\n\n# conductor.db.type=postgres\n# spring.datasource.url=jdbc:postgresql://localhost:5432/postgres\n# spring.datasource.username=postgres\n# spring.datasource.password=postgres\n# Additionally you can use set the spring.datasource.XXX properties for connection pool size etc.\n\n# If you want to use Postgres as indexing store set the following\n# conductor.indexing.enabled=true\n# conductor.indexing.type=postgres\n\n# When using Elasticsearch 7 for indexing, set the following\n\n# conductor.indexing.enabled=true\n# conductor.elasticsearch.url=http://es:9200\n# conductor.elasticsearch.version=7\n# conductor.elasticsearch.indexName=conductor\n\n"
  },
  {
    "path": "docker/server/config/log4j-file-appender.properties",
    "content": "#\n# Copyright 2020 Netflix, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nlog4j.rootLogger=INFO,console,file\n\nlog4j.appender.console=org.apache.log4j.ConsoleAppender\nlog4j.appender.console.layout=org.apache.log4j.PatternLayout\nlog4j.appender.console.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n\nlog4j.appender.file=org.apache.log4j.RollingFileAppender\nlog4j.appender.file.File=/app/logs/conductor.log\nlog4j.appender.file.MaxFileSize=10MB\nlog4j.appender.file.MaxBackupIndex=10\nlog4j.appender.file.layout=org.apache.log4j.PatternLayout\nlog4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n\n# Dedicated file appender for metrics\nlog4j.appender.fileMetrics=org.apache.log4j.RollingFileAppender\nlog4j.appender.fileMetrics.File=/app/logs/metrics.log\nlog4j.appender.fileMetrics.MaxFileSize=10MB\nlog4j.appender.fileMetrics.MaxBackupIndex=10\nlog4j.appender.fileMetrics.layout=org.apache.log4j.PatternLayout\nlog4j.appender.fileMetrics.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n\nlog4j.logger.ConductorMetrics=INFO,console,fileMetrics\nlog4j.additivity.ConductorMetrics=false\n\n"
  },
  {
    "path": "docker/server/config/log4j.properties",
    "content": "#\n# Copyright 2017 Netflix, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n# Set root logger level to DEBUG and its only appender to A1.\nlog4j.rootLogger=INFO, A1\n\n# A1 is set to be a ConsoleAppender.\nlog4j.appender.A1=org.apache.log4j.ConsoleAppender\n\n# A1 uses PatternLayout.\nlog4j.appender.A1.layout=org.apache.log4j.PatternLayout\nlog4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n\nlogging.logger.com.netflix.dyno.queues.redis.RedisDynoQueue=ERROR"
  },
  {
    "path": "docker/server/config/redis.conf",
    "content": "appendonly yes"
  },
  {
    "path": "docker/server/nginx/nginx.conf",
    "content": "server {\n  listen 5000;\n  server_name conductor;\n  server_tokens off;\n\n  location / {\n    add_header Referrer-Policy \"strict-origin\";\n    add_header X-Frame-Options \"SAMEORIGIN\";\n    add_header X-Content-Type-Options \"nosniff\";\n    add_header Content-Security-Policy \"script-src 'self' 'unsafe-inline' 'unsafe-eval' assets.orkes.io *.googletagmanager.com *.pendo.io https://cdn.jsdelivr.net; worker-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;\";\n    add_header Permissions-Policy \"accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), clipboard-read=(self), clipboard-write=(self), gamepad=(), hid=(), idle-detection=(), serial=(), window-placement=(self)\";\n\n    # This would be the directory where your React app's static files are stored at\n    root /usr/share/nginx/html;\n    try_files $uri /index.html;\n  }\n\n  location /api {\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-NginX-Proxy true;\n    proxy_pass http://localhost:8080/api;\n    proxy_ssl_session_reuse off;\n    proxy_set_header Host $http_host;\n    proxy_cache_bypass $http_upgrade;\n    proxy_redirect off;\n  }\n\n  location /actuator {\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-NginX-Proxy true;\n    proxy_pass http://localhost:8080/actuator;\n    proxy_ssl_session_reuse off;\n    proxy_set_header Host $http_host;\n    proxy_cache_bypass $http_upgrade;\n    proxy_redirect off;\n  }\n\n  location /swagger-ui {\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-NginX-Proxy true;\n    proxy_pass http://localhost:8080/swagger-ui;\n    proxy_ssl_session_reuse off;\n    proxy_set_header Host $http_host;\n    proxy_cache_bypass $http_upgrade;\n    proxy_redirect off;\n  }\n}"
  },
  {
    "path": "docker/ui/Dockerfile",
    "content": "#\n# conductor:ui - Netflix Conductor UI\n#\nFROM node:20-alpine\nLABEL maintainer=\"Netflix OSS <conductor@netflix.com>\"\n\n# Install the required packages for the node build\n# to run on alpine\nRUN apk update && apk add --no-cache python3 py3-pip make g++\n\n# A directory within the virtualized Docker environment\n# Becomes more relevant when using Docker Compose later\nWORKDIR /usr/src/app\n\n# Copies package.json to Docker environment in a separate layer as a performance optimization\nCOPY ./ui/package.json ./\n\n# Installs all node packages. Cached unless package.json changes\nRUN yarn install && mkdir -p public && cp -r node_modules/monaco-editor public/\n\n# Copies everything else over to Docker environment\n# node_modules excluded in .dockerignore.\nCOPY ./ui .\n\n# Include monaco sources into bundle (instead of using CDN)\nENV REACT_APP_MONACO_EDITOR_USING_CDN=false\n\nCMD [ \"yarn\", \"start\" ]\n"
  },
  {
    "path": "docker/ui/README.md",
    "content": "# Docker\n## Conductor UI\nThis Dockerfile create the conductor:ui image\n\n## Building the image\n\nRun the following commands from the project root.\n\n`docker build -f docker/ui/Dockerfile -t conductor:ui .`\n\n## Running the conductor server\n - With localhost conductor server: `docker run -p 5000:5000 -d -t conductor:ui`\n - With external conductor server: `docker run -p 5000:5000 -d -t -e \"WF_SERVER=http://conductor-server:8080\" conductor:ui`\n"
  },
  {
    "path": "docs/docs/apispec.md",
    "content": "# API Specification\n\n## Task & Workflow Metadata\n| Endpoint                                 | Description                      | Input                                                       |\n|------------------------------------------|:---------------------------------|-------------------------------------------------------------|\n| `GET /metadata/taskdefs`                 | Get all the task definitions     | n/a                                                         |\n| `GET /metadata/taskdefs/{taskType}`      | Retrieve task definition         | Task Name                                                   |\n| `POST /metadata/taskdefs`                | Register new task definitions    | List of [Task Definitions](/configuration/taskdef.html)        |\n| `PUT /metadata/taskdefs`                 | Update a task definition         | A [Task Definition](/configuration/taskdef.html)               |\n| `DELETE /metadata/taskdefs/{taskType}`   | Delete a task definition         | Task Name                                                   |\n|||\n| `GET /metadata/workflow`                 | Get all the workflow definitions | n/a                                                         |\n| `POST /metadata/workflow`                | Register new workflow            | [Workflow Definition](/configuration/workflowdef.html)         |\n| `PUT /metadata/workflow`                 | Register/Update new workflows    | List of [Workflow Definition](/configuration/workflowdef.html) |\n| `GET /metadata/workflow/{name}?version=` | Get the workflow definitions     | workflow name, version (optional)                           |\n|||\n \n## Start A Workflow\n### With Input only\nSee [Start Workflow Request](/gettingstarted/startworkflow.html).\n\n#### Output\nId of the workflow (GUID)\n\n### With Input and Task Domains\n```\nPOST /workflow\n{\n   //JSON payload for Start workflow request\n}\n```\n#### Start workflow request\nJSON for start workflow request\n```\n{\n  \"name\": \"myWorkflow\", // Name of the workflow\n  \"version\": 1, // Version\n  “correlationId”: “corr1”, // correlation Id\n  \"priority\": 1, // Priority\n  \"input\": {\n\t// Input map. \n  },\n  \"taskToDomain\": {\n\t// Task to domain map\n  }\n}\n```\n\n#### Output\nId of the workflow (GUID)\n\n\n## Retrieve Workflows\n| Endpoint                                                                    | Description                                   |\n|-----------------------------------------------------------------------------|-----------------------------------------------|\n| `GET /workflow/{workflowId}?includeTasks=true                               | false`                                        |Get Workflow State by workflow Id.  If includeTasks is set, then also includes all the tasks executed and scheduled.|\n| `GET /workflow/running/{name}`                                              | Get all the running workflows of a given type |\n| `GET /workflow/running/{name}/correlated/{correlationId}?includeClosed=true | false&includeTasks=true                       |false`|Get all the running workflows filtered by correlation Id.  If includeClosed is set, also includes workflows that have completed running.|\n| `GET /workflow/search`                                                      | Search for workflows.  See Below.             |\n\n\n## Search for Workflows\nConductor uses Elasticsearch for indexing workflow execution and is used by search APIs.\n\n`GET /workflow/search?start=&size=&sort=&freeText=&query=`\n\n| Parameter | Description                                                                                                      |\n|-----------|------------------------------------------------------------------------------------------------------------------|\n| start     | Page number.  Defaults to 0                                                                                      |\n| size      | Number of results to return                                                                                      |\n| sort      | Sorting.  Format is: `ASC:<fieldname>` or `DESC:<fieldname>` to sort in ascending or descending order by a field |\n| freeText  | Elasticsearch supported query. e.g. workflowType:\"name_of_workflow\"                                              |\n| query     | SQL like where clause.  e.g. workflowType = 'name_of_workflow'.  Optional if freeText is provided.               |\n\n### Output\nSearch result as described below:\n```json\n{\n  \"totalHits\": 0,\n  \"results\": [\n    {\n      \"workflowType\": \"string\",\n      \"version\": 0,\n      \"workflowId\": \"string\",\n      \"correlationId\": \"string\",\n      \"startTime\": \"string\",\n      \"updateTime\": \"string\",\n      \"endTime\": \"string\",\n      \"status\": \"RUNNING\",\n      \"input\": \"string\",\n      \"output\": \"string\",\n      \"reasonForIncompletion\": \"string\",\n      \"executionTime\": 0,\n      \"event\": \"string\"\n    }\n  ]\n}\n```\n\n## Manage Workflows\n| Endpoint                                                  | Description                                                                                        |\n|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------|\n| `PUT /workflow/{workflowId}/pause`                        | Pause.  No further tasks will be scheduled until resumed.  Currently running tasks are not paused. |\n| `PUT /workflow/{workflowId}/resume`                       | Resume normal operations after a pause.                                                            |\n| `POST /workflow/{workflowId}/rerun`                       | See Below.                                                                                         |\n| `POST /workflow/{workflowId}/restart`                     | Restart workflow execution from the start.  Current execution history is wiped out.                |\n| `POST /workflow/{workflowId}/retry`                       | Retry the last failed task.                                                                        |\n| `PUT /workflow/{workflowId}/skiptask/{taskReferenceName}` | See below.                                                                                         |\n| `DELETE /workflow/{workflowId}`                           | Terminates the running workflow.                                                                   |\n| `DELETE /workflow/{workflowId}/remove`                    | Deletes the workflow from system.  Use with caution.                                               |\n\n### Rerun\nRe-runs a completed workflow from a specific task. \n\n`POST /workflow/{workflowId}/rerun`\n\n```json\n{\n  \"reRunFromWorkflowId\": \"string\",\n  \"workflowInput\": {},\n  \"reRunFromTaskId\": \"string\",\n  \"taskInput\": {}\n}\n```\n\n###Skip Task\n\nSkips a task execution (specified as `taskReferenceName` parameter) in a running workflow and continues forward.\nOptionally updating task's input and output as specified in the payload.\n`PUT /workflow/{workflowId}/skiptask/{taskReferenceName}?workflowId=&taskReferenceName=`\n```json\n{\n  \"taskInput\": {},\n  \"taskOutput\": {}\n}\n```\n\n## Manage Tasks\n| Endpoint                                              | Description                                           |\n|-------------------------------------------------------|-------------------------------------------------------|\n| `GET /tasks/{taskId}`                                 | Get task details.                                     |\n| `GET /tasks/queue/all`                                | List the pending task sizes.                          |\n| `GET /tasks/queue/all/verbose`                        | Same as above, includes the size per shard            |\n| `GET /tasks/queue/sizes?taskType=&taskType=&taskType` | Return the size of pending tasks for given task types |\n|||\n\n## Polling, Ack and Update Task\nThese are critical endpoints used to poll for task, send ack (after polling) and finally updating the task result by worker.\n\n\n| Endpoint                                                            | Description                                                                                                                                                                                                                                                                                                        |\n|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `GET /tasks/poll/{taskType}?workerid=&domain=`                      | Poll for a task. `workerid` identifies the worker that polled for the job and `domain` allows the poller to poll for a task in a specific domain                                                                                                                                                                   |\n| `GET /tasks/poll/batch/{taskType}?count=&timeout=&workerid=&domain` | Poll for a task in a batch specified by `count`.  This is a long poll and the connection will wait until `timeout` or if there is at-least 1 item available, whichever comes first.`workerid` identifies the worker that polled for the job and `domain` allows the poller to poll for a task in a specific domain |\n| `POST /tasks`                                                       | Update the result of task execution.  See the schema below.                                                                                                                                                                                                                                                        |\n\n\n### Schema for updating Task Result\n```json\n{\n    \"workflowInstanceId\": \"Workflow Instance Id\",\n    \"taskId\": \"ID of the task to be updated\",\n    \"reasonForIncompletion\" : \"If failed, reason for failure\",\n    \"callbackAfterSeconds\": 0,\n    \"status\": \"IN_PROGRESS|FAILED|COMPLETED\",\n    \"outputData\": {\n \t\t//JSON document representing Task execution output     \n    }\n    \n}\n```\n!!!Info \"Acknowledging tasks after poll\"\n\tIf the worker fails to ack the task after polling, the task is re-queued and put back in queue and is made available during subsequent poll.\n"
  },
  {
    "path": "docs/docs/architecture/overview.md",
    "content": "# Overview\n\n![Architecture diagram](/img/conductor-architecture.png)\n\nThe API and storage layers are pluggable and provide ability to work with different backends and queue service providers.\n\n## Runtime Model\nConductor follows RPC based communication model where workers are running on a separate machine from the server. Workers communicate with server over HTTP based endpoints and employs polling model for managing work queues.\n\n![Runtime Model of Conductor](/img/overview.png)\n\n**Notes**\n\n* Workers are remote systems that communicate over HTTP with the conductor servers.\n* Task Queues are used to schedule tasks for workers.  We use [dyno-queues][1] internally but it can easily be swapped with SQS or similar pub-sub mechanism.\n* conductor-redis-persistence module uses [Dynomite][2] for storing the state and metadata along with [Elasticsearch][3] for indexing backend.\n* See section under extending backend for implementing support for different databases for storage and indexing.\n\n[1]: https://github.com/Netflix/dyno-queues\n[2]: https://github.com/Netflix/dynomite\n[3]: https://www.elastic.co\n"
  },
  {
    "path": "docs/docs/architecture/tasklifecycle.md",
    "content": "## Task state transitions\nThe figure below depicts the state transitions that a task can go through within a workflow execution.\n\n![Task_States](/img/task_states.png)\n\n## Retries and Failure Scenarios\n\n### Task failure and retries\nRetries for failed task executions of each task can be configured independently. retryCount, retryDelaySeconds and retryLogic can be used to configure the retry mechanism.\n\n![Task Failure](/img/TaskFailure.png)\n\n1. Worker (W1) polls for task T1 from the Conductor server and receives the task.\n2. Upon processing this task, the worker determines that the task execution is a failure and reports this to the server with FAILED status after 10 seconds.\n3. The server will persist this FAILED execution of T1. A new execution of task T1 will be created and scheduled to be polled. This task will be available to be polled after 5 (retryDelaySeconds) seconds.\n\n\n### Timeout seconds\nTimeout is the maximum amount of time that the task must reach a terminal state in, else the task will be marked as TIMED_OUT.\n\n![Task Timeout](/img/TimeoutSeconds.png)\n\n**0 seconds** -> Worker polls for task T1 from the Conductor server and receives the task. T1 is put into IN_PROGRESS status by the server.  \nWorker starts processing the task but is unable to process the task at this time. Worker updates the server with T1 set to IN_PROGRESS status and a callback of 9 seconds.  \nServer puts T1 back in the queue but makes it invisible and the worker continues to poll for the task but does not receive T1 for 9 seconds.  \n\n**9,18 seconds** -> Worker receives T1 from the server and is still unable to process the task and updates the server with a callback of 9 seconds.\n\n**27 seconds** -> Worker polls and receives task T1 from the server and is now able to process this task.\n\n**30 seconds** (T1 timeout) -> Server marks T1 as TIMED_OUT because it is not in a terminal state after first being moved to IN_PROGRESS status. Server schedules a new task based on the retry count.\n\n**32 seconds** -> Worker completes processing of T1 and updates the server with COMPLETED status. Server will ignore this update since T1 has already been moved to a terminal status (TIMED_OUT).\n\n\n### Response timeout seconds\nResponse timeout is the time within which the worker must respond to the server with an update for the task, else the task will be marked as TIMED_OUT.\n\n![Response Timeout](/img/ResponseTimeoutSeconds.png)\n\n**0 seconds** -> Worker polls for the task T1 from the Conductor server and receives the task. T1 is put into IN_PROGRESS status by the server.\n\nWorker starts processing the task but the worker instance dies during this execution.\n\n**20 seconds** (T1 responseTimeout) -> Server marks T1 as TIMED_OUT since the task has not been updated by the worker within the configured responseTimeoutSeconds (20). A new instance of task T1 is scheduled as per the retry configuration.\n\n**25 seconds** -> The retried instance of T1 is available to be polled by the worker, after the retryDelaySeconds (5) has elapsed.\n"
  },
  {
    "path": "docs/docs/bestpractices.md",
    "content": "## Response Timeout\n- Configure the responseTimeoutSeconds of each task to be > 0.\n- Should be less than or equal to timeoutSeconds.\n\n## Payload sizes\n- Configure your workflows such that conductor is not used as a persistence store.\n- Ensure that the output data in the task result set in your worker is used by your workflow for execution. If the values in the output payloads are not used by subsequent tasks in your workflow, this data should not be sent back to conductor in the task result.\n- In cases where the output data of your task is used within subsequent tasks in your workflow but is substantially large (> 100KB), consider uploading this data to an object store (S3 or similar) and set the location to the object in your task output. The subsequent tasks can then download this data from the given location and use it during execution.\n"
  },
  {
    "path": "docs/docs/configuration/eventhandlers.md",
    "content": "# Event Handlers\nEventing in Conductor provides for loose coupling between workflows and support for producing and consuming events from external systems.\n\nThis includes:\n\n1.  Being able to produce an event (message) in an external system like SQS or internal to Conductor. \n2. Start a workflow when a specific event occurs that matches the provided criteria.\n\nConductor provides SUB_WORKFLOW task that can be used to embed a workflow inside parent workflow.  Eventing supports provides similar capability without explicitly adding dependencies and provides **fire-and-forget** style integrations.\n\n## Event Task\nEvent task provides ability to publish an event (message) to either Conductor or an external eventing system like SQS. Event tasks are useful for creating event based dependencies for workflows and tasks.\n\nSee [Event Task](/reference-docs/event-task.html) for documentation.\n\n## Event Handler\nEvent handlers are listeners registered that executes an action when a matching event occurs.  The supported actions are:\n\n1.  Start a Workflow\n2.  Fail a Task\n3.  Complete a Task\n\nEvent Handlers can be configured to listen to Conductor Events or an external event like SQS.\n\n### Configuration\nEvent Handlers are configured via ```/event/``` APIs.\n\n#### Structure:\n```json\n{\n  \"name\" : \"descriptive unique name\",\n  \"event\": \"event_type:event_location\",\n  \"condition\": \"boolean condition\",\n  \"actions\": [\"see examples below\"]\n}\n```\n#### Condition\nCondition is an expression that MUST evaluate to a boolean value.  A Javascript like syntax is supported that can be used to evaluate condition based on the payload.\nActions are executed only when the condition evaluates to `true`.\n\n**Examples**\n\nGiven the following payload in the message:\n\n```json\n{\n    \"fileType\": \"AUDIO\",\n    \"version\": 3,\n    \"metadata\": {\n       \"length\": 300,\n       \"codec\": \"aac\"\n    }\n}\n```\n\n|Expression|Result|\n|---|---|\n|`$.version > 1`|true|\n|`$.version > 10`|false|\n|`$.metadata.length == 300`|true|\n\n\n### Actions\n\n**Start A Workflow**\n\n```json\n{\n    \"action\": \"start_workflow\",\n    \"start_workflow\": {\n        \"name\": \"WORKFLOW_NAME\",\n        \"version\": \"<optional_param>\",\n        \"input\": {\n            \"param1\": \"${param1}\" \n        }\n    }\n}\n```\n\n**Complete Task***\n\n```json\n{\n    \"action\": \"complete_task\",\n    \"complete_task\": {\n      \"workflowId\": \"${workflowId}\",\n      \"taskRefName\": \"task_1\",\n      \"output\": {\n        \"response\": \"${result}\"\n      }\n    },\n    \"expandInlineJSON\": true\n}\n```\n\n**Fail Task***\n\n```json\n{\n    \"action\": \"fail_task\",\n    \"fail_task\": {\n      \"workflowId\": \"${workflowId}\",\n      \"taskRefName\": \"task_1\",\n      \"output\": {\n        \"response\": \"${result}\"\n      }\n    },\n    \"expandInlineJSON\": true\n}\n```\nInput for starting a workflow and output when completing / failing task follows the same [expressions](/configuration/workflowdef.html#wiring-inputs-and-outputs) used for wiring workflow inputs.\n\n!!!info \"Expanding stringified JSON elements in payload\"\n\t`expandInlineJSON` property, when set to true will expand the inlined stringified JSON elements in the payload to JSON documents and replace the string value with JSON document.  \n\tThis feature allows such elements to be used with JSON path expressions. \n\n## Extending\n\nProvide the implementation of [EventQueueProvider](https://github.com/Netflix/conductor/blob/master/core/src/main/java/com/netflix/conductor/core/events/EventQueueProvider.java).\n\nSQS Queue Provider: \n[SQSEventQueueProvider.java ](https://github.com/Netflix/conductor/blob/master/contribs/src/main/java/com/netflix/conductor/core/events/sqs/SQSEventQueueProvider.java)\n"
  },
  {
    "path": "docs/docs/configuration/isolationgroups.md",
    "content": "# Isolation Groups\n\nConsider an HTTP task where the latency of an API is high, task queue piles up effecting execution of other HTTP tasks which have low latency.\n\nWe can isolate the execution of such tasks to have predictable performance using `isolationgroupId`, a property of task definition.\n\nWhen we set isolationGroupId,  the executor `SystemTaskWorkerCoordinator` will allocate an isolated queue and an isolated thread pool for execution of those tasks.\n\nIf no `isolationgroupId` is specified in task definition, then fallback is default behaviour where the executor executes the task in shared thread-pool for all tasks. \n\n## Example\n\n** Task Definition **\n```json\n{\n  \"name\": \"encode_task\",\n  \"retryCount\": 3,\n\n  \"timeoutSeconds\": 1200,\n  \"inputKeys\": [\n    \"sourceRequestId\",\n    \"qcElementType\"\n  ],\n  \"outputKeys\": [\n    \"state\",\n    \"skipped\",\n    \"result\"\n  ],\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 600,\n  \"responseTimeoutSeconds\": 3600,\n  \"concurrentExecLimit\": 100,\n  \"rateLimitFrequencyInSeconds\": 60,\n  \"rateLimitPerFrequency\": 50,\n  \"isolationgroupId\": \"myIsolationGroupId\"\n}\n```\n** Workflow Definition **\n```json\n{\n  \"name\": \"encode_and_deploy\",\n  \"description\": \"Encodes a file and deploys to CDN\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"encode\",\n      \"taskReferenceName\": \"encode\",\n      \"type\": \"HTTP\", \n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"http://localhost:9200/conductor/_search?size=10\",\n          \"method\": \"GET\"\n        }\n      }\n    }\n  ],\n  \"outputParameters\": {\n    \"cdn_url\": \"${d1.output.location}\"\n  },\n  \"failureWorkflow\": \"cleanup_encode_resources\",\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": true,\n  \"schemaVersion\": 2\n}\n```\n\n\n- puts `encode` in `HTTP-myIsolationGroupId` queue, and allocates a new thread pool for this for execution.\n\n<b>Note: </b>  To enable this feature, the `workflow.isolated.system.task.enable` property needs to be made `true`,its default value is `false`\n\nThe property `workflow.isolated.system.task.worker.thread.count`  sets the thread pool size for isolated tasks; default is `1`.\n\nisolationGroupId is currently supported only in HTTP and kafka Task. \n\n### Execution Name Space\n\n`executionNameSpace` A property of taskdef can be used to provide JVM isolation to task execution and scale executor deployments horizontally.\n\nLimitation of using isolationGroupId is that we need to scale executors vertically as the executor allocates a new thread pool per `isolationGroupId`.  Also, since the executor runs the tasks in the same JVM, task execution is not isolated completely. \n\nTo support JVM isolation, and also allow the executors to scale horizontally, we can use `executionNameSpace` property in taskdef.\n\nExecutor consumes tasks whose executionNameSpace matches with the configuration property `workflow.system.task.worker.executionNameSpace`\n\nIf the property is not set, the executor executes tasks without any executionNameSpace set. \n\n\n```json\n{\n  \"name\": \"encode_task\",\n  \"retryCount\": 3,\n\n  \"timeoutSeconds\": 1200,\n  \"inputKeys\": [\n    \"sourceRequestId\",\n    \"qcElementType\"\n  ],\n  \"outputKeys\": [\n    \"state\",\n    \"skipped\",\n    \"result\"\n  ],\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 600,\n  \"responseTimeoutSeconds\": 3600,\n  \"concurrentExecLimit\": 100,\n  \"rateLimitFrequencyInSeconds\": 60,\n  \"rateLimitPerFrequency\": 50,\n  \"executionNameSpace\": \"myExecutionNameSpace\"\n}\n```\n\n#### Example Workflow task\n\n```json\n{ \n  \"name\": \"encode_and_deploy\",\n  \"description\": \"Encodes a file and deploys to CDN\",\n  \"version\": 1,\n  \"tasks\": [\n    { \n      \"name\": \"encode\",\n      \"taskReferenceName\": \"encode\",\n      \"type\": \"HTTP\", \n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"http://localhost:9200/conductor/_search?size=10\",\n          \"method\": \"GET\"\n        }\n      }\n    }\n  ],\n  \"outputParameters\": {\n    \"cdn_url\": \"${d1.output.location}\"\n  },\n  \"failureWorkflow\": \"cleanup_encode_resources\",\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": true,\n  \"schemaVersion\": 2\n}\n``` \n \n- `encode` task is executed by the executor deployment whose `workflow.system.task.worker.executionNameSpace` property is `myExecutionNameSpace` \n\n`executionNameSpace` can be used along with `isolationGroupId`\n\nIf the above task contains a isolationGroupId `myIsolationGroupId`, the tasks will be scheduled in a queue HTTP@myExecutionNameSpace-myIsolationGroupId, and have a new threadpool for execution in the deployment group with myExecutionNameSpace\n\n\n\n"
  },
  {
    "path": "docs/docs/configuration/sysoperator.md",
    "content": "# System Operators\n\nOperators are built-in primitives in Conductor that allow you to define the control flow in the workflow.\nOperators are similar to programming constructs such as for loops, decisions, etc.\nConductor has support for most of the programing primitives allowing you to define the most advanced workflows.\n\n## Supported Operators\nConductor supports the following programming language constructs: \n\n| Language Construct               | Conductor Operator                                          |\n|----------------------------------|-------------------------------------------------------------|\n| Do/While or Loops                | [Do While Task](/reference-docs/do-while-task.html)         |\n| Dynamic Fork                     | [Dynamic Fork Task](/reference-docs/dynamic-fork-task.html) |\n| Fork / Parallel execution        | [Fork Task](/reference-docs/fork-task.html)                 | \n| Join                             | [Join Task](/reference-docs/join-task.html)                 |\n| Sub Process / Sub-Flow           | [Sub Workflow Task](/reference-docs/sub-workflow-task.html) |\n| Switch//Decision/if..then...else | [Switch Task](/reference-docs/switch-task.html)             |\n| Terminate                        | [Terminate Task](/reference-docs/terminate-task.html)       |\n| Variables                        | [Variable Task](/reference-docs/set-variable-task.html)     |\n| Wait                             | [Wait Task](/reference-docs/wait-task.html)                 |\n"
  },
  {
    "path": "docs/docs/configuration/systask.md",
    "content": "# System Tasks\n\nSystem Tasks (Workers) are built-in tasks that are general purpose and re-usable. They run on the Conductor servers.\nSuch tasks allow you to get started without having to write custom workers.\n\n## Available System Tasks\n\nConductor has the following set of system tasks available.\n\n| Task                  | Description                                            | Use Case                                                                           |\n|-----------------------|--------------------------------------------------------|------------------------------------------------------------------------------------|\n| Event Publishing      | [Event Task](/reference-docs/event-task.html)          | External eventing system integration. e.g. amqp, sqs, nats                         |\n| HTTP                  | [HTTP Task](/reference-docs/http-task.html)            | Invoke any HTTP(S) endpoints                                                       |\n| Inline Code Execution | [Inline Task](/reference-docs/inline-task.html)        | Execute arbitrary lightweight javascript code                                      |\n| JQ Transform          | [JQ Task](/reference-docs/json-jq-transform-task.html) | Use <a href=\"https://github.com/stedolan/jq\">JQ</a> to transform task input/output |\n| Kafka Publish         | [Kafka Task](/reference-docs/kafka-publish-task.html)  | Publish messages to Kafka                                                          |\n\n| Name                     | Description                                                                                                                               |\n|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|\n| joinOn                   | List of task reference names, which the EXCLUSIVE_JOIN will lookout for to capture output. From above example, this could be [\"T2\", \"T3\"] |\n| defaultExclusiveJoinTask | Task reference name, whose output should be used incase the decision case is undefined. From above example, this could be [\"T1\"]          |\n\n\n**Example**\n\n``` json\n{\n  \"name\": \"exclusive_join\",\n  \"taskReferenceName\": \"exclusiveJoin\",\n  \"type\": \"EXCLUSIVE_JOIN\",\n  \"joinOn\": [\n    \"task2\",\n    \"task3\"\n  ],\n  \"defaultExclusiveJoinTask\": [\n    \"task1\"\n  ]\n}\n```\n\n\n## Wait\nA wait task is implemented as a gate that remains in ```IN_PROGRESS``` state unless marked as ```COMPLETED``` or ```FAILED``` by an external trigger.\nTo use a wait task, set the task type as ```WAIT```\n\n**Parameters:**\nNone required.\n\n**External Triggers for Wait Task**\n\nTask Resource endpoint can be used to update the status of a task to a terminate state. \n\nContrib module provides SQS integration where an external system can place a message in a pre-configured queue that the server listens on.  As the messages arrive, they are marked as ```COMPLETED``` or ```FAILED```.  \n\n**SQS Queues**\n\n* SQS queues used by the server to update the task status can be retrieve using the following API:\n```\nGET /queue\n```\n* When updating the status of the task, the message needs to conform to the following spec:\n\t* \tMessage has to be a valid JSON string.\n\t*  The message JSON should contain a key named ```externalId``` with the value being a JSONified string that contains the following keys:\n\t\t*  ```workflowId```: Id of the workflow\n\t\t*  ```taskRefName```: Task reference name that should be updated.\n\t*  Each queue represents a specific task status and tasks are marked accordingly.  e.g. message coming to a ```COMPLETED``` queue marks the task status as ```COMPLETED```.\n\t*  Tasks' output is updated with the message.\n\n**Example SQS Payload:**\n\n```json\n{\n  \"some_key\": \"valuex\",\n  \"externalId\": \"{\\\"taskRefName\\\":\\\"TASK_REFERENCE_NAME\\\",\\\"workflowId\\\":\\\"WORKFLOW_ID\\\"}\"\n}\n```\n\n\n## Dynamic Task\n\nDynamic Task allows to execute one of the registered Tasks dynamically at run-time. It accepts the task name to execute in inputParameters.\n\n**Parameters:**\n\n|name|description|\n|---|---|\n| dynamicTaskNameParam|Name of the parameter from the task input whose value is used to schedule the task.  e.g. if the value of the parameter is ABC, the next task scheduled is of type 'ABC'.|\n\n**Example**\n``` json\n{\n  \"name\": \"user_task\",\n  \"taskReferenceName\": \"t1\",\n  \"inputParameters\": {\n    \"files\": \"${workflow.input.files}\",\n    \"taskToExecute\": \"${workflow.input.user_supplied_task}\"\n  },\n  \"type\": \"DYNAMIC\",\n  \"dynamicTaskNameParam\": \"taskToExecute\"\n}\n```\nIf the workflow is started with input parameter user_supplied_task's value as __user_task_2__, Conductor will schedule __user_task_2__ when scheduling this dynamic task.\n\n## Inline Task\n\nInline Task helps execute ad-hoc logic at Workflow run-time, using any evaluator engine. Supported evaluators \nare `value-param` evaluator which simply translates the input parameter to output and `javascript` evaluator that \nevaluates Javascript expression.\n\nThis is particularly helpful in running simple evaluations in Conductor server, over creating Workers.\n\n**Parameters:**\n\n|name|type|description|notes|\n|---|---|---|---|\n|evaluatorType|String|Type of the evaluator. Supported evaluators: `value-param`, `javascript` which evaluates javascript expression.|\n|expression|String|Expression associated with the type of evaluator. For `javascript` evaluator, Javascript evaluation engine is used to evaluate expression defined as a string. Must return a value.|Must be non-empty.|\n\nBesides `expression`, any value is accessible as `$.value` for the `expression` to evaluate.\n\n**Outputs:**\n\n|name|type|description|\n|---|---|---|\n|result|Map|Contains the output returned by the evaluator based on the `expression`|\n\nThe task output can then be referenced in downstream tasks like:\n```\"${inline_test.output.result.testvalue}\"```\n\n**Example**\n``` json\n{\n  \"name\": \"INLINE_TASK\",\n  \"taskReferenceName\": \"inline_test\",\n  \"type\": \"INLINE\",\n  \"inputParameters\": {\n      \"inlineValue\": \"${workflow.input.inlineValue}\",\n      \"evaluatorType\": \"javascript\",\n      \"expression\": \"function scriptFun(){if ($.inlineValue == 1){ return {testvalue: true} } else { return \n      {testvalue: false} }} scriptFun();\"\n  }\n}\n```\n\n## Terminate Task\n\nTask that can terminate a workflow with a given status and modify the workflow's output with a given parameter. It can act as a \"return\" statement for conditions where you simply want to terminate your workflow.\n\nFor example, if you have a decision where the first condition is met, you want to execute some tasks, otherwise you want to finish your workflow.\n\n**Parameters:**\n\n|name|type| description                                       | notes                   |\n|---|---|---------------------------------------------------|-------------------------|\n|terminationStatus|String| can only accept \"COMPLETED\" or \"FAILED\"           | task cannot be optional |\n |terminationReason|String| reason for incompletion to be set in the workflow | optional                |\n|workflowOutput|Any| Expected workflow output | optional |\n\n**Outputs:**\n\n|name|type|description|\n|---|---|---|\n|output|Map|The content of `workflowOutput` from the inputParameters. An empty object if `workflowOutput` is not set.|\n\n```json\n{\n  \"name\": \"terminate\",\n  \"taskReferenceName\": \"terminate0\",\n  \"inputParameters\": {\n      \"terminationStatus\": \"COMPLETED\",\n\t  \"terminationReason\": \"\",\n      \"workflowOutput\": \"${task0.output}\"\n  },\n  \"type\": \"TERMINATE\",\n  \"startDelay\": 0,\n  \"optional\": false\n}\n```\n\n\n## Kafka Publish Task\n\nA kafka Publish task is used to push messages to another microservice via kafka\n\n**Parameters:**\n\nThe task expects an input parameter named ```kafka_request``` as part of the task's input with the following details:\n\n|name|description|\n|---|---|\n| bootStrapServers |bootStrapServers for connecting to given kafka.|\n|key|Key to be published|\n|keySerializer | Serializer used for serializing the key published to kafka.  One of the following can be set : <br/> 1. org.apache.kafka.common.serialization.IntegerSerializer<br/>2. org.apache.kafka.common.serialization.LongSerializer<br/>3. org.apache.kafka.common.serialization.StringSerializer. <br/>Default is String serializer  |\n|value| Value published to kafka|\n|requestTimeoutMs| Request timeout while publishing to kafka. If this value is not given the value is read from the property `kafka.publish.request.timeout.ms`. If the property is not set the value defaults to 100 ms |\n|maxBlockMs| maxBlockMs while publishing to kafka. If this value is not given the value is read from the property `kafka.publish.max.block.ms`. If the property is not set the value defaults to 500 ms |\n|headers|A map of additional kafka headers to be sent along with the request.|\n|topic|Topic to publish|\n\nThe producer created in the kafka task is cached. By default the cache size is 10 and expiry time is 120000 ms. To change the defaults following can be modified kafka.publish.producer.cache.size,kafka.publish.producer.cache.time.ms respectively.  \n\n**Kafka Task Output**\n\nTask status transitions to COMPLETED\n\n**Example**\n\nTask sample\n\n```json\n{\n  \"name\": \"call_kafka\",\n  \"taskReferenceName\": \"call_kafka\",\n  \"inputParameters\": {\n    \"kafka_request\": {\n      \"topic\": \"userTopic\",\n      \"value\": \"Message to publish\",\n      \"bootStrapServers\": \"localhost:9092\",\n      \"headers\": {\n  \t\"x-Auth\":\"Auth-key\"    \n      },\n      \"key\": \"123\",\n      \"keySerializer\": \"org.apache.kafka.common.serialization.IntegerSerializer\"\n    }\n  },\n  \"type\": \"KAFKA_PUBLISH\"\n}\n```\n\nThe task is marked as ```FAILED``` if the message could not be published to the Kafka queue. \n\n\n## Do While Task\n\nSequentially execute a list of task as long as a condition is true. The list of tasks is executed first, before the condition is\nchecked (even for the first iteration).\n\nWhen scheduled, each task of this loop will see its `taskReferenceName` concatenated with `__i`, with `i` being the\niteration number, starting at 1. Warning: `taskReferenceName` containing arithmetic operators must not be used.\n\nEach task output is stored as part of the `DO_WHILE` task, indexed by the iteration value (see example below), allowing\nthe condition to reference the output of a task for a specific iteration (eg. ```$.LoopTask['iteration]['first_task']```)\n\nThe `DO_WHILE` task is set to `FAILED` as soon as one of the loopTask fails. In such case retry, iteration starts from 1.\n\nLimitations:\n - Domain or isolation group execution is unsupported;\n - Nested `DO_WHILE` is unsupported;\n - `SUB_WORKFLOW` is unsupported;\n - Since loopover tasks will be executed in loop inside scope of parent do while task, crossing branching outside of DO_WHILE\n   task is not respected. Branching inside loopover task is supported.\n\n**Parameters:**\n\n|name|type|description|\n\n"
  },
  {
    "path": "docs/docs/configuration/taskdef.md",
    "content": "# Task Definition\n\nTasks are the building blocks of workflow in Conductor. A task can be an operator, system task or custom code written in any programming language.\n\nA typical Conductor workflow is a list of tasks that are executed until completion or the termination of the workflow.\n\nConductor maintains a registry of worker tasks.  A task MUST be registered before being used in a workflow.\n\n**Example**\n``` json\n{\n  \"name\": \"encode_task\",\n  \"retryCount\": 3,\n  \n  \"timeoutSeconds\": 1200,\n  \"pollTimeoutSeconds\": 3600,\n  \"inputKeys\": [\n    \"sourceRequestId\",\n    \"qcElementType\"\n  ],\n  \"outputKeys\": [\n    \"state\",\n    \"skipped\",\n    \"result\"\n  ],\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 600,\n  \"responseTimeoutSeconds\": 1200,\n  \"concurrentExecLimit\": 100,\n  \"rateLimitFrequencyInSeconds\": 60,\n  \"rateLimitPerFrequency\": 50,\n  \"ownerEmail\": \"foo@bar.com\",\n  \"description\": \"Sample Encoding task\"\n}\n```\n\n| Field                                              | Description                                                                                                                                                                                                                                     | Notes                                    |\n|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|\n| name                                               | Task Type. Unique name of the Task that resonates with it's function.                                                                                                                                                                           | Unique                                   |\n| description                                        | Description of the task                                                                                                                                                                                                                         | optional                                 |\n| retryCount                                         | No. of retries to attempt when a Task is marked as failure                                                                                                                                                                                      | defaults to 3                            |\n| retryLogic                                         | Mechanism for the retries                                                                                                                                                                                                                       | [Retry Logic values](#retry-logic)       |\n| retryDelaySeconds                                  | Time to wait before retries                                                                                                                                                                                                                     | defaults to 60 seconds                   |\n| timeoutPolicy                                      | Task's timeout policy                                                                                                                                                                                                                           | [timeout policy values](#timeout-policy) |\n| timeoutSeconds                                     | Time in seconds, after which the task is marked as `TIMED_OUT` if not completed after transitioning to `IN_PROGRESS` status for the first time                                                                                                  | No timeouts if set to 0                  |\n| pollTimeoutSeconds                                 | Time in seconds, after which the task is marked as `TIMED_OUT` if not polled by a worker                                                                                                                                                        | No timeouts if set to 0                  |\n| responseTimeoutSeconds                             | Must be greater than 0 and less than timeoutSeconds. The task is rescheduled if not updated with a status after this time (heartbeat mechanism). Useful when the worker polls for the task but fails to complete due to errors/network failure. | defaults to 3600                         |\n| backoffScaleFactor                                 | Must be greater than 0. Scale factor for linearity of the backoff                                                                                                                                                                               | defaults to 1                            |\n| inputKeys                                          | Array of keys of task's expected input.  Used for documenting task's input. See [Using inputKeys and outputKeys](#using-inputkeys-and-outputkeys).                                                                                              | optional                                 |\n| outputKeys                                         | Array of keys of task's expected output.  Used for documenting task's output                                                                                                                                                                    | optional                                 |\n| inputTemplate                                      | See [Using inputTemplate](#using-inputtemplate) below.                                                                                                                                                                                          | optional                                 |\n| concurrentExecLimit                                | Number of tasks that can be executed at any given time.                                                                                                                                                                                         | optional                                 |\n| rateLimitFrequencyInSeconds, rateLimitPerFrequency | See [Task Rate limits](#task-rate-limits) below.                                                                                                                                                                                                | optional                                 |\n\n\n### Retry Logic\n\n* FIXED : Reschedule the task after the ```retryDelaySeconds```\n* EXPONENTIAL_BACKOFF : Reschedule after ```retryDelaySeconds  2^(attemptNumber)```\n* LINEAR_BACKOFF : Reschedule after ```retryDelaySeconds * backoffRate * attemptNumber```\n \n### Timeout Policy\n\n* RETRY : Retries the task again\n* TIME_OUT_WF : Workflow is marked as TIMED_OUT and terminated\n* ALERT_ONLY : Registers a counter (task_timeout)\n\n### Task Concurrent Execution Limits\n\n* `concurrentExecLimit` limits the number of simultaneous Task executions at any point.  \n**Example:**  \nIf you have 1000 task executions waiting in the queue, and 1000 workers polling this queue for tasks, but if you have set `concurrentExecLimit` to 10, only 10 tasks would be given to workers (which would lead to starvation). If any of the workers finishes execution, a new task(s) will be removed from the queue, while still keeping the current execution count to 10.\n\n### Task Rate limits\n\n> Note: Rate limiting is only supported for the Redis-persistence module and is not available with other persistence layers.\n\n* `rateLimitFrequencyInSeconds` and `rateLimitPerFrequency` should be used together.\n* `rateLimitFrequencyInSeconds` sets the \"frequency window\", i.e the `duration` to be used in `events per duration`. Eg: 1s, 5s, 60s, 300s etc.\n* `rateLimitPerFrequency`defines the number of Tasks that can be given to Workers per given \"frequency window\".  \n\n**Example:**  \nLet's set `rateLimitFrequencyInSeconds = 5`, and `rateLimitPerFrequency = 12`. This means, our frequency window is of 5 seconds duration, and for each frequency window, Conductor would only give 12 tasks to workers. So, in a given minute, Conductor would only give 12*(60/5) = 144 tasks to workers irrespective of the number of workers that are polling for the task.  \nNote that unlike `concurrentExecLimit`, rate limiting doesn't take into account tasks already in progress/completed. Even if all the previous tasks are executed within 1 sec, or would take a few days, the new tasks are still given to workers at configured frequency, 144 tasks per minute in above example.   \n\n\n### Using inputKeys and outputKeys\n\n* `inputKeys` and `outputKeys` can be considered as parameters and return values for the Task. \n* Consider the task Definition as being represented by an interface: ```(value1, value2 .. valueN) someTaskDefinition(key1, key2 .. keyN);```\n* However, these parameters are not strictly enforced at the moment. Both `inputKeys` and `outputKeys` act as a documentation for task re-use. The tasks in workflow need not define all of the keys in the task definition.\n* In the future, this can be extended to be a strict template that all task implementations must adhere to, just like interfaces in programming languages.\n\n### Using inputTemplate\n\n* `inputTemplate` allows to define default values, which can be overridden by values provided in Workflow.\n* Eg: In your Task Definition, you can define your inputTemplate as:\n\n```json\n\"inputTemplate\": {\n    \"url\": \"https://some_url:7004\"\n}\n```\n\n* Now, in your workflow Definition, when using above task, you can use the default `url` or override with something else in the task's `inputParameters`.\n\n```json\n\"inputParameters\": {\n    \"url\": \"${workflow.input.some_new_url}\"\n}\n```\n"
  },
  {
    "path": "docs/docs/configuration/taskdomains.md",
    "content": "# Task Domains\nTask domains helps support task development. The idea is same “task definition” can be implemented in different “domains”. A domain is some arbitrary name that the developer controls. So when the workflow is started, the caller can specify, out of all the tasks in the workflow, which tasks need to run in a specific domain, this domain is then used to poll for task on the client side to execute it.  \n\nAs an example if a workflow (WF1) has 3 tasks T1, T2, T3. The workflow is deployed and working fine, which means there are T2 workers polling and executing. If you modify T2 and run it locally there is no guarantee that your modified T2 worker will get the task that you are looking for as it coming from the general T2 queue. “Task Domain” feature solves this problem by splitting the T2 queue by domains, so when the app polls for task T2 in a specific domain, it get the correct task.\n\nWhen starting a workflow multiple domains can be specified as a fall backs, for example \"domain1,domain2\". Conductor keeps track of last polling time for each task, so in this case it checks if the there are any active workers for \"domain1\" then the task is put in \"domain1\", if not then the same check is done for the next domain in sequence \"domain2\" and so on.\n\nIf no workers are active for the domains provided:\n\n- If `NO_DOMAIN` is provided as last token in list of domains, then no domain is set.\n- Else, task will be added to last inactive domain in list of domains, hoping that workers would soon be available for that domain.\n\nAlso, a `*` token can be used to apply domains for all tasks. This can be overridden by providing task specific mappings along with `*`. \n\nFor example, the below configuration:\n\n```json\n\"taskToDomain\": {\n  \"*\": \"mydomain\",\n  \"some_task_x\":\"NO_DOMAIN\",\n  \"some_task_y\": \"someDomain, NO_DOMAIN\",\n  \"some_task_z\": \"someInactiveDomain1, someInactiveDomain2\"\n}\n```\n\n- puts `some_task_x` in default queue (no domain).\n- puts `some_task_y` in `someDomain` domain, if available or in default otherwise.\n- puts `some_task_z` in `someInactiveDomain2`, even though workers are not available yet.\n- and puts all other tasks in `mydomain` (even if workers are not available).\n\n\n<b>Note</b> that this \"fall back\" type domain strings can only be used when starting the workflow, when polling from the client only one domain is used. Also, `NO_DOMAIN` token should be used last.\n\n## How to use Task Domains\n### Change the poll call\nThe poll call must now specify the domain. \n\n#### Java Client\nIf you are using the java client then a simple property change will force  TaskRunnerConfigurer to pass the domain to the poller.\n```\n\tconductor.worker.T2.domain=mydomain //Task T2 needs to poll for domain \"mydomain\"\n```\n#### REST call\n`GET /tasks/poll/batch/T2?workerid=myworker&domain=mydomain`\n`GET /tasks/poll/T2?workerid=myworker&domain=mydomain`\n\n### Change the start workflow call\nWhen starting the workflow, make sure the task to domain mapping is passes\n\n#### Java Client\n```\n\tMap<String, Object> input = new HashMap<>();\n\tinput.put(\"wf_input1\", \"one”);\n\n\tMap<String, String> taskToDomain = new HashMap<>();\n\ttaskToDomain.put(\"T2\", \"mydomain\");\n\t\n\t// Other options ...\n\t// taskToDomain.put(\"*\", \"mydomain, NO_DOMAIN\")\n\t// taskToDomain.put(\"T2\", \"mydomain, fallbackDomain1, fallbackDomain2\")\n\t\n\tStartWorkflowRequest swr = new StartWorkflowRequest();\n\tswr.withName(“myWorkflow”)\n\t\t.withCorrelationId(“corr1”)\n\t\t.withVersion(1)\n\t\t.withInput(input)\n\t\t.withTaskToDomain(taskToDomain);\n\t\n\twfclient.startWorkflow(swr);\n\n```\n\n#### REST call\n`POST /workflow`\n\n```json\n{\n  \"name\": \"myWorkflow\",\n  \"version\": 1,\n  \"correlatonId\": \"corr1\"\n  \"input\": {\n\t\"wf_input1\": \"one\"\n  },\n  \"taskToDomain\": {\n\t\"*\": \"mydomain\",\n\t\"some_task_x\":\"NO_DOMAIN\",\n    \"some_task_y\": \"someDomain, NO_DOMAIN\"\n  }\n}\n\n```\n\n"
  },
  {
    "path": "docs/docs/configuration/workerdef.md",
    "content": "# Worker Definition\n\nA worker is responsible for executing a task.  Operator and System tasks are handled by the Conductor server, while user \ndefined tasks needs to have a worker created that awaits the work to be scheduled by the server for it to be executed.\nWorkers can be implemented in any language, and Conductor provides support for Java, Golang and Python worker framework that provides features such as \npolling threads, metrics and server communication that makes creating workers easy.\n\nEach worker embodies Microservice design pattern and follows certain basic principles:\n\n1. Workers are stateless and do not implement a workflow specific logic.  \n2. Each worker executes a very specific task and produces well defined output given specific inputs.\n3. Workers are meant to be idempotent (or should handle cases where the task that partially executed gets rescheduled due to timeouts etc.)\n4. Workers do not implement the logic to handle retries etc, that is taken care by the Conductor server.\n \n"
  },
  {
    "path": "docs/docs/configuration/workflowdef.md",
    "content": "# Workflow Definition\n\n## What are Workflows?\n\nAt a high level, a workflow is the Conductor primitive that encompasses the definition and flow of your business logic.\nA workflow is a collection (graph) of tasks and sub-workflows. A workflow definition specifies the order of execution of\nthese [Tasks](taskdef.md). It also specifies how data/state is passed from one task to the other (using the\ninput/output parameters). These are then combined to give you the final result. This orchestration of Tasks can\nhappen in a hybrid ecosystem that includes microservices, serverless functions, and monolithic applications. They can\nalso span across any public cloud and on-premise data center footprints. In addition, the orchestration of tasks can be\nacross any programming language since Conductor is also language agnostic.\n\nOne key benefit of this approach is that you can build a complex application using simple and granular tasks that do not\nneed to be aware of or keep track of the state of your application's execution flow. Conductor keeps track of the state,\ncalls tasks in the right order (sequentially or in parallel, as defined by you), retry calls if needed, handle failure\nscenarios gracefully, and outputs the final result.\n\nLeveraging workflows in Conductor enables developers to truly focus on their core mission - building their application\ncode in the languages of their choice. Conductor does the heavy lifting associated with ensuring high\nreliability, transactional consistency, and long durability of their workflows. Simply put, wherever your application's\ncomponent lives and whichever languages they were written in, you can build a workflow in Conductor to orchestrate their\nexecution in a reliable & scalable manner.\n\n## What does a Workflow look like?\n\nLet's start with a basic workflow and understand what are the different aspects of it. In particular, we will talk about\ntwo stages of a workflow, *defining* a workflow and *executing* a workflow\n\n### Simple Workflow Example\n\nAssume your business logic is to simply to get some shipping information and then do the shipping. You start by\nlogically partitioning them into two tasks:\n\n* **shipping_info**\n* **shipping_task**\n\nFirst we would build these two task definitions. Let's assume that ```shipping info``` takes an account number, and returns a name and address.\n\n**Example**\n```json\n{\n  \"name\": \"mail_a_box\",\n  \"description\": \"shipping Workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"shipping_info\",\n      \"taskReferenceName\": \"shipping_info_ref\",\n      \"inputParameters\": {\n        \"account\": \"${workflow.input.accountNumber}\"\n      },\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"shipping_task\",\n      \"taskReferenceName\": \"shipping_task_ref\",\n      \"inputParameters\": {\n        \"name\": \"${shipping_info_ref.output.name}\",\n\t\t\"streetAddress\": \"${shipping_info_ref.output.streetAddress}\",\n\t\t\"city\": \"${shipping_info_ref.output.city}\",\n\t\t\"state\": \"${shipping_info_ref.output.state}\",\n\t\t\"zipcode\": \"${shipping_info_ref.output.zipcode}\",\n      },\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"outputParameters\": {\n    \"trackingNumber\": \"${shipping_task_ref.output.trackinNumber}\"\n  },\n  \"failureWorkflow\": \"shipping_issues\",\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": true,\n  \"ownerEmail\": \"conductor@example.com\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"variables\": {},\n  \"inputTemplate\": {}\n}\n```\n\nThe mail_a_box workflow has 2 tasks:\n 1. The first task takes the provided account number, and outputs an address.  \n 2. The 2nd task takes the address info and generates a shipping label.\n \n Upon completion of the 2 tasks, the workflow outputs the tracking number generated in the 2nd task.  If the workflow fails, a second workflow named ```shipping_issues``` is run.\n\n## Fields in a Workflow\n\n| Field                         | Description                                                                                                                              | Notes                                                                                             |\n|:------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------|\n| name                          | Name of the workflow                                                                                                                     ||\n| description                   | Description of the workflow                                                                                                              | optional                                                                                          |\n| version                       | Numeric field used to identify the version of the schema.  Use incrementing numbers                                                      | When starting a workflow execution, if not specified, the definition with highest version is used |\n| tasks                         | An array of task definitions.                                                                                                            | [Task properties](#tasks-within-workflow)                                                         |\n| inputParameters               | List of input parameters. Used for documenting the required inputs to workflow                                                           | optional                                                                                          |\n| inputTemplate                 | Default input values. See [Using inputTemplate](#using-inputtemplate)                                                                    | optional                                                                                          |\n| outputParameters              | JSON template used to generate the output of the workflow                                                                                | If not specified, the output is defined as the output of the _last_ executed task                 |\n| failureWorkflow               | String; Workflow to be run on current Workflow failure. Useful for cleanup or post actions on failure.                                   | optional                                                                                          |\n| schemaVersion                 | Current Conductor Schema version. schemaVersion 1 is discontinued.                                                                       | Must be 2                                                                                         |\n| restartable                   | Boolean flag to allow Workflow restarts                                                                                                  | defaults to true                                                                                  |\n| workflowStatusListenerEnabled | If true, every workflow that gets terminated or completed will send a notification. See [workflow notifictions](#workflow-notifications) | optional (false by default)                                                                       |\n\n## Tasks within Workflow\n```tasks``` property in a workflow execution defines an array of tasks to be executed in that order.\n\n| Field             | Description                                                                                                                                    | Notes                                                                   |\n|:------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------|\n| name              | Name of the task. MUST be registered as a task with Conductor before starting the workflow                                                     ||\n| taskReferenceName | Alias used to refer the task within the workflow.  MUST be unique within workflow.                                                             ||\n| type              | Type of task. SIMPLE for tasks executed by remote workers, or one of the system task types                                                     ||\n| description       | Description of the task                                                                                                                        | optional                                                                |\n| optional          | true or false.  When set to true - workflow continues even if the task fails.  The status of the task is reflected as `COMPLETED_WITH_ERRORS` | Defaults to `false`                                                     |\n| inputParameters   | JSON template that defines the input given to the task                                                                                         | See [Wiring Inputs and Outputs](#wiring-inputs-and-outputs) for details |\n| domain            | See [Task Domains](/configuration/taskdomains.html) for more information.                                                                 | optional                                                                |\n\nIn addition to these parameters, System Tasks have their own parameters. Checkout [System Tasks](/configuration/systask.html) for more information.\n\n## Wiring Inputs and Outputs\n\nWorkflows are supplied inputs by client when a new execution is triggered. \nWorkflow input is a JSON payload that is available via ```${workflow.input...}``` expressions. \n\nEach task in the workflow is given input based on the ```inputParameters``` template configured in workflow definition.  ```inputParameters``` is a JSON fragment with value containing parameters for mapping values from input or output of a workflow or another task during the execution.\n\nSyntax for mapping the values follows the pattern as: \n\n__${SOURCE.input/output.JSONPath}__\n\n| field        | description                                                              |\n|--------------|--------------------------------------------------------------------------|\n| SOURCE       | can be either \"workflow\" or any of the task reference name               |\n| input/output | refers to either the input or output of the source                       |\n| JSONPath     | JSON path expression to extract JSON fragment from source's input/output |\n\n\n!!! note \"JSON Path Support\"\n\tConductor supports [JSONPath](http://goessner.net/articles/JsonPath/) specification and uses Java implementation from [here](https://github.com/jayway/JsonPath).\n\n!!! note \"Escaping expressions\"\n\tTo escape an expression, prefix it with an extra _$_ character (ex.: ```$${workflow.input...}```).\n\n**Example**\n\nConsider a task with input configured to use input/output parameters from workflow and a task named __loc_task__.\n\n```json\n{\n  \"inputParameters\": {\n    \"movieId\": \"${workflow.input.movieId}\",\n    \"url\": \"${workflow.input.fileLocation}\",\n    \"lang\": \"${loc_task.output.languages[0]}\",\n    \"http_request\": {\n      \"method\": \"POST\",\n      \"url\": \"http://example.com/${loc_task.output.fileId}/encode\",\n      \"body\": {\n        \"recipe\": \"${workflow.input.recipe}\",\n        \"params\": {\n          \"width\": 100,\n          \"height\": 100\n        }\n      },\n      \"headers\": {\n        \"Accept\": \"application/json\",\n        \"Content-Type\": \"application/json\"\n      }\n    }\n  }\n}\n```\n\nConsider the following as the _workflow input_\n\n```json\n{\n  \"movieId\": \"movie_123\",\n  \"fileLocation\":\"s3://moviebucket/file123\",\n  \"recipe\":\"png\"\n}\n```\nAnd the output of the _loc_task_ as the following;\n\n```json\n{\n  \"fileId\": \"file_xxx_yyy_zzz\",\n  \"languages\": [\"en\",\"ja\",\"es\"]\n}\n```\n\nWhen scheduling the task, Conductor will merge the values from workflow input and loc_task's output and create the input to the task as follows:\n\n```json\n{\n  \"movieId\": \"movie_123\",\n  \"url\": \"s3://moviebucket/file123\",\n  \"lang\": \"en\",\n  \"http_request\": {\n    \"method\": \"POST\",\n    \"url\": \"http://example.com/file_xxx_yyy_zzz/encode\",\n    \"body\": {\n      \"recipe\": \"png\",\n      \"params\": {\n        \"width\": 100,\n        \"height\": 100\n      }\n    },\n    \"headers\": {\n    \t\"Accept\": \"application/json\",\n    \t\"Content-Type\": \"application/json\"\n    }\n  }\n}\n```\n\n### Using inputTemplate\n\n* `inputTemplate` allows to define default values, which can be overridden by values provided in Workflow.\n* Eg: In your Workflow Definition, you can define your inputTemplate as:\n\n```json\n\"inputTemplate\": {\n    \"url\": \"https://some_url:7004\"\n}\n```\n\nAnd `url` would be `https://some_url:7004` if no `url` was provided as input to your workflow.\n\n## Workflow notifications\n\nConductor can be configured to publish notifications to external systems upon completion/termination of workflows. See [extending conductor](/extend.html) for details.\n"
  },
  {
    "path": "docs/docs/css/custom.css",
    "content": ":root {\n  /*--main-text-color: #212121;*/\n  --brand-blue: #1976d2;\n  --brand-dark-blue: #242A36;\n  --caption-color: #4f4f4f;\n  --brand-lt-blue: #f0f5fb;\n  --brand-gray: rgb(118, 118, 118);\n  --brand-lt-gray: rgb(203,204,207);\n  --brand-red: #e50914;\n}\nbody {\n  color: var(--brand-dark-blue);\n  font-family: \"Roboto\", sans-serif;\n  font-weight: 400;\n}\n\nbody::before {\n  background: none;\n  display: none;\n}\nbody > .container {\n  padding-top: 30px;\n}\n\n.bg-primary {\n  background: #fff !important;\n}\n\n/* Navbar */\n.navbar {\n  box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%);\n  padding-left: 30px;\n  padding-right: 30px;\n  height: 80px;\n}\n.navbar-brand {\n  background-image: url(/img/logo.svg);\n  background-size: cover;\n  color: transparent !important;\n  padding: 0;\n  text-shadow: none;\n  margin-top: -6px;\n  height: 37px;\n  width: 175px;\n}\n.navbar-nav {\n  margin-left: 50px;\n}\n.navbar-nav > .navitem, .navbar-nav > .dropdown {\n  margin-left: 30px;\n}\n.navbar-nav > li .nav-link{\n  font-size: 15px;\n}\n\n.navbar-nav .nav-link {\n  color: #242A36 !important;\n  font-family: \"Inter\";\n  font-weight: 700; \n}\n\n.navbar-nav.ml-auto > li:first-child {\n  display: none;\n}\n.navbar-nav.ml-auto .nav-link{\n  font-size: 0px;\n}\n.navbar-nav.ml-auto .nav-link .fa{\n  font-size: 30px;\n}\n.navbar-nav .dropdown-item {\n  color: var(--brand-dark-blue);\n  font-family: \"Inter\";\n  font-weight: 500; \n  font-size: 14px;\n  background-color: transparent;\n}\n.navbar-nav .dropdown-menu > li:hover {\n  background-color: var(--brand-blue);\n}\n.navbar-nav .dropdown-menu > li:hover > .dropdown-item {\n  color: #fff;\n}\n.navbar-nav .dropdown-submenu:hover > .dropdown-item {\n  background-color: var(--brand-blue);\n}\n\n\n.navbar-nav .dropdown-menu li {\n  margin: 0px;\n  padding-top: 5px;\n  padding-bottom: 5px;\n}\n.navbar-nav .dropdown-item.active {\n  background-color: transparent;\n}\n\n.brand-darkblue {\n  background: #242A36 !important;\n}\n\n.brand-gray {\n  background: rgb(245,245,245);\n}\n.brand-blue {\n  background: #1976D2;\n}\n.brand-white {\n  background: #fff;\n}\n.logo {\n  height: 444px;\n}\n\n/* Fonts */\nh1, h2, h3, h4, h5, h6 {\n  color: var(--brand-dark-blue);\n  margin-bottom: 20px;\n}\nh1:first-child {\n  margin-top: 0;\n}\n\nh1 {\n  font-family: \"Inter\", sans-serif;\n  font-size: 32px;\n  font-weight: 700;\n  margin-top: 50px;\n}\n\nh2 {\n  font-family: \"Inter\", sans-serif;\n  font-size: 24px;\n  font-weight: 700;\n  margin-top: 40px;\n}\n\nh3 {\n  font-family: \"Roboto\", sans-serif;\n  font-size: 20px;\n  font-weight: 500;\n  margin-top: 30px;\n}\n\nh4 {\n  font-family: \"Roboto\", sans-serif;\n  font-size: 18px;\n  font-weight: 400;\n  margin-top: 20px;\n}\n\n.main li {\n  margin-bottom: 15px;\n}\n\n\n.btn {\n  font-family: \"Roboto\", sans-serif;\n  font-size: 14px;\n}\n.btn-primary {\n  background: #1976D2;\n  border: none;\n}\n\n.hero {\n  padding-top: 100px;\n  padding-bottom: 100px;\n}\n\n.hero .heading {\n  font-size: 56px;\n  font-weight: 900;\n  line-height: 68px;\n}\n\n.hero .btn {\n  font-size: 16px;\n  padding: 10px 20px;\n}\n\n.hero .illustration {\n  margin-left: 35px;\n}\n\n\n.bullets .heading, .module .heading {\n  font-family: \"Inter\", sans-serif;\n  font-size: 26px;\n  font-weight: 700;\n}\n.bullets .row {\n  margin-bottom: 60px;\n}\n.bullets .caption {\n  padding-top: 10px;\n  padding-right: 30px;\n}\n.icon {\n  height: 25px;\n  margin-right: 5px;\n  vertical-align: -3px;\n}\n\n.caption {\n  font-weight: 400;\n  font-size: 17px;\n  line-height: 24px;\n  color: var(--caption-color);\n}\n\n.module {\n  margin-top: 80px;\n  margin-bottom: 80px;\n  padding-top: 50px;\n  padding-bottom: 50px;\n}\n\n.module .caption {\n  padding-top: 10px;\n  padding-right: 80px;\n}\n.module .screenshot {\n  width: 600px;\n  height: 337px;\n  box-shadow:inset 0 1px 0 rgba(255,255,255,.6), 0 22px 70px 4px rgba(0,0,0,0.56), 0 0 0 1px rgba(0, 0, 0, 0.0);\n  border-radius: 5px;\n  background-size: cover;\n}\n\n/* Footer */\nfooter {\n  margin: 0px;\n  padding: 0px !important;\n  text-align: left;\n  font-weight: 400;\n}\n.footer {\n  background-color: var(--brand-dark-blue);\n  padding: 50px 0px;\n  color: #fff;\n  font-size: 14px;\n  margin-top: 50px;\n}\n.footer a {\n  color: var(--brand-lt-gray);\n}\n.footer .subhead {  \n  font-weight: 700;\n  color: #fff;\n  font-size: 15px;\n  margin-bottom: 10px;\n}\n.footer .red {\n  color: var(--brand-red);\n}\n.footer .fr {\n  text-align: right;\n}\n\n/* TOC menu */\n.toc ul {\n  list-style: none;\n  padding: 0px;\n}\n.toc > ul > li li {\n  padding-left: 15px;\n  font-weight: 400;\n  font-size: 14px;\n}\n.toc > ul > li {\n  font-size: 15px; \n  font-weight: 500;\n}\n.toc .toc-link {\n  margin-bottom: 5px;\n  display: block;\n  color: var(--brand-dark-blue);\n}\n.toc .toc-link.active {\n  font-weight: 700;\n}\n\n/* Homepage Overrides */\n.homepage > .container {\n  max-width: none;\n}\n.homepage .toc {\n  display: none;\n}\n\n/* Comparison block */\n.compare {\n  background-color: var(--brand-lt-blue);\n  padding-top: 80px;\n  padding-bottom: 80px;\n  margin: 0px -15px;\n}\n.compare .heading {\n  margin-bottom: 30px;\n  margin-top: 0px;\n}\n.compare .bubble {\n  background: #fff;\n  border-radius: 10px;\n  padding: 30px;\n  height: 100%;\n}\n\n.compare .caption {\n  font-size: 15px;\n  line-height: 22px;\n}\n"
  },
  {
    "path": "docs/docs/extend.md",
    "content": "# Extending Conductor\n\n## Backend\nConductor provides a pluggable backend.  The current implementation uses Dynomite.\n\nThere are 4 interfaces that need to be implemented for each backend:\n\n```java\n//Store for workflow and task definitions\ncom.netflix.conductor.dao.MetadataDAO\n```\n\n```java\n//Store for workflow executions\ncom.netflix.conductor.dao.ExecutionDAO\n```\n\n```java\n//Index for workflow executions\ncom.netflix.conductor.dao.IndexDAO\n```\n\n```java\n//Queue provider for tasks\ncom.netflix.conductor.dao.QueueDAO\n```\n\nIt is possible to mix and match different implementations for each of these.  \nFor example, SQS for queueing and a relational store for others.\n\n\n## System Tasks\nTo create system tasks follow the steps below:\n\n* Extend ```com.netflix.conductor.core.execution.tasks.WorkflowSystemTask```\n* Instantiate the new class as part of the startup (eager singleton)\n* Implement the ```TaskMapper``` [interface](https://github.com/Netflix/conductor/blob/master/core/src/main/java/com/netflix/conductor/core/execution/mapper/TaskMapper.java)\n* Add this implementation to the map identified by [TaskMappers](https://github.com/Netflix/conductor/blob/master/core/src/main/java/com/netflix/conductor/core/config/CoreModule.java#L70)\n\n## External Payload Storage\nTo configure conductor to externalize the storage of large payloads:\n\n* Implement the `ExternalPayloadStorage` [interface](https://github.com/Netflix/conductor/blob/master/common/src/main/java/com/netflix/conductor/common/utils/ExternalPayloadStorage.java).\n* Add the storage option to the enum [here](https://github.com/Netflix/conductor/blob/master/server/src/main/java/com/netflix/conductor/bootstrap/ModulesProvider.java#L39).\n* Set this JVM system property ```workflow.external.payload.storage``` to the value of the enum element added above.\n* Add a binding similar to [this](https://github.com/Netflix/conductor/blob/master/server/src/main/java/com/netflix/conductor/bootstrap/ModulesProvider.java#L120-L127).\n\n## Workflow Status Listener\nTo provide a notification mechanism upon completion/termination of workflows:\n\n* Implement the ```WorkflowStatusListener``` [interface](https://github.com/Netflix/conductor/blob/master/core/src/main/java/com/netflix/conductor/core/execution/WorkflowStatusListener.java)\n* This can be configured to plugin custom notification/eventing upon workflows reaching a terminal state.\n\n## Locking Service\n\nBy default, Conductor Server module loads Zookeeper lock module. If you'd like to provide your own locking implementation module, \nfor eg., with Dynomite and Redlock:\n\n* Implement ```Lock``` interface.\n* Add a binding similar to [this](https://github.com/Netflix/conductor/blob/master/server/src/main/java/com/netflix/conductor/bootstrap/ModulesProvider.java#L115-L129)\n* Enable locking service: ```conductor.app.workflowExecutionLockEnabled: true```\n"
  },
  {
    "path": "docs/docs/externalpayloadstorage.md",
    "content": "# External Payload Storage\n\n!!!warning\n    The external payload storage is currently only implemented to be used to by the Java client. Client libraries in other languages need to be modified to enable this.  \n    Contributions are welcomed.\n\n## Context\nConductor can be configured to enforce barriers on the size of workflow and task payloads for both input and output.  \nThese barriers can be used as safeguards to prevent the usage of conductor as a data persistence system and to reduce the pressure on its datastore.\n\n## Barriers\nConductor typically applies two kinds of barriers:\n\n* Soft Barrier\n* Hard Barrier\n\n\n#### Soft Barrier\n\nThe soft barrier is used to alleviate pressure on the conductor datastore. In some special workflow use-cases, the size of the payload is warranted enough to be stored as part of the workflow execution.  \nIn such cases, conductor externalizes the storage of such payloads to S3 and uploads/downloads to/from S3 as needed during the execution. This process is completely transparent to the user/worker process.  \n\n\n#### Hard Barrier\nThe hard barriers are enforced to safeguard the conductor backend from the pressure of having to persist and deal with voluminous data which is not essential for workflow execution.\nIn such cases, conductor will reject such payloads and will terminate/fail the workflow execution with the reasonForIncompletion set to an appropriate error message detailing the payload size.\n\n## Usage\n\n### Barriers setup\n\nSet the following properties to the desired values in the JVM system properties:\n\n| Property | Description | default value |\n| -- | -- | -- |\n| conductor.app.workflowInputPayloadSizeThreshold | Soft barrier for workflow input payload in KB | 5120 |\n| conductor.app.maxWorkflowInputPayloadSizeThreshold | Hard barrier for workflow input payload in KB | 10240 |\n| conductor.app.workflowOutputPayloadSizeThreshold | Soft barrier for workflow output payload in KB | 5120 |\n| conductor.app.maxWorkflowOutputPayloadSizeThreshold | Hard barrier for workflow output payload in KB | 10240 |\n| conductor.app.taskInputPayloadSizeThreshold | Soft barrier for task input payload in KB | 3072 |\n| conductor.app.maxTaskInputPayloadSizeThreshold | Hard barrier for task input payload in KB | 10240 |\n| conductor.app.taskOutputPayloadSizeThreshold | Soft barrier for task output payload in KB | 3072 |\n| conductor.app.maxTaskOutputPayloadSizeThreshold | Hard barrier for task output payload in KB | 10240 |\n\n### Amazon S3\n\nConductor provides an implementation of [Amazon S3](https://aws.amazon.com/s3/) used to externalize large payload storage.  \nSet the following property in the JVM system properties:\n```\nconductor.external-payload-storage.type=S3\n```\n\n!!!note\n    This [implementation](https://github.com/Netflix/conductor/blob/master/core/src/main/java/com/netflix/conductor/core/utils/S3PayloadStorage.java#L44-L45) assumes that S3 access is configured on the instance.\n\nSet the following properties to the desired values in the JVM system properties:\n\n| Property | Description | default value |\n| --- | --- | --- |\n| conductor.external-payload-storage.s3.bucketName | S3 bucket where the payloads will be stored | |\n| conductor.external-payload-storage.s3.signedUrlExpirationDuration | The expiration time in seconds of the signed url for the payload | 5 |\n\nThe payloads will be stored in the bucket configured above in a `UUID.json` file at locations determined by the type of the payload. See [here](https://github.com/Netflix/conductor/blob/master/core/src/main/java/com/netflix/conductor/core/utils/S3PayloadStorage.java#L149-L167) for information about how the object key is determined.\n\n### Azure Blob Storage\n\nProductLive provides an implementation of [Azure Blob Storage](https://azure.microsoft.com/services/storage/blobs/) used to externalize large payload storage.  \n\nTo build conductor with azure blob feature read the [README.md](https://github.com/Netflix/conductor/blob/master/azureblob-storage/README.md) in `azureblob-storage` module \n\n!!!note\n    This implementation assumes that you have an [Azure Blob Storage account's connection string or SAS Token](https://github.com/Azure/azure-sdk-for-java/blob/master/sdk/storage/azure-storage-blob/README.md).\n    If you want signed url to expired you must specify a Connection String. \n\nSet the following properties to the desired values in the JVM system properties:\n\n| Property | Description | default value |\n| --- | --- | --- |\n| workflow.external.payload.storage.azure_blob.connection_string | Azure Blob Storage connection string. Required to sign Url. | |\n| workflow.external.payload.storage.azure_blob.endpoint | Azure Blob Storage endpoint. Optional if connection_string is set. | |\n| workflow.external.payload.storage.azure_blob.sas_token | Azure Blob Storage SAS Token. Must have permissions `Read` and `Write` on Resource `Object` on Service `Blob`. Optional if connection_string is set. | |\n| workflow.external.payload.storage.azure_blob.container_name | Azure Blob Storage container where the payloads will be stored | `conductor-payloads` |\n| workflow.external.payload.storage.azure_blob.signedurlexpirationseconds | The expiration time in seconds of the signed url for the payload | 5 |\n| workflow.external.payload.storage.azure_blob.workflow_input_path | Path prefix where workflows input will be stored with an random UUID filename | workflow/input/ |\n| workflow.external.payload.storage.azure_blob.workflow_output_path | Path prefix where workflows output will be stored with an random UUID filename | workflow/output/ |\n| workflow.external.payload.storage.azure_blob.task_input_path | Path prefix where tasks input will be stored with an random UUID filename | task/input/ |\n| workflow.external.payload.storage.azure_blob.task_output_path | Path prefix where tasks output will be stored with an random UUID filename | task/output/ |\n\nThe payloads will be stored as done in [Amazon S3](https://github.com/Netflix/conductor/blob/master/core/src/main/java/com/netflix/conductor/core/utils/S3PayloadStorage.java#L149-L167).\n\n### PostgreSQL Storage\n\nFrinx provides an implementation of [PostgreSQL Storage](https://www.postgresql.org/) used to externalize large payload storage.\n\n!!!note\nThis implementation assumes that you have an [PostgreSQL database server with all required credentials](https://jdbc.postgresql.org/documentation/94/connect.html).\n\nSet the following properties to your application.properties:\n\n| Property                                                    | Description                                                                                                                                                                              | default value                         |\n|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------|\n| conductor.external-payload-storage.postgres.conductor-url   | URL, that can be used to pull the json configurations, that will be downloaded from PostgreSQL to the conductor server. For example: for local development it is `http://localhost:8080` | `\"\"`                                  |\n| conductor.external-payload-storage.postgres.url             | PostgreSQL database connection URL. Required to connect to database.                                                                                                                     |                                       |\n| conductor.external-payload-storage.postgres.username        | Username for connecting to PostgreSQL database. Required to connect to database.                                                                                                         |                                       |\n| conductor.external-payload-storage.postgres.password        | Password for connecting to PostgreSQL database. Required to connect to database.                                                                                                         |                                       |\n| conductor.external-payload-storage.postgres.table-name      | The PostgreSQL schema and table name where the payloads will be stored                                                                                                                   | `external.external_payload`           |\n| conductor.external-payload-storage.postgres.max-data-rows   | Maximum count of data rows in PostgreSQL database. After overcoming this limit, the oldest data will be deleted.                                                                         | Long.MAX_VALUE (9223372036854775807L) |\n| conductor.external-payload-storage.postgres.max-data-days   | Maximum count of days of data age in PostgreSQL database. After overcoming limit, the oldest data will be deleted.                                                                       | 0                                     |\n| conductor.external-payload-storage.postgres.max-data-months | Maximum count of months of data age in PostgreSQL database. After overcoming limit, the oldest data will be deleted.                                                                     | 0                                     |\n| conductor.external-payload-storage.postgres.max-data-years  | Maximum count of years of data age in PostgreSQL database. After overcoming limit, the oldest data will be deleted.                                                                      | 1                                     |\n\nThe maximum date age for fields in the database will be: `years + months + days`  \nThe payloads will be stored in PostgreSQL database with key (externalPayloadPath) `UUID.json` and you can generate\nURI for this data using `external-postgres-payload-resource` rest controller.   \nTo make this URI work correctly, you must correctly set the conductor-url property.\n"
  },
  {
    "path": "docs/docs/faq.md",
    "content": "# Frequently asked Questions\n\n### How do you schedule a task to be put in the queue after some time (e.g. 1 hour, 1 day etc.)\n\nAfter polling for the task update the status of the task to `IN_PROGRESS` and set the `callbackAfterSeconds` value to the desired time.  The task will remain in the queue until the specified second before worker polling for it will receive it again.\n\nIf there is a timeout set for the task, and the `callbackAfterSeconds` exceeds the timeout value, it will result in task being TIMED_OUT.\n\t\n\n### How long can a workflow be in running state?  Can I have a workflow that keeps running for days or months?\n\nYes.  As long as the timeouts on the tasks are set to handle long running workflows, it will stay in running state.\n\n\n### My workflow fails to start with missing task error\n\nEnsure all the tasks are registered via `/metadata/taskdefs` APIs.  Add any missing task definition (as reported in the error) and try again.\n\n\n### Where does my worker run?  How does conductor run my tasks?\n\nConductor does not run the workers.  When a task is scheduled, it is put into the queue maintained by Conductor.  Workers are required to poll for tasks using `/tasks/poll` API at periodic interval, execute the business logic for the task and report back the results using `POST /tasks` API call. \nConductor, however will run [system tasks](/configuration/systask.html) on the Conductor server.\n\n\n### How can I schedule workflows to run at a specific time?\n\nNetflix Conductor itself does not provide any scheduling mechanism.  But there is a community project [_Schedule Conductor Workflows_](https://github.com/jas34/scheduledwf) which provides workflow scheduling capability as a pluggable module as well as workflow server.\nOther way is you can use any of the available scheduling systems to make REST calls to Conductor to start a workflow.  Alternatively, publish a message to a supported eventing system like SQS to trigger a workflow.  \nMore details about [eventing](/configuration/eventhandlers.html).\n\n\n### How do I setup Dynomite cluster?\n\nVisit Dynomite's [Github page](https://github.com/Netflix/dynomite) to find details on setup and support mechanism.\n\n\n### Can I use conductor with Ruby / Go / Python?\n\nYes.  Workers can be written any language as long as they can poll and update the task results via HTTP endpoints.\n\nConductor provides frameworks for Java and Python to simplify the task of polling and updating the status back to Conductor server.\n\n**Note:** Python and Go clients have been contributed by the community.\n\n\n### How can I get help with Dynomite?\n\nVisit Dynomite's [Github page](https://github.com/Netflix/dynomite) to find details on setup and support mechanism.\n\n\n### My workflow is running and the task is SCHEDULED but it is not being processed.\n\nMake sure that the worker is actively polling for this task. Navigate to the `Task Queues` tab on the Conductor UI and select your task name in the search box. Ensure that `Last Poll Time` for this task is current.\n\nIn Conductor 3.x, ```conductor.redis.availabilityZone``` defaults to ```us-east-1c```.  Ensure that this matches where your workers are, and that it also matches```conductor.redis.hosts```.\n\n### How do I configure a notification when my workflow completes or fails?\n\nWhen a workflow fails, you can configure a \"failure workflow\" to run using the```failureWorkflow``` parameter. By default, three parameters are passed:\n\n* reason\n* workflowId: use this to pull the details of the failed workflow.\n* failureStatus\n\nYou can also use the Workflow Status Listener: \n\n* Set the workflowStatusListenerEnabled field in your workflow definition to true which enables [notifications](/configuration/workflowdef.html#workflow-notifications).\n* Add a custom implementation of the Workflow Status Listener. Refer [this](/extend.html#workflow-status-listener).\n* This notification can be implemented in such a way as to either send a notification to an external system or to send an event on the conductor queue to complete/fail another task in another workflow as described [here](/configuration/eventhandlers.html).\n\nRefer to this [documentation](/configuration/workflowdef.html#workflow-notifications) to extend conductor to send out events/notifications upon workflow completion/failure. \n\n\n\n\n### I want my worker to stop polling and executing tasks when the process is being terminated. (Java client)\n\nIn a `PreDestroy` block within your application, call the `shutdown()` method on the `TaskRunnerConfigurer` instance that you have created to facilitate a graceful shutdown of your worker in case the process is being terminated.\n\n\n### Can I exit early from a task without executing the configured automatic retries in the task definition?\n\nSet the status to `FAILED_WITH_TERMINAL_ERROR` in the TaskResult object within your worker. This would mark the task as FAILED and fail the workflow without retrying the task as a fail-fast mechanism.\n"
  },
  {
    "path": "docs/docs/gettingstarted/basicconcepts.md",
    "content": "# Basic Concepts\n\n## Definitions (aka Metadata or Blueprints)\nConductor definitions are like class definitions in OOP paradigm, or templates. You define this once, and use for each workflow execution. Definitions to Executions have 1:N relationship.\n\n## Tasks\nTasks are the building blocks of Workflow. There must be at least one task in a Workflow.  \nTasks can be categorized into two types: \n\n * [System tasks](/configuration/systask.html) - executed by Conductor server.\n * [Worker tasks](/configuration/workerdef.html) - executed by your own workers.\n\n## Workflow\nA Workflow is the container of your process flow. It could include several different types of Tasks, Sub-Workflows, inputs and outputs connected to each other, to effectively achieve the desired result. The tasks are either control tasks (fork, conditional etc) or application tasks (e.g. encode a file) that are executed on a remote machine.\n\n[Detailed description](/configuration/workflowdef.html)\n\n## Task Definition\nTask definitions help define Task level parameters like inputs and outputs, timeouts, retries etc.\n\n* All tasks need to be registered before they can be used by active workflows.\n* A task can be re-used within multiple workflows.\n\n[Detailed description](/configuration/taskdef.html)\n\n## System Tasks\nSystem tasks are executed within the JVM of the Conductor server and managed by Conductor for its execution and scalability.\n\nSee [Systems tasks](/configuration/systask.html) for list of available Task types, and instructions for using them.\n\n!!! Note\n\tConductor provides an API to create user defined tasks that are executed in the same JVM as the engine.\tSee [WorkflowSystemTask](https://github.com/Netflix/conductor/blob/main/core/src/main/java/com/netflix/conductor/core/execution/tasks/WorkflowSystemTask.java) interface for details.\n\n## Worker Tasks\nWorker tasks are implemented by your application(s) and run in a separate environment from Conductor. The worker tasks can be implemented in any language.  These tasks talk to Conductor server via REST/gRPC to poll for tasks and update its status after execution.\n\nWorker tasks are identified by task type __SIMPLE__ in the blueprint.\n"
  },
  {
    "path": "docs/docs/gettingstarted/client.md",
    "content": "# Using the Client\nConductor tasks that are executed by remote workers communicate over HTTP endpoints/gRPC to poll for the task and update the status of the execution.\n\n## Client APIs\nConductor provides the following java clients to interact with the various APIs\n\n| Client          | Usage                                                                     |\n|-----------------|---------------------------------------------------------------------------|\n| Metadata Client | Register / Update workflow and task definitions                           |\n| Workflow Client | Start a new workflow / Get execution status of a workflow                 |\n| Task Client     | Poll for task / Update task result after execution / Get status of a task |\n\n## SDKs\n\n* [Clojure](/how-tos/clojure-sdk.html)\n* [C#](/how-tos/csharp-sdk.html)\n* [Go](/how-tos/go-sdk.html)\n* [Java](/how-tos/java-sdk.html)\n* [Python](/how-tos/python-sdk.html)\n\nThe non-Java Conductor SDKs are hosted on a separate GitHub repository: [github.com/conductor-sdk](https://github.com/conductor-sdk).  Contributions from the community are encouraged!\n"
  },
  {
    "path": "docs/docs/gettingstarted/docker.md",
    "content": "\n# Running Conductor using Docker\n\nIn this article we will explore how you can set up Netflix Conductor on your local machine using Docker compose.\nThe docker compose will bring up the following:\n\n1. Conductor API Server\n2. Conductor UI\n3. Elasticsearch for searching workflows\n\n## Prerequisites\n1. Docker: [https://docs.docker.com/get-docker/](https://docs.docker.com/get-docker/)\n2. Recommended host with CPU and RAM to be able to run multiple docker containers (at-least 16GB RAM)\n\n## Steps\n\n### 1. Clone the Conductor Code\n\n```shell\n$ git clone https://github.com/Netflix/conductor.git\n```\n\n### 2. Build the Docker Compose\n\n```shell\n$ cd conductor\nconductor $ cd docker\ndocker $ docker-compose build\n```\n\n### 3. Run Docker Compose\n\n```shell\ndocker $ docker-compose up\n```\n\nOnce up and running, you will see the following in your Docker dashboard:\n\n1. Elasticsearch\n2. Conductor UI\n3. Conductor Server\n\nYou can access the UI & Server on your browser to verify that they are running correctly:\n\n#### Conductor Server URL\n[http://localhost:8080](http://localhost:8080)\n\n<img src=\"/img/tutorial/swagger.png\" style=\"width: 100%\"/>\n\n#### Conductor UI URL\n[http://localhost:5000/](http://localhost:5000)\n\n<img src=\"/img/tutorial/conductorUI.png\" style=\"width: 100%\" />\n\n\n### 4. Exiting Compose\n`Ctrl+c` will exit docker compose.\n\nTo ensure images are stopped execute: `docker-compose down`.\n\n## Alternative Persistence Engines\nBy default `docker-compose.yaml` uses `config-local.properties`. This configures the `memory` database, where data is lost when the server terminates. This configuration is useful for testing or demo only.\n\nA selection of `docker-compose-*.yaml` and `config-*.properties` files are provided demonstrating the use of alternative persistence engines.\n\n| File                           | Containers                                                                              |\n|--------------------------------|-----------------------------------------------------------------------------------------|\n| docker-compose.yaml            | <ol><li>In Memory Conductor Server</li><li>Elasticsearch</li><li>UI</li></ol>           |\n| docker-compose-dynomite.yaml   | <ol><li>Conductor Server</li><li>Elasticsearch</li><li>UI</li><li>Dynomite Redis for persistence</li></ol> |\n| docker-compose-postgres.yaml   | <ol><li>Conductor Server</li><li>Elasticsearch</li><li>UI</li><li>Postgres persistence</li></ol> |\n| docker-compose-prometheus.yaml | Brings up Prometheus server                                                             |    \n\nFor example this will start the server instance backed by a PostgreSQL DB.\n```\ndocker-compose -f docker-compose.yaml -f docker-compose-postgres.yaml up\n```\n\n## Standalone Server Image\nTo build and run the server image, without using `docker-compose`, from the `docker` directory execute:\n```\ndocker build -t conductor:server -f server/Dockerfile ../\ndocker run -p 8080:8080 -d --name conductor_server conductor:server\n```\nThis builds the image `conductor:server` and runs it in a container named `conductor_server`. The API should now be accessible at `localhost:8080`.\n\nTo 'login' to the running container, use the command:\n```\ndocker exec -it conductor_server /bin/sh\n```\n\n## Standalone UI Image\nFrom the `docker` directory, \n```\ndocker build -t conductor:ui -f ui/Dockerfile ../\ndocker run -p 5000:5000 -d --name conductor_ui conductor:ui\n```\nThis builds the image `conductor:ui` and runs it in a container named `conductor_ui`. The UI should now be accessible at `localhost:5000`.\n\n### Note\n* In order for the UI to do anything useful the Conductor Server must already be running on port 8080, either in a Docker container (see above), or running directly in the local JRE.\n* Additionally, significant parts of the UI will not be functional without Elastisearch being available. Using the `docker-compose` approach alleviates these considerations.\n\n## Monitoring with Prometheus\n\nStart Prometheus with:\n`docker-compose -f docker-compose-prometheus.yaml up -d`\n\nGo to [http://127.0.0.1:9090](http://127.0.0.1:9090).\n\n## Combined Server & UI Docker Image\nThis image at `/docker/serverAndUI` is provided to illustrate starting both the server & UI within the same container. The UI is hosted using nginx.\n\n### Building the combined image\nFrom the `docker` directory,\n```\ndocker build -t conductor:serverAndUI -f serverAndUI/Dockerfile ../\n```\n\n### Running the combined image\n - With interal DB: `docker run -p 8080:8080 -p 80:5000 -d -t conductor:serverAndUI`\n - With external DB: `docker run -p 8080:8080 -p 80:5000 -d -t -e \"CONFIG_PROP=config.properties\" conductor:serverAndUI`\n\n\n\n## Potential problem when using Docker Images\n\n#### Not enough memory\n\n    1. You will need at least 16 GB of memory to run everything. You can modify the docker compose to skip using\n       Elasticsearch if you have no option to run this with your memory options.\n    2. To disable Elasticsearch using Docker Compose - follow the steps listed here: **TODO LINK**\n\n#### Elasticsearch fails to come up in arm64 based CPU machines\n\n    1. As of writing this article, Conductor relies on 6.8.x version of Elasticsearch. This version doesn't have an\n       arm64 based Docker image. You will need to use Elasticsearch 7.x which requires a bit of customization to get up\n       and running\n\n#### Elasticsearch remains in yellow health state\n\nWhen you run Elasticsearch, sometimes the health remains in the *yellow* state. Conductor server by default requires\n*green* state to run when indexing is enabled. To work around this, you can use the following property: `conductor.elasticsearch.clusterHealthColor=yellow`.\n\nReference: [Issue 2262][issue2262]\n\n#### Elasticsearch timeout\n\nBy default, a standalone (single node) Elasticsearch has a *yellow* status which will cause timeout (`java.net.SocketTimeoutException`) for Conductor server (required status is *green*).\nSpin up a cluster (more than one node) to prevent the timeout or use config option `conductor.elasticsearch.clusterHealthColor=yellow`.\n\nReference: [Issue 2262][issue2262]\n\n#### Changes in config-*.properties do not take effect\nConfig is copy into image during docker build. You have to rebuild the image or better, link a volume to it to reflect new changes.\n\n#### To troubleshoot a failed startup\nCheck the log of the server, which is located at `/app/logs` (default directory in dockerfile)\n\n#### Unable to access to conductor:server API on port 8080\nIt may takes some time for conductor server to start. Please check server log for potential error.\n\n#### Elasticsearch\nElasticsearch is optional, please be aware that disable it will make most of the conductor UI not functional.\n\n##### How to enable Elasticsearch\n* Set `conductor.indexing.enabled=true` in your_config.properties\n* Add config related to elasticsearch\n  E.g.: `conductor.elasticsearch.url=http://es:9200`\n\n##### How to disable Elasticsearch\n* Set `conductor.indexing.enabled=false` in your_config.properties\n* Comment out all the config related to elasticsearch\nE.g.: `conductor.elasticsearch.url=http://es:9200`\n\n[issue2262]: https://github.com/Netflix/conductor/issues/2262\n"
  },
  {
    "path": "docs/docs/gettingstarted/hosted.md",
    "content": "# Hosted Solutions\n\n## Orkes\n[Orkes](https://orkes.io) is a commercial vendor that offers a cloud hosted version of Conductor requiring minimal operational investment to get started.\n\n### Developer Playground\nOrkes provides a developer playground for Conductor at [https://play.orkes.io/](https://play.orkes.io/).  The playground is self-service and is free to try. The playground allows you to create new workflow definitions, tasks and execute them using the UI or through APIs.\n\nOrkes also operates a Slack community featuring discussion and guidance for their product and Conductor in general.\n\n### Cloud Hosted Conductor\nOrkes provides multiple options of hosted Conductor clusters in the cloud (AWS, Azure, and GCP, in addition to private clouds) with enterprise support provided by the Orkes team.\n\nBeyond full compatibility with the open source Netflix Conductor, the Orkes product provides additional features, such as in the area of security and analytics, not present in the open source release.\n\n\n\n"
  },
  {
    "path": "docs/docs/gettingstarted/intro.md",
    "content": "# Why Conductor?\n## Conductor was built to help Netflix orchestrate microservices based process flows with the following features:\n\n* A distributed server ecosystem, which stores workflow state information efficiently.\n* Allow creation of process / business flows in which each individual task can be implemented by the same / different microservices.\n* A DAG (Directed Acyclic Graph) based workflow definition.\n* Workflow definitions are decoupled from the service implementations.\n* Provide visibility and traceability into these process flows.\n* Simple interface to connect workers, which execute the tasks in workflows.\n* Workers are language agnostic, allowing each microservice to be written in the language most suited for the service.\n* Full operational control over workflows with the ability to pause, resume, restart, retry and terminate.\n* Allow greater reuse of existing microservices providing an easier path for onboarding.\n* User interface to visualize, replay and search the process flows.\n* Ability to scale to millions of concurrently running process flows.\n* Backed by a queuing service abstracted from the clients.\n* Be able to operate on HTTP or other transports e.g. gRPC.\n* Event handlers to control workflows via external actions.\n* Client implementations in Java, Python and other languages.\n* Various configurable properties with sensible defaults to fine tune workflow and task executions like rate limiting, concurrent execution limits etc.\n\n## Why not peer to peer choreography?\n\nWith peer to peer task choreography, we found it was harder to scale with growing business needs and complexities.\nPub/sub model worked for simplest of the flows, but quickly highlighted some of the issues associated with the approach:\n\n* Process flows are “embedded” within the code of multiple application.\n* Often, there is tight coupling and assumptions around input/output, SLAs etc, making it harder to adapt to changing needs.\n* Almost no way to systematically answer “How much are we done with process X”?\n"
  },
  {
    "path": "docs/docs/gettingstarted/source.md",
    "content": "# Building Conductor From Source\n## Build and Run\n\nIn this article we will explore how you can set up Netflix Conductor on your local machine for trying out some of its\nfeatures.\n\n### Prerequisites\n1. JDK 17 or greater\n2. (Optional) Docker if you want to run tests.  You can install docker from [here](https://www.docker.com/get-started/).\n3. Node for building and running UI.  Instructions at [https://nodejs.org](https://nodejs.org).\n4. Yarn for building and running UI.  Instructions at [https://classic.yarnpkg.com/en/docs/install](https://classic.yarnpkg.com/en/docs/install).\n\n### Steps to build Conductor Server\n\n#### 1. Checkout the code\nClone conductor code from the repo: https://github.com/Netflix/conductor\n\n```shell\n$ git clone https://github.com/Netflix/conductor.git\n```\n#### 2. Build and run Server\n\n> **NOTE for Mac users**: If you are using a new Mac with an Apple Silicon Chip, you must make a small change to ```conductor/grpc/build.gradle``` - adding \"osx-x86_64\" to two lines:\n```\nprotobuf {\n    protoc {\n        artifact = \"com.google.protobuf:protoc:${revProtoBuf}:osx-x86_64\"\n    }\n    plugins {\n        grpc {\n            artifact = \"io.grpc:protoc-gen-grpc-java:${revGrpc}:osx-x86_64\"\n        }\n    }\n...\n} \n```\n\nYou may also need to install rosetta:  \n\n```shell\nsoftwareupdate --install-rosetta\n``` \n\n```shell\n$ cd conductor\nconductor $ cd server\nserver $ ../gradlew bootRun\n```\n\nNavigate to the swagger API docs:\n[http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config](http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config)\n\n<img src=\"/img/tutorial/swagger.png\" style=\"width: 100%\"/>\n\n## Download and Run\nAs an alternative to building from source, you can download and run the pre-compiled JAR.\n\n```shell\nexport CONDUCTOR_VER=3.3.4\nexport REPO_URL=https://repo1.maven.org/maven2/com/netflix/conductor/conductor-server\ncurl $REPO_URL/$CONDUCTOR_VER/conductor-server-$CONDUCTOR_VER-boot.jar \\\n--output conductor-server-$CONDUCTOR_VER-boot.jar; java -jar conductor-server-$CONDUCTOR_VER-boot.jar \n```\nNavigate to the swagger URL: [http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config](http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config)\n\n\n\n## Build and Run UI\n\n### Conductor UI from Source\n\nThe UI is a standard `create-react-app` React Single Page Application (SPA). To get started, with Node 14 and `yarn` installed, first run `yarn install` from within the `/ui` directory to retrieve package dependencies.\n\n\n```shell\n$ cd conductor/ui\nui $ yarn install\n```\n\nThere is no need to \"build\" the project unless you require compiled assets to host on a production web server. If the latter is true, the project can be built with the command `yarn build`.\n\nTo run the UI on the bundled development server, run `yarn run start`. Navigate your browser to `http://localhost:5000`. The server must already be running on port 8080. \n\n```shell\nui $ yarn run start\n```\n\nLaunch UI [http://localhost:5000](http://localhost:5000)\n\n<img src=\"/img/tutorial/conductorUI.png\" style=\"width: 100%\" />\n\n## Summary\n1. By default in-memory persistence is used, so any workflows created or excuted will be wiped out once the server is terminated.\n2. Without indexing configured, the search functionality in UI will not work and will result an empty set.\n3. See how to install Conductor using [Docker](docker.md) with persistence and indexing.\n"
  },
  {
    "path": "docs/docs/gettingstarted/startworkflow.md",
    "content": "# Starting a Workflow\n## Start Workflow Endpoint\nWhen starting a Workflow execution with a registered definition, `/workflow` accepts following parameters:\n\n| Field                           | Description                                                                                                                               | Notes                                                                                                   |\n|:--------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------|\n| name                            | Name of the Workflow. MUST be registered with Conductor before starting workflow                                                          |                                                                                                         |\n| version                         | Workflow version                                                                                                                          | defaults to latest available version                                                                    |\n| input                           | JSON object with key value params, that can be used by downstream tasks                                                                   | See [Wiring Inputs and Outputs](/configuration/workflowdef.html#wiring-inputs-and-outputs) for details |\n| correlationId                   | Unique Id that correlates multiple Workflow executions                                                                                    | optional                                                                                                |\n| taskToDomain                    | See [Task Domains](/configuration/taskdomains.html) for more information.                                                   | optional                                                                                                |\n| workflowDef                     | An adhoc [Workflow Definition](/configuration/workflowdef.html) to run, without registering. See [Dynamic Workflows](#dynamic-workflows). | optional                                                                                                |\n| externalInputPayloadStoragePath | This is taken care of by Java client. See [External Payload Storage](/externalpayloadstorage.html) for more info.                        | optional                                                                                                |\n| priority                        | Priority level for the tasks within this workflow execution. Possible values are between 0 - 99.                                          | optional                                                                                                |\n\n**Example:**\n\nSend a `POST` request to `/workflow` with payload like:\n```json\n{\n    \"name\": \"encode_and_deploy\",\n    \"version\": 1,\n    \"correlationId\": \"my_unique_correlation_id\",\n    \"input\": {\n        \"param1\": \"value1\",\n        \"param2\": \"value2\"\n    }\n}\n```\n\n## Dynamic Workflows\n\nIf the need arises to run a one-time workflow, and it doesn't make sense to register Task and Workflow definitions in Conductor Server, as it could change dynamically for each execution, dynamic workflow executions can be used.\n\nThis enables you to provide a workflow definition embedded with the required task definitions to the Start Workflow Request in the `workflowDef` parameter, avoiding the need to register the blueprints before execution.\n\n**Example:**\n\nSend a `POST` request to `/workflow` with payload like:\n```json\n{\n  \"name\": \"my_adhoc_unregistered_workflow\",\n  \"workflowDef\": {\n    \"ownerApp\": \"my_owner_app\",\n    \"ownerEmail\": \"my_owner_email@test.com\",\n    \"createdBy\": \"my_username\",\n    \"name\": \"my_adhoc_unregistered_workflow\",\n    \"description\": \"Test Workflow setup\",\n    \"version\": 1,\n    \"tasks\": [\n    \t{\n\t        \"name\": \"fetch_data\",\n\t        \"type\": \"HTTP\",\n\t        \"taskReferenceName\": \"fetch_data\",\n\t        \"inputParameters\": {\n\t          \"http_request\": {\n\t            \"connectionTimeOut\": \"3600\",\n\t            \"readTimeOut\": \"3600\",\n\t            \"uri\": \"${workflow.input.uri}\",\n\t            \"method\": \"GET\",\n\t            \"accept\": \"application/json\",\n\t            \"content-Type\": \"application/json\",\n\t            \"headers\": {\n\t            }\n\t          }\n\t        },\n\t        \"taskDefinition\": {\n\t            \"name\": \"fetch_data\",\n\t\t\t    \"retryCount\": 0,\n\t\t\t    \"timeoutSeconds\": 3600,\n\t\t\t    \"timeoutPolicy\": \"TIME_OUT_WF\",\n\t\t\t    \"retryLogic\": \"FIXED\",\n\t\t\t    \"retryDelaySeconds\": 0,\n\t\t\t    \"responseTimeoutSeconds\": 3000\n\t        }\n\t    }\n    ],\n    \"outputParameters\": {\n    }\n  },\n  \"input\": {\n    \"uri\": \"http://www.google.com\"\n  }\n}\n```\n\n!!! Note\n    If the `taskDefinition` is defined with Metadata API, it doesn't have to be added in above dynamic workflow definition.\n"
  },
  {
    "path": "docs/docs/gettingstarted/steps.md",
    "content": "\n# High Level Steps\nSteps required for a new workflow to be registered and get executed\n\n1. Define task definitions used by the workflow. \n2. Create the workflow definition\n3. Create task worker(s) that polls for scheduled tasks at regular interval\n\n### Trigger Workflow Execution\n\n```\nPOST /workflow/{name}\n{\n\t... //json payload as workflow input\n}\n```\n\n### Polling for a task\n\n```\nGET /tasks/poll/batch/{taskType}\n```\n\t\n### Update task status\n\t\n```\nPOST /tasks\n{\n\t\"outputData\": {\n        \"encodeResult\":\"success\",\n        \"location\": \"http://cdn.example.com/file/location.png\"\n        //any task specific output\n     },\n     \"status\": \"COMPLETED\"\n}\n```\n"
  },
  {
    "path": "docs/docs/googleba55068fa3e0e553.html",
    "content": "google-site-verification: googleba55068fa3e0e553.html"
  },
  {
    "path": "docs/docs/how-tos/Monitoring/Conductor-LogLevel.md",
    "content": "# Conductor Log Level\n\nConductor is based on Spring Boot, so the log levels are set via [Spring Boot properties](https://docs.spring.io/spring-boot/docs/2.1.13.RELEASE/reference/html/boot-features-logging.html):\n\nFrom the Spring Boot Docs:\n\n\n> All the supported logging systems can have the logger levels set in the Spring Environment (for example, in application.properties) by using ```logging.level.<logger-name>=<level>``` where level is one of TRACE, DEBUG, INFO, WARN, ERROR, FATAL, or OFF. The ```root``` logger can be configured by using logging.level.root.\n\n> The following example shows potential logging settings in ```application.properties```:\n\n```\nlogging.level.root=warn\nlogging.level.org.springframework.web=debug\nlogging.level.org.hibernate=error\n```\n\nIt’s also possible to set logging levels using environment variables. For example, ```LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG``` will set ```org.springframework.web``` to `DEBUG`.\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/creating-tasks.md",
    "content": "# Creating Task Definitions\n\nTasks can be created using the tasks metadata API\n\n`POST /api/metadata/taskdefs`\n\nThis API takes an array of new task definitions.\n\n### Example using curl\n\n```shell\ncurl 'http://localhost:8080/api/metadata/taskdefs' \\\n  -H 'accept: */*' \\\n  -H 'content-type: application/json' \\\n  --data-raw '[{\"createdBy\":\"user\",\"name\":\"sample_task_name_1\",\"description\":\"This is a sample task for demo\",\"responseTimeoutSeconds\":10,\"timeoutSeconds\":30,\"inputKeys\":[],\"outputKeys\":[],\"timeoutPolicy\":\"TIME_OUT_WF\",\"retryCount\":3,\"retryLogic\":\"FIXED\",\"retryDelaySeconds\":5,\"inputTemplate\":{},\"rateLimitPerFrequency\":0,\"rateLimitFrequencyInSeconds\":1}]'\n```\n\n### Example using node fetch\n\n```javascript\nfetch(\"http://localhost:8080/api/metadata/taskdefs\", {\n    \"headers\": {\n        \"accept\": \"*/*\",\n        \"content-type\": \"application/json\",\n    },\n    \"body\": \"[{\\\"createdBy\\\":\\\"user\\\",\\\"name\\\":\\\"sample_task_name_1\\\",\\\"description\\\":\\\"This is a sample task for demo\\\",\\\"responseTimeoutSeconds\\\":10,\\\"timeoutSeconds\\\":30,\\\"inputKeys\\\":[],\\\"outputKeys\\\":[],\\\"timeoutPolicy\\\":\\\"TIME_OUT_WF\\\",\\\"retryCount\\\":3,\\\"retryLogic\\\":\\\"FIXED\\\",\\\"retryDelaySeconds\\\":5,\\\"inputTemplate\\\":{},\\\"rateLimitPerFrequency\\\":0,\\\"rateLimitFrequencyInSeconds\\\":1}]\",\n    \"method\": \"POST\"\n});\n```\n## Best Practices\n\n1. You can update a set of tasks together in this API\n2. Task configurations are important attributes that controls the behavior of this task in a Workflow. Refer to [Task Configurations](/configuration/taskdef.html) for all the options and details' \n3. You can also use the Conductor Swagger UI to update the tasks\n\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/dynamic-vs-switch-tasks.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Dynamic vs Switch Tasks\n\nLearn more about\n\n1. [Dynamic Tasks](/reference-docs/dynamic-task.html)\n2. [Switch Tasks](/reference-docs/switch-task.html)\n\nDynamic Tasks are useful in situations when need to run a task of which the task type is determined at runtime instead\nof during the configuration. It is similar to the [SWITCH](/reference-docs/switch-task.html) use case but with `DYNAMIC`\nwe won't need to preconfigure all case options in the workflow definition itself. Instead, we can mark the task\nas `DYNAMIC` and determine which underlying task does it run during the workflow execution itself.\n\n1. Use DYNAMIC task as a replacement for SWITCH if you have too many case options\n2. DYNAMIC task is an option when you want to programmatically determine the next task to run instead of using expressions\n3. DYNAMIC task simplifies the workflow execution UI view which will now only show the selected task\n4. SWITCH task visualization is helpful as a documentation - showing you all options that the workflow could have\n   taken\n5. SWITCH task comes with a default task option which can be useful in some use cases\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/extending-system-tasks.md",
    "content": "# Extending System Tasks\n\n[System tasks](/configuration/systask.html) allow Conductor to run simple tasks on the server - removing the need to build (and deploy) workers for basic tasks.  This allows for automating more mundane tasks without building specific microservices for them.\n\nHowever, sometimes it might be necessary to add additional parameters to a System Task to gain the behavior that is desired.\n\n## Example HTTP Task\n\n```json\n{\n  \"name\": \"get_weather_90210\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"get_weather_90210\",\n      \"taskReferenceName\": \"get_weather_90210\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"https://weatherdbi.herokuapp.com/data/weather/90210\",\n          \"method\": \"GET\",\n          \"connectionTimeOut\": 1300,\n          \"readTimeOut\": 1300\n        }\n      },\n      \"type\": \"HTTP\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"data\": \"${get_weather_ref.output.response.body.currentConditions.comment}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"ownerEmail\": \"conductor@example.com\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"variables\": {},\n  \"inputTemplate\": {}\n}\n\n```\n\nThis very simple workflow has a single HTTP Task inside.  No parameters need to be passed, and when run, the HTTP task will return the weather in Beverly Hills, CA (Zip code = 90210).\n\n> This API has a very slow response time. In the HTTP task, the connection is set to time out after 1300ms, which is *too short* for this API, resulting in a timeout.  This API *will* work if we allowed for a longer timeout, but in order to demonstrate adding retries to the HTTP Task, we will artificially force the API call to fail.\n\nWhen this workflow is run - it fails, as expected.\n\nNow, sometimes an API call might fail due to an issue on the remote server, and retrying the call will result in a response.  With many Conductor tasks,  ```retryCount```, ```retryDelaySeconds``` and ```retryLogic``` fields can be applied to retry the worker (with the desired parameters).\n\nBy default, the [HTTP Task](/reference-docs/http-task.html) does not have ```retryCount```, ```retryDelaySeconds``` or ```retryLogic``` built in.  Attempting to add these parameters to a HTTP Task results in an error.\n\n## The Solution\n\nWe can create a task with the same name with the desired parameters.  Defining the following task (note that the ```name``` is identical to the one in the workflow):\n\n```json\n{\n\n  \"createdBy\": \"\",\n  \"name\": \"get_weather_90210\",\n  \"description\": \"editing HTTP task\",\n  \"retryCount\": 3,\n  \"timeoutSeconds\": 5,\n  \"inputKeys\": [],\n  \"outputKeys\": [],\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 5,\n  \"responseTimeoutSeconds\": 5,\n  \"inputTemplate\": {},\n  \"rateLimitPerFrequency\": 0,\n  \"rateLimitFrequencyInSeconds\": 1\n}\n\n```\n\nWe've added the three parameters: ```retryCount: 3, retryDelaySeconds: 5, retryLogic: FIXED```\n\nThe ```get_weather_90210``` task will now run 4 times (it will fail once, and then retry 3 times), with a ```FIXED``` 5 second delay between attempts.\n\nRe-running the task (and looking at the timeline view) shows that this is what occurs.  There are 4 attempts, with a 5 second delay between them.\n\nIf we change the ```retryLogic``` to EXPONENTIAL_BACKOFF, the delay between attempts grows exponentially:\n\n1. 5*2^0 = 5 seconds\n2. 5*2^1 = 10 seconds\n3. 5*2^2 = 20 seconds\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/monitoring-task-queues.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Monitoring Task Queues\n\nConductor offers an API and UI interface to monitor the task queues. This is useful to see details of the number of\nworkers polling and monitoring the queue backlog.\n\n### Using the UI\n\n```http request\n<your UI server URL>/taskQueue\n```\n\nAccess this screen via - Home > Task Queues\n\nOn this screen you can select and view the details of the task queue. The following information is shown:\n\n1. Queue Size - The number of tasks waiting to be executed\n2. Workers - The count and list of works and their instance reference who are polling for work for this task\n\n### Using APIs\n\nTo see the size of the task queue via API:\n\n```shell\ncurl 'http://localhost:8080/api/tasks/queue/sizes?taskType=<TASK_NAME>' \\\n  -H 'accept: */*' \n```\n\nTo see the worker poll information of the task queue via API:\n\n```shell\ncurl 'http://localhost:8080/api/tasks/queue/polldata?taskType=<TASK_NAME>' \\\n  -H 'accept: */*'\n```\n\n> Replace `<TASK_NAME>` with your task name\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/reusing-tasks.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Reusing Tasks\n\nA powerful feature of Conductor is that it supports and enables re-usability out of the box. Task workers typically\nperform a unit of work and is usually a part of a larger workflow. Such workers are often re-usable in multiple\nworkflows. Once a task is defined, you can use it across as any workflow.\n\nWhen re-using tasks, it's important to think of situations that a multi-tenant system faces. All the work assigned to\nthis worker by default goes to the same task scheduling queue. This could result in your worker not being polled quickly\nif there is a noisy neighbour in the ecosystem. One way you can tackle this situation is by re-using the worker code,\nbut having different task names registered for different use cases. And for each task name, you can run an appropriate\nnumber of workers based on expected load.\n\n\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/task-configurations.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Task Configurations\n\nRefer to [Task Definitions](/configuration/taskdef.html) for details on how to configure task definitions\n\n### Example\n\nHere is a task template payload with commonly used fields:\n\n```json\n{\n  \"createdBy\": \"user\",\n  \"name\": \"sample_task_name_1\",\n  \"description\": \"This is a sample task for demo\",\n  \"responseTimeoutSeconds\": 10,\n  \"timeoutSeconds\": 30,\n  \"inputKeys\": [],\n  \"outputKeys\": [],\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryCount\": 3,\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 5,\n  \"inputTemplate\": {},\n  \"rateLimitPerFrequency\": 0,\n  \"rateLimitFrequencyInSeconds\": 1\n}\n```\n\n### Best Practices\n\n1. Refer to [Task Timeouts](/how-tos/Tasks/task-timeouts.html) for additional information on how the various timeout settings work\n2. Refer to [Monitoring Task Queues](/how-tos/Tasks/monitoring-task-queues.html) on how to monitor task queues\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/task-inputs.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Task Inputs\n\nTask inputs can be provided in multiple ways. This is configured in the workflow definition when a task is participating\nin the workflow.\n\n### Inputs referred from Workflow inputs\n\nWhen we start a workflow, we can provide inputs to the workflow in a json format. For example:\n\n```json\n{\n  \"worfklowInputNumberExample\": 1,\n  \"worfklowInputTextExample\": \"SAMPLE\",\n  \"worfklowInputJsonExample\": {\n    \"nestedKey\": \"nestedValue\"\n  }\n}\n```\n\nThese values can be referred as inputs into your task using the following expression:\n\n```json\n{\n  \"taskInput1Key\": \"${workflow.input.worfklowInputNumberExample}\",\n  \"taskInput2Key\": \"${workflow.input.worfklowInputJsonExample.nestedKey}\"\n}\n```\n\nIn this example, the tasks will receive the following inputs after they are evaluated:\n```json\n{\n  \"taskInput1Key\": 1,\n  \"taskInput2Key\": \"nestedValue\"\n}\n```\n\n### Inputs referred from other Task outputs\n\nSimilar to how we can refer to workflow inputs, we can also refer to an output field that was generated by a task that\nexecuted before.\n\nLet's assume a task with the task reference name `previousTaskReference` executed and produced the following output:\n\n```json\n{\n  \"taskOutputKey1\": \"outputValue\",\n  \"taskOutputKey2\": {\n    \"nestedKey1\": \"outputValue-1\"\n  }\n}\n```\n\nWe can refer to these as the new task's input by using the following expression:\n\n```json\n{\n  \"taskInput1Key\": \"${previousTaskReference.output.taskOutputKey1}\",\n  \"taskInput2Key\": \"${previousTaskReference.output.taskOutputKey2.nestedKey1}\"\n}\n```\n\nThe expression format is based on [Json Path](https://goessner.net/articles/JsonPath/) and you can construct complex\ninput params based on the syntax.\n\n### Hard coded inputs\n\nTask inputs can also be hard coded in the workflow definitions. This is useful when you have a re-usable task which has\nconfigurable options that can be applied in different workflow contexts.\n\n```json\n{\n  \"taskInput1\": \"OPTION_A\",\n  \"taskInput2\": 100\n}\n```\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/task-timeouts.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Task Timeouts\n\nTasks can be configured to handle various scenarios of timeouts. Here are some scenarios and the relevance configuration\nfields.\n\n| Scenario                                                                                                  | Configuration            |\n|-----------------------------------------------------------------------------------------------------------|--------------------------|\n| A task worker picked up the task, but fails to respond back with an update                                | `responseTimeoutSeconds` |\n| A task worker picked up the task and updates progress, but fails to complete within an expected timeframe | `timeoutSeconds`         |\n| A task is stuck in a retry loop with repeated failures beyond an expected timeframe                       | `timeoutSeconds`         |\n| Task doesn't get picked by any workers for a specific amount of time                                      | `pollTimeoutSeconds`     |\n| Task isn't completed within a specified amount of time despite being picked up by task workers            | `timeoutSeconds`         |\n\n> `timeoutSeconds` should always be greater than `responseTimeoutSeconds`\n\n### Timeout Seconds\n\n```json\n\"timeoutSeconds\" : 30\n```\n\nWhen configured with a value > `0`, the system will wait for this task to complete successfully up until this number of\nseconds from when the task is first polled. We can use this to fail a workflow when a task breaches the overall SLA for\ncompletion.\n\n### Response Timeout Seconds\n\n```json\n\"responseTimeoutSeconds\" : 10\n```\n\nWhen configured with a value > `0`, the system will wait for this number of seconds from when the task is polled before\nthe worker updates back with a status. The worker can keep the task in `IN_PROGRESS` state if it requires more time to\ncomplete.\n\n### Poll Timeout Seconds\n\n```json\n\"pollTimeoutSeconds\" : 10\n```\n\nWhen configured with a value > `0`, the system will wait for this number of seconds for the task to be picked up by a\ntask worker. Useful when you want to detect a backlogged task queue with not enough workers.\n"
  },
  {
    "path": "docs/docs/how-tos/Tasks/updating-tasks.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Updating Task Definitions\n\nUpdates to the task definitions can be made using the following API\n\n```http\n\nPUT /api/metadata/taskdefs\n```\n\nThis API takes a single task definition and updates itself. \n\n\n### Example using curl\n\n```shell\ncurl 'http://localhost:8080/api/metadata/taskdefs' \\\n  -X 'PUT' \\\n  -H 'accept: */*' \\\n  -H 'content-type: application/json' \\\n  --data-raw '{\"createdBy\":\"user\",\"name\":\"sample_task_name_1\",\"description\":\"This is a sample task for demo\",\"responseTimeoutSeconds\":10,\"timeoutSeconds\":30,\"inputKeys\":[],\"outputKeys\":[],\"timeoutPolicy\":\"TIME_OUT_WF\",\"retryCount\":3,\"retryLogic\":\"FIXED\",\"retryDelaySeconds\":5,\"inputTemplate\":{},\"rateLimitPerFrequency\":0,\"rateLimitFrequencyInSeconds\":1}'\n```\n\n### Example using node fetch\n\n```javascript\nfetch(\"http://localhost:8080/api/metadata/taskdefs\", {\n    \"headers\": {\n        \"accept\": \"*/*\",\n        \"content-type\": \"application/json\",\n    },\n    \"body\": \"{\\\"createdBy\\\":\\\"user\\\",\\\"name\\\":\\\"sample_task_name_1\\\",\\\"description\\\":\\\"This is a sample task for demo\\\",\\\"responseTimeoutSeconds\\\":10,\\\"timeoutSeconds\\\":30,\\\"inputKeys\\\":[],\\\"outputKeys\\\":[],\\\"timeoutPolicy\\\":\\\"TIME_OUT_WF\\\",\\\"retryCount\\\":3,\\\"retryLogic\\\":\\\"FIXED\\\",\\\"retryDelaySeconds\\\":5,\\\"inputTemplate\\\":{},\\\"rateLimitPerFrequency\\\":0,\\\"rateLimitFrequencyInSeconds\\\":1}\",\n    \"method\": \"PUT\"\n});\n```\n## Best Practices\n\n1. You can also use the Conductor Swagger UI to update the tasks \n2. Task configurations are important attributes that controls the behavior of this task in a Workflow. Refer to [Task Configurations](/how-tos/Tasks/task-configurations.html) for all the options and details'\n"
  },
  {
    "path": "docs/docs/how-tos/Test/testing-workflows.md",
    "content": "# Conductor Workflow Testing Guide\n\n## Unit and Regression testing workflows\n\n### Unit Tests\nConductor workflows can be unit tested using `POST /workflow/test` endpoint.\nThe approach is similar to how you unit test using Mock objects in Java or similar languages.\n\n#### Why Unit Test Workflows?\nUnit tests allows you to test for the correctness of the workflow definition ensuring:\n1. Given a specific input workflow reaches the terminal state in COMPLETED or FAILED state\n2. Given a specific input, the workflow executes specific set of tasks. This is useful for testing branching and dynamic forks\n3. Task inputs are wired correctly - e.g. if a task receives its input from the output of another task, this can be verified using the unit test.\n\n### Unit Testing Workflows\nConductor SDKs provides the following method that allows testing a workflow definition against mock inputs:\n```java\n    public abstract Workflow testWorkflow(WorkflowTestRequest testRequest);\n```\nThe actual workflow is executed on a real Conductor server ensuring you are testing the behavior that will match the ACTUAL executon of the server.\n\n### Setting up Conductor server for testing\nTests can be run against a remote server (useful when running integration tests) or local containerized instance.  Recommended approach is to use `testcontainers`.\n\n### Examples\n\n#### Unit Test\n* [LoanWorkflowTest.java](/client/src/test/java/com/netflix/conductor/client/testing/LoanWorkflowTest.java)\n* Testing workflows that contain sub-workflows : [SubWorkflowTest.java](/client/src/test/java/com/netflix/conductor/client/testing/SubWorkflowTest.java)\n\n#### Regression Test\nWorkflows can be regression tested with golden inputs and outputs.  This approach is useful when modifying workflows that are running in production to ensure the behavior remains correct.\n\nSee [RegressionTest.java](/client/src/test/java/com/netflix/conductor/client/testing/RegressionTest.java) for an example, which uses previously captured workflow execution as golden input/output to verify the workflow execution."
  },
  {
    "path": "docs/docs/how-tos/Workers/build-a-golang-task-worker.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Build a Go Task Worker\n\n## Install\n```shell \ngo get github.com/netflix/conductor/client/go\n```\nThis will create a Go project under $GOPATH/src and download any dependencies.\n\n## Implementing a Task a Worker\n`task`package provides the types used to implement the worker.  Here is a reference worker implementation:\n\n```go\npackage task\n\nimport (\n    \"fmt\"\n)\n\n// Implementation for \"task_1\"\nfunc Task_1_Execution_Function(t *task.Task) (taskResult *task.TaskResult, err error) {\n    log.Println(\"Executing Task_1_Execution_Function for\", t.TaskType)\n\n    //Do some logic\n    taskResult = task.NewTaskResult(t)\n    \n    output := map[string]interface{}{\"task\":\"task_1\", \"key2\":\"value2\", \"key3\":3, \"key4\":false}\n    taskResult.OutputData = output\n    taskResult.Status = \"COMPLETED\"\n    err = nil\n\n    return taskResult, err\n}\n```\n\n## Worker Polling\nHere is an example that shows how to start polling for tasks after defining the tasks.\n\n```go\npackage main\n\nimport (\n    \"github.com/netflix/conductor/client/go\"\n    \"github.com/netflix/conductor/client/go/task/sample\"\n)\n\nfunc main() {\n    c := conductor.NewConductorWorker(\"http://localhost:8080\", 1, 10000)\n\n    c.Start(\"task_1\", \"\", sample.Task_1_Execution_Function, false)\n    c.Start(\"task_2\", \"mydomain\", sample.Task_2_Execution_Function, true)\n}\n```\n### `NewConductorWoker` parameters\n1. baseUrl: Server address.  \n2. threadCount: No. of threads.  Number of threads should be at-least same as the number of workers\n3. pollingInterval: Time in millisecond between subsequent polls\n"
  },
  {
    "path": "docs/docs/how-tos/Workers/build-a-java-task-worker.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Build a Java Task Worker\n\nThis guide provides introduction to building Task Workers in Java.\n\n## Dependencies\n\nConductor provides Java client libraries, which we will use to build a simple task worker.\n\n### Maven Dependency\n\n```xml\n<dependency>\n    <groupId>com.netflix.conductor</groupId>\n    <artifactId>conductor-client</artifactId>\n    <version>3.13.2</version>\n</dependency>\n<dependency>\n    <groupId>com.netflix.conductor</groupId>\n    <artifactId>conductor-common</artifactId>\n    <version>3.13.2</version>\n</dependency>\n```\n\n### Gradle\n\n```groovy\nimplementation group: 'com.netflix.conductor', name: 'conductor-client', version: '3.13.2'\nimplementation group: 'com.netflix.conductor', name: 'conductor-common', version: '3.13.2'\n```\n\n## Implementing a Task Worker\n\nTo create a worker, implement the `Worker` interface.\n\n```java\npublic class SampleWorker implements Worker {\n\n    private final String taskDefName;\n\n    public SampleWorker(String taskDefName) {\n        this.taskDefName = taskDefName;\n    }\n\n    @Override\n    public String getTaskDefName() {\n        return taskDefName;\n    }\n\n    @Override\n    public TaskResult execute(Task task) {\n        TaskResult result = new TaskResult(task);\n        result.setStatus(Status.COMPLETED);\n\n        //Register the output of the task\n        result.getOutputData().put(\"outputKey1\", \"value\");\n        result.getOutputData().put(\"oddEven\", 1);\n        result.getOutputData().put(\"mod\", 4);\n\n        return result;\n    }\n}\n```\n\n### Implementing worker's logic\n\nWorker's core implementation logic goes in the `execute` method. Upon completion, set the `TaskResult` with status as one of the following:\n\n1. **COMPLETED**: If the task has completed successfully.\n2. **FAILED**: If there are failures - business or system failures. Based on the task's configuration, when a task fails, it may be retried.\n\nThe `getTaskDefName()` method returns the name of the task for which this worker provides the execution logic.\n\nSee [SampleWorker.java](https://github.com/Netflix/conductor/blob/main/client/src/test/java/com/netflix/conductor/client/sample/SampleWorker.java) for the complete example.\n\n## Configuring polling using TaskRunnerConfigurer\n\nThe `TaskRunnerConfigurer` can be used to register the worker(s) and initialize the polling loop.\nIt manages the task workers thread pool and server communication (poll and task update).\n\nUse the [Builder](https://github.com/Netflix/conductor/blob/main/client/src/main/java/com/netflix/conductor/client/automator/TaskRunnerConfigurer.java#L64) to create an instance of the `TaskRunnerConfigurer`. The builder accepts the following parameters:\n\n```java\n TaskClient taskClient = new TaskClient();\n taskClient.setRootURI(\"http://localhost:8080/api/\");        //Point this to the server API\n\n        int threadCount = 2;            //number of threads used to execute workers.  To avoid starvation, should be same or more than number of workers\n\n        Worker worker1 = new SampleWorker(\"task_1\");\n        Worker worker2 = new SampleWorker(\"task_5\");\n\n        // Create TaskRunnerConfigurer\n        TaskRunnerConfigurer configurer = new TaskRunnerConfigurer.Builder(taskClient, Arrays.asList(worker1, worker2))\n            .withThreadCount(threadCount)\n            .build();\n\n        // Start the polling and execution of tasks\n        configurer.init();\n```\n\nSee [Sample](https://github.com/Netflix/conductor/blob/main/client/src/test/java/com/netflix/conductor/client/sample/Main.java) for full example.\n\n### Configuration Details\n\nInitialize the `Builder` with the following:\n\n| Parameter | Description |\n| --- | --- |\n| TaskClient | TaskClient used to communicate with the Conductor server |\n| Workers | Workers that will be used for polling work and task execution. |\n\n| Parameter | Description | Default |\n| --- | --- | --- |\n| withEurekaClient | EurekaClient is used to identify if the server is in discovery or not. When the server goes out of discovery, the polling is stopped unless `pollOutOfDiscovery` is set to true. If passed null, discovery check is not done. | provided by platform |\n| withThreadCount | Number of threads assigned to the workers. Should be at-least the size of taskWorkers to avoid starvation in a busy system. | Number of registered workers |\n| withSleepWhenRetry | Time in milliseconds, for which the thread should sleep when task update call fails, before retrying the operation. | 500 |\n| withUpdateRetryCount | Number of attempts to be made when updating task status when update status call fails. | 3 |\n| withWorkerNamePrefix | String prefix that will be used for all the workers. | workflow-worker- |\n\nOnce an instance is created, call `init()` method to initialize the `TaskPollExecutor` and begin the polling and execution of tasks.\n\n!!! tip \"Note\"\n    To ensure that the `TaskRunnerConfigurer` stops polling for tasks when the instance becomes unhealthy, call the provided `shutdown()` hook in a `PreDestroy` block.\n\n#### Properties\n\nThe worker behavior can be further controlled by using these properties:\n\n| Property | Type | Description | Default |\n| --- | --- | --- | --- |\n| paused | boolean | If set to true, the worker stops polling.| false |\n| pollInterval | int | Interval in milliseconds at which the server should be polled for tasks. | 1000 |\n| pollOutOfDiscovery | boolean | If set to true, the instance will poll for tasks regardless of the discovery status. This is useful while running on a dev machine. | false |\n\nFurther, these properties can be set either by a `Worker` implementation or by setting the following system properties in the JVM:\n\n| Name | Description |\n| --- | --- |\n| `conductor.worker.<property>` | Applies to ALL the workers in the JVM. |\n| `conductor.worker.<taskDefName>.<property>` | Applies to the specified worker.  Overrides the global property. |\n"
  },
  {
    "path": "docs/docs/how-tos/Workers/build-a-python-task-worker.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Build a Python Task Worker\n## Install the python client\n```shell \n   virtualenv conductorclient\n   source conductorclient/bin/activate\n   cd ../conductor/client/python\n   python setup.py install\n```\n\n## Implement a Task Worker\n[ConductorWorker](https://github.com/Netflix/conductor/blob/main/polyglot-clients/python/conductor/ConductorWorker.py#L36) \nclass is used to implement task workers.\nThe following script shows how to bring up two task workers named `book_flight` and `book_car`:\n\n```python\nfrom __future__ import print_function\nfrom conductor.ConductorWorker import ConductorWorker\n\ndef book_flight_task(task):\n\treturn {'status': 'COMPLETED', 'output': {'booking_ref': 2341111, 'airline': 'delta'}, 'logs': ['trying delta', 'skipping aa']}\n\ndef book_car_task(task):\n\treturn {'status': 'COMPLETED', 'output': {'booking_ref': \"84545fdfd\", 'agency': 'hertz'}, 'logs': ['trying hertz']}\n\ndef main():\n\tprint('Starting Travel Booking workflows')\n\tcc = ConductorWorker('http://localhost:8080/api', 1, 0.1)\n    cc.start('book_flight', book_flight_task, False)\n    cc.start('book_car', book_car_task, True)\n\nif __name__ == '__main__':\n    main()\n```\n### `ConductorWorker` parameters\n```python\nserver_url: str\n    The url to the server hosting the conductor api.\n    Ex: 'http://localhost:8080/api'\n\nthread_count: int\n    The number of threads that will be polling for and\n    executing tasks in case of using the start method.\n\npolling_interval: float\n    The number of seconds that each worker thread will wait\n    between polls to the conductor server.\n\nworker_id: str, optional\n    The worker_id of the worker that is going to execute the\n    task. For further details, refer to the documentation\n    By default, it is set to hostname of the machine\n```\n### `start` method parameters\n```pythhon\ntaskType: str\n    The name of the task that the worker is looking to execute\n\nexec_function: function\n    The function that the worker will execute. The function\n    must return a dict with the `status`, `output` and `logs`\n    keys present. If this is not present, an Exception will be\n    raised\n\nwait: bool\n    Whether the worker will block execution of further code.\n    Since the workers are being run in daemon threads, when the\n    program completes execution, all the threads are destroyed.\n    Setting wait to True prevents the program from ending.\n    If multiple workers are being called from the same program,\n    all but the last start call but have wait set to False.\n    The last start call must always set wait to True. If a\n    single worker is being called, set wait to True.\n\ndomain: str, optional\n    The domain of the task under which the worker will run. For\n    further details refer to the conductor server documentation\n    By default, it is set to None\n```\n\nSee \n[https://github.com/Netflix/conductor/tree/main/polyglot-clients/python](https://github.com/Netflix/conductor/tree/main/polyglot-clients/python)\nfor the source code.\n"
  },
  {
    "path": "docs/docs/how-tos/Workflows/debugging-workflows.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Debugging Workflows\n\nConductor UI is a tool that we can leverage for debugging issues. Refer to the following articles to search and view\nyour workflow execution.\n\n1. [Searching Workflows](/how-tos/Workflows/searching-workflows.html)\n2. [View Workflow Executions](/how-tos/Workflows/view-workflow-executions.html)\n\n\n## Debugging Executions\n\nOpen the **Tasks > Diagram** tab to see the diagram of the overall workflow execution\n\nIf there is a failure, you will them on the view marked as red. In most cases it should be clear what went wrong from\nthe view itself. To see details of the failure, you can click on the failed task.\n\nThe following fields are useful in debugging\n\n| Field Name                                      | Description                                                                                                                   |\n|-------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|\n| Task Detail > Summary > Reason for Incompletion | If an exception was thrown by the worker, it will be captured and displayed here                                              |\n| Task Detail > Summary > Worker                  | The worker instance id where this failure last occurred. Useful to dig for detailed logs if not already captured by Conductor |\n| Task Detail > Input                             | Verify if the task inputs were computed and provided correctly to the task                                                    |\n| Task Detail > Output                            | If output of a previous task is used as an input to your next task, refer here for what was produced                          |\n| Task Detail > Logs                              | If your task is supplying logs, we can look at that here                                                                      |\n| Task Detail > Retried Task - Select an instance | If your task was retried, we can see all the attempts and correponding details here                                           |\n\nNote: We can also access the task list from **Tasks > Task List** tab.\n\nHere is a screen grab of the fields referred above.\n\n![Debugging Wowkflow Execution](/img/tutorial/workflow_debugging.png)\n\n## Recovering From Failures\n\nOnce we have resolved the underlying issue of workflow execution failure, we might want to replay or retry failed\nworkflows. The UI has functions that would allow us to do this:\n\nThe **Actions** button provides the following options:\n\n|Action Name|Description|\n|---|---|\n| Restart with Current Definitions | Restart this workflow from the beginning using the same version of the workflow definition that originally ran this workflow execution. This is useful if the workflow definition has changed and we want to retain this instance to the original version|\n| Restart with Latest Definitions | Restart this workflow from the beginning using the latest definition of the workflow. If we made changes to definition, we can use this option to re-run this flow with the latest version| \n| Retry - From failed task | Retry this workflow from the failed task| \n\n<br/>\n\n> **Note:** Conductor configurations allow your tasks to be retried automatically for transient failures.\n> Refer to the task configuration options on how to leverage this.  \n"
  },
  {
    "path": "docs/docs/how-tos/Workflows/handling-errors.md",
    "content": "# Handling Errors\n\nWhen a workflow fails, there are 2 ways to handle the exception.\n\n## Set ```failureWorkflow``` in Workflow Definition\n\nIn your main workflow definition, you can configure a workflow to run upon failure, by adding the following parameter to the workflow:\n\n```json\n\"failureWorkflow\": \"<name of your failure workflow\",\n```\n\nWhen there is an issue with your workflow, Conductor will start the failure workflow.  By default, three parameters are passed:\n\n* reason\n* workflowId: use this to pull the details of the failed workflow.\n* failureStatus\n\n### Example\n\nHere is a sample failure workflow that sends a message to Slack when the workflow fails. It posts the reason and the workflowId into a slack message - to allow for quick debugging:\n\n```json\n{\n  \"name\": \"shipping_failure\",\n  \"description\": \"workflow for failures with Bobs widget workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"slack_message\",\n      \"taskReferenceName\": \"send_slack_message\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"headers\": {\n            \"Content-type\": \"application/json\"\n          },\n          \"uri\": \"https://hooks.slack.com/services/<Unique Slack generated Key goes here>\",\n          \"method\": \"POST\",\n          \"body\": {\n            \"text\": \"workflow: ${workflow.input.workflowId} failed. ${workflow.input.reason}\"\n          },\n          \"connectionTimeOut\": 5000,\n          \"readTimeOut\": 5000\n        }\n      },\n      \"type\": \"HTTP\",\n      \"retryCount\": 3\n    }\n  ],\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"ownerEmail\": \"conductor@example.com\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n}\n```\n\n##  Set ```workflowStatusListenerEnabled``` \n\nWhen this is enabled, notifications are now possible, and by building a custom implementation of the Workflow Status Listener, a notification can be sent to an external service. [More details.](https://github.com/Netflix/conductor/issues/1017#issuecomment-468869173)\n"
  },
  {
    "path": "docs/docs/how-tos/Workflows/searching-workflows.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Searching Workflows\n\nIn this article we will learn how to search through workflow executions via the UI.\n\n### Prerequisites\n\n1. Conductor app and UI installed and running in an environment. If required we can look at the following options to get\n   an environment up and running.\n\n    1. [Build and Run Conductor Locally](/gettingstarted/local.html)\n    2. [Running via Docker Compose](/gettingstarted/docker.html)\n\n## UI Workflows View\n\nOpen the home page of the UI installation. It will take you to the `Workflow Executions` view. This is where we can look\nat available workflow executions.\n\n### Basic Search\n\nThe following fields are available for searching for workflows.\n\n| Search Field Name | Description                                                                                             |\n|-------------------|---------------------------------------------------------------------------------------------------------|\n| Workflow Name     | Use this field to filter workflows by the configured name                                               |\n| Workflow ID       | Use this field to filter to a specific workflow by its id                                               |\n| Status            | Use this field to filter by status - available options are presented as a multi-select option           |\n| Start Time - From | Use this field to filter workflows that started on or after the time specified                          |\n| Start Time - To   | Use this field to filter workflows that started on or before the time specified                         |\n| Lookback (days)   | Use this field to filter workflows that ran in the last given number of days                            |\n| Free Text Query   | If you have indexing enabled, you can query by values that was part of your workflow inputs and outputs |\n\nThe table listing has options to\n1. Select columns for display\n2. Sort by column value\n\nAt the bottom of the table, there are options to\n1. Select number of rows per page\n2. Navigating through pages\n\n### Find by Tasks\n\nIn addition to the options listed in **Basic Search** view, we have the following options in the **Find by Tasks** view.\n\n| Search Field Name  | Description                                                                                                  |\n|--------------------|--------------------------------------------------------------------------------------------------------------|\n| Include Task ID    | Use this field to filter workflows that contains a task with this id                                         |\n| Include Task Name  | Use this field to filter workflows that contains a task with name                                            |\n| Free Text in Tasks | If you have indexing enabled, you can query by values that was part of your workflow task inputs and outputs |\n\n"
  },
  {
    "path": "docs/docs/how-tos/Workflows/starting-workflows.md",
    "content": "# Starting Workflows\n\nWorkflow executions can be started by using the following API:\n\n```http\nPOST /api/workflow/{name}\n```\n\n`{name}` is the placeholder for workflow name. The POST API body is your workflow input parameters which can be empty if\nthere are none.\n\n### Using Client SDKs\n\nConductor offers client SDKs for popular languages which has library methods that can be used to make this API call.\nRefer to the SDK documentation to configure a client in your selected language to invoke workflow executions.\n\n### Example using curl\n\n```bash\ncurl 'https://localhost:8080/api/workflow/sample_workflow' \\\n  -H 'accept: text/plain' \\\n  -H 'content-type: application/json' \\\n  --data-raw '{\"service\":\"fedex\"}'\n```\n\nIn this example we are specifying one input param called `service` with a value of `fedex` and the name of the workflow\nis `sample_workflow`\n\n### Example using node fetch\n\n```javascript\nfetch(\"https://localhost:8080/api/workflow/sample_workflow\", {\n    \"headers\": {\n        \"accept\": \"text/plain\",\n        \"content-type\": \"application/json\",\n    },\n    \"body\": \"{\\\"service\\\":\\\"fedex\\\"}\",\n    \"method\": \"POST\",\n});\n```\n\n\n"
  },
  {
    "path": "docs/docs/how-tos/Workflows/updating-workflows.md",
    "content": "# Updating Workflows\n\nWorkflows can be created or updated using the workflow metadata API\n\n```html\nPUT /api/metadata/workflow\n```\n\n### Example using curl \n\n```shell\ncurl 'http://localhost:8080/api/metadata/workflow' \\\n  -X 'PUT' \\\n  -H 'accept: */*' \\\n  -H 'content-type: application/json' \\\n  --data-raw '[{\"name\":\"sample_workflow\",\"version\":1,\"tasks\":[{\"name\":\"ship_via_fedex\",\"taskReferenceName\":\"ship_via_fedex\",\"type\":\"SIMPLE\"}],\"schemaVersion\":2}]'\n```\n\n### Example using node fetch\n\n```javascript\nfetch(\"http://localhost:8080/api/metadata/workflow\", {\n  \"headers\": {\n    \"accept\": \"*/*\",\n    \"content-type\": \"application/json\"\n  },\n  \"body\": \"[{\\\"name\\\":\\\"sample_workflow\\\",\\\"version\\\":1,\\\"tasks\\\":[{\\\"name\\\":\\\"ship_via_fedex\\\",\\\"taskReferenceName\\\":\\\"ship_via_fedex\\\",\\\"type\\\":\\\"SIMPLE\\\"}],\\\"schemaVersion\\\":2}]\",\n  \"method\": \"PUT\"\n});\n```\n## Best Practices\n\n1. If you are updating the workflow with new tasks, remember to register the task definitions first\n2. You can also use the Conductor Swagger UI to update the workflows \n\n"
  },
  {
    "path": "docs/docs/how-tos/Workflows/versioning-workflows.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Versioning Workflows\n\nEvery workflow has a version number (this number **must** be an integer.)\n\nVersioning allows you to run different versions of the same workflow simultaneously.\n\n\n## Summary\n\n> Use Case:  A new version of your core workflow will add a capability that is required for *veryImportantCustomer*.  However, *otherVeryImportantCustomer* will not be ready to implement this code for another 6 months.\n\n\n## Version 1\n\n```json\n{\n  \"name\": \"Core_workflow\",\n  \"description\": \"Very_important_business\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n        <list of tasks>\n    }\n  ],\n  \"outputParameters\": {\n  }\n}\n```\n\n## Version 2\n\n```json\n{\n  \"name\": \"Core_workflow\",\n  \"description\": \"Very_important_business\",\n  \"version\": 2,\n  \"tasks\": [\n    {\n        <a different list of tasks>\n    }\n  ],\n  \"outputParameters\": {\n  }\n}\n```\n\n### Version 2 launch\nInitially, both customers are on version 1 of the workflow.\n\n* **veryImportantCustomer* may begin transitioning traffic onto version 2.  Any tasks that remain unfinished on version 1 *stay* on version 1.  \n* *otherVeryImportantCustomer* remains on version 1.\n\n\n### 6 months later\n\n* All *veryImportantCustomer* workflows are on version 2.\n* *otherVeryImportantCustomer* may begin transitioning traffic onto version 2.  Any tasks that remain unfinished on version 1 *stay* on version 1. "
  },
  {
    "path": "docs/docs/how-tos/Workflows/view-workflow-executions.md",
    "content": "---\nsidebar_position: 1\n---\n\n# View Workflow Executions\n\nIn this article we will learn how to view workflow executions via the UI.\n\n### Prerequisites\n\n1. Conductor app and UI installed and running in an environment. If required we can look at the following options to get\n   an environment up and running.\n\n    1. [Build and Run Conductor Locally](/gettingstarted/local.html)\n    2. [Running via Docker Compose](/gettingstarted/docker.html)\n\n### Viewing a Workflow Execution\n\nRefer to [Searching Workflows](/how-tos/Workflows/searching-workflows.html) to filter and find an execution you want to\nview. Click on the workflow id hyperlink to open the Workflow Execution Details page.\n\nThe following tabs are available to view the details of the Workflow Execution\n\n| Tab Name              | Description                                                                                                       |\n|-----------------------|-------------------------------------------------------------------------------------------------------------------|\n| Tasks                 | Shows a view with the sub tabs **Diagram**, **Task List** and **Timeline**                                        |\n| Tasks > Diagram       | Visual view of the workflow and its tasks.                                                                        |\n| Tasks > Task List     | Tabular view of the task executions under this workflow. If there were failures, we will be able to see that here |\n| Tasks > Timeline      | Shows the time each of the tasks took for execution in a timeline view                                            |\n| Summary               | Summary view of the workflow execution                                                                            |\n| Workflow Input/Output | Shows the input and output payloads of the workflow. Enable copy mode to copy all or parts of the payload         |\n| JSON                  | Full JSON payload of the workflow including all tasks, inputs and outputs. Useful for detailed debugging.         |\n\n### Viewing a Workflow Task Detail\n\nFrom both the **Tasks > Diagram** and **Tasks > Task List** views, we can click to see a task execution detail. This\nopens a flyout panel from the side and contains the following tabs.\n\n| Tab Name   | Description                                                                                                                              |\n|------------|------------------------------------------------------------------------------------------------------------------------------------------|\n| Summary    | Summary info of the task execution                                                                                                       |\n| Input      | Task input payload - refer to this tab to see computed inputs passed into the task. Enable copy mode to copy all or parts of the payload |\n| Output     | Shows the output payload produced by the executed task. Enable copy mode to copy all or parts of the payload                             |\n| Log        | Any log messages logged by the task worked will show up here                                                                             |\n| JSON       | Complete JSON payload for debugging issues                                                                                               |\n| Definition | Task definition used when executing this task                                                                                            |\n\n### Execution Path\n\nAn exciting feature of conductor is the ability to see the exact execution path of a workflow. The executed paths are\nshown in green and is easy to follow like the example below. The alternative paths are greyed out for reference\n\n![Conductor UI - Workflow Run](/img/tutorial/workflow_execution_view.png)\n\nErrors will be visible on the UI in ref such as the example below\n\n![Conductor UI - Failed Task](/img/tutorial/workflow_task_fail.png)\n"
  },
  {
    "path": "docs/docs/how-tos/clojure-sdk.md",
    "content": "# Conductor Clojure\n\nSoftware Development Kit for Netflix Conductor, written on and providing support for Clojure.\n\nThe code for the Clojure SDk is available on [Github](https://github.com/conductor-sdk/conductor-clojure). Please feel free to file PRs, issues, etc. there.\n\n## Get the SDK\nhttps://clojars.org/io.orkes/conductor-clojure\n\n## Quick Guide\n\n1. Create connection options\n\n```clojure\n(def options {\n                  :url  \"http://localhost:8080/api/\" ;; Conductor Server Path\n                  :app-key \"THIS-IS-SOME-APP-KEY\" ;; Optional if using Orkes Conductor\n                  :app-secret \"THIS-IS-SOME-APP-SECRET\" ;; Optional if using Orkes Conductor\n              } )\n```\n1. Creating a task using above options \n\n``` clojure\n(ns some.namespace \n    (:require [io.orkes.metadata :as metadata])\n\n    ;; Will Create a task. returns nil\n    (metadata/register-tasks options [{\n                         :name \"cool_clj_task\"\n                         :description \"some description\"\n                         :ownerEmail \"somemail@mail.com\"\n                         :retryCount 3\n                         :timeoutSeconds 300\n                         :responseTimeoutSeconds 180 }])\n)\n```\n\n2. Creating a Workflow that uses the task \n\n``` clojure\n(ns some.namespace \n    (:require [io.orkes.metadata :as metadata])\n\n;; Will Register a workflow that uses the above task returns nil\n(metadata/register-workflow-def options {\n                                              :name \"cool_clj_workflow\"\n                                              :description \"created programmatically from clj\"\n                                              :version 1\n                                              :tasks [ {\n                                                       :name \"cool_clj_task\"\n                                                       :taskReferenceName \"cool_clj_task_ref\"\n                                                       :inputParameters {}\n                                                       :type \"SIMPLE\" \n                                                       } ]\n                                              :inputParameters []\n                                              :outputParameters {:message \"${clj_prog_task_ref.output.:message}\"}\n                                              :schemaVersion 2\n                                              :restartable true\n                                              :ownerEmail \"owner@yahoo.com\"\n                                              :timeoutSeconds 0\n                                         }))\n\n```\n3. Create and run a list of workers\n\n``` clojure\n;; Creates a worker and starts polling for work. will return an instance of Runner which can then be used to shutdown\n(def instance (runner-executor-for-workers\n               (list {\n                      :name \"cool_clj_task\"\n                      :execute (fn [someData]\n                                 [:completed {:message \"Hi From Clj i was created programmatically\"}])\n                      })\n               options ))\n\n;; Shutsdown the polling for the workers defined above\n(.shutdown instance)\n               \n```\n## Options\nOptions are a map with optional paremeters\n```\n(def options {\n                  :url  \"http://localhost:8080/api/\" ;; Api url (Optional will default to \"http://localhost:8080\")\n                  :app-key \"THIS-IS-SOME-APP-KEY\" ;; Application Key (This is only relevant if you are using Orkes Conductor)\n                  :app-secret \"THIS-IS-SOME-APP-SECRET\" ;; Application Secret (This is only relevant if you are using Orkes Conductor)\n              } )\n```\n\n\n## Metadata namespace\nHolds the functions to register workflows and tasks.\n\n`(:require [conductor.metadata :as metadata])`\n\n### Registering tasks\n\nTakes the option map and a list/vector of tasks to register. on success it will return nil\n\n```clojure\n(metadata/register-tasks options [{\n                                                  :name \"cool_clj_task_b\"\n                                                  :description \"some description\"\n                                                  :ownerEmail \"mail@gmail.com\"\n                                                  :retryCount 3\n                                                  :timeoutSeconds 300\n                                                  :responseTimeoutSeconds 180 },\n                                                 {\n                                                  :name \"cool_clj_task_z\"\n                                                  :description \"some description\"\n                                                  :ownerEmail \"mail@gmail.com\"\n                                                  :retryCount 3\n                                                  :timeoutSeconds 300\n                                                  :responseTimeoutSeconds 180 }\n                                                 {\n                                                  :name \"cool_clj_task_x\"\n                                                  :description \"some description\"\n                                                  :ownerEmail \"mail@gmail.com\"\n                                                  :retryCount 3\n                                                  :timeoutSeconds 300\n                                                  :responseTimeoutSeconds 180 }\n                                                 ])\n```\n    \n### Registering a workspace \n```clojure \n(metadata/register-workflow-def options {\n                                                        :name \"cool_clj_workflow_2\"\n                                                        :description \"created programmatically from clj\"\n                                                        :version 1\n                                                        :tasks [ {\n                                                                  :name \"cool_clj_task_b\"\n                                                                  :taskReferenceName \"cool_clj_task_ref\"\n                                                                  :inputParameters {}\n                                                                  :type \"SIMPLE\"\n                                                                  },\n                                                                {\n                                                                 :name \"someting\",\n                                                                 :taskReferenceName \"other\"\n                                                                 :inputParameters {}\n                                                                 :type \"FORK_JOIN\"\n                                                                 :forkTasks [[\n                                                                               {\n                                                                                :name \"cool_clj_task_z\"\n                                                                                :taskReferenceName \"cool_clj_task_z_ref\"\n                                                                                :inputParameters {}\n                                                                                :type \"SIMPLE\"\n                                                                                }\n                                                                               ]\n                                                                              [\n                                                                               {\n                                                                                :name \"cool_clj_task_x\"\n                                                                                :taskReferenceName \"cool_clj_task_x_ref\"\n                                                                                :inputParameters {}\n                                                                                :type \"SIMPLE\"\n                                                                                }\n                                                                               ]\n                                                                              ]\n                                                                 }\n                                                                {\n                                                                 :name \"join\"\n                                                                 :type \"JOIN\"\n                                                                 :taskReferenceName \"join_ref\"\n                                                                 :joinOn [ \"cool_clj_task_z\", \"cool_clj_task_x\"]\n                                                                 }\n                                                                ]\n                                                        :inputParameters []\n                                                        :outputParameters {\"message\" \"${clj_prog_task_ref.output.:message}\"}\n                                                        :schemaVersion 2\n                                                        :restartable true\n                                                        :ownerEmail \"mail@yahoo.com\"\n                                                        :timeoutSeconds 0\n                                                        :timeoutPolicy \"ALERT_ONLY\"\n                                                        })\n```\n\n\n## Client namespace\nThe client namespace holds the function to start a workflow and running a worker\n\n`[io.orkes.client :as conductor]`\n \n``` clojure\n;; Creates a worker and starts polling for work. will return an instance of Runner which can then be used to shutdown\n(def instance (runner-executor-for-workers\n               (list {\n                      :name \"cool_clj_task\"\n                      :execute (fn [someData]\n                                 [:completed {:message \"Hi From Clj i was created programmatically\"}])\n                      })\n               options ))\n\n;; Shutsdown the polling for the workers defined above\n(.shutdown instance)\n               \n```\nThe (runner-executor-for-workers) function will take a list of worker implementations map, and options and start pooling for work\nit will return a TaskRunnerConfigurer instance, which you can shutdown by calling the .shutdown() java method\n\n## Mapper-Utils namespace\nThe  `[io.orkes.mapper-utils :as mapper-utils]` namespace holds the functions to map to java object which are mostly not necesary.\n\n### The mapper-utils/java-map->clj-map protocol\nWill map a java map to a clojure map which may come in handy for workers implementation. for example consider a worker that sums two input parameters. For a workflow defined like this :\n\n``` clojure\n(metadata/register-workflow-def options {:name \"simple_wf\"\n                                         :description \"created programmatically from clj\"\n                                         :version 1\n                                         :tasks [{:name \"simplest_task\"\n                                                  :taskReferenceName \"repl_task_ref\"\n                                                  :inputParameters {\"firstNumber\" \"${workflow.input.firstNumber}\"\n                                                                     \"secondNumber\" \"${workflow.input.secondNumber}\"}\n                                                  :type \"SIMPLE\"}]\n                                         :inputParameters [\"firstNumber\" \"secondNumber\"]\n                                         :outputParameters {\"result\" \"${repl_task_ref.output.:result}\"}\n                                         :schema-version 2\n                                         :restartable true\n                                         :ownerEmail \"mail@yahoo.com\"\n                                         :timeoutSeconds 0\n                                         :timeoutPolicy \"ALERT_ONLY\"})\n```\n\nTo be able to use the input params you would need to use the string names like this:\n\n``` clojure\n(def instance (conductor/runner-executor-for-workers\n               (list {:name \"simplest_task\"\n                      :execute (fn [someData]\n                                                            \n                                 [:completed {\"result\" (+ (get someData \"firstNumber\") (get someData \"secondNumber\"))}])})\n               options))\n```\n\nA more clojure friendly way would be to convert to clojure our map :\n\n``` clojure\n(def instance (conductor/runner-executor-for-workers\n               (list {:name \"simplest_task\"\n                      :execute (fn [someData]\n                      (let [convertedToClj (-> someData mapper-utils/java-map->clj-map)]\n                        [:completed {\"result\" (+ (:firstNumber convertedToClj) (:secondNumber convertedToClj))}]\n                      ))})\n               options))\n```\n\n\n\n"
  },
  {
    "path": "docs/docs/how-tos/csharp-sdk.md",
    "content": "# Netflix Conductor Client C# SDK\n\n`conductor-csharp` repository provides the client SDKs to build Task Workers and Clients in C#\n\nThe code for the C# SDk is available on [Github](https://github.com/conductor-sdk/conductor-csharp). Please feel free to file PRs, issues, etc. there.\n\n\n## Quick Start\n1. [Get Secrets](#Get-Secrets)\n2. [Write workers](#Write-workers)\n3. [Run workers](#Run-workers)\n4. [Worker Configurations](#Worker-Configurations)\n\n### Dependencies\n`conductor-csharp` packages are published to nugget package manager.  You can find the latest releases [here](https://www.nuget.org/packages/conductor-csharp/).\n\n### Write workers  \n\n```\n internal class MyWorkflowTask : IWorkflowTask\n    {\n        public MyWorkflowTask(){}\n\n        public string TaskType => \"test_ctask\";\n        public int? Priority => null;\n\n        public async Task<TaskResult> Execute(Conductor.Client.Models.Task task, CancellationToken token)\n        {\n           Dictionary<string, object> newOutput = new Dictionary<string, object>();\n           newOutput.Add(\"output\", \"1\");\n           return task.Completed(task.OutputData.MergeValues(newOutput));\n        }\n    }\n\n internal class MyWorkflowTask2 : IWorkflowTask\n    {\n        public MyWorkflowTask2(){}\n\n        public string TaskType => \"test_ctask2\";\n        public int? Priority => null;\n\n        public async Task<TaskResult> Execute(Conductor.Client.Models.Task task, CancellationToken token)\n        {\n           Dictionary<string, object> newOutput = new Dictionary<string, object>();\n           //Reuse the existing code written in C#\n           newOutput.Add(\"output\", \"success\");\n           return task.Completed(task.OutputData.MergeValues(newOutput));\n        }\n    }\n```\n\n### Run workers\n\n```\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Collections.Generic;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\nusing Conductor.Client.Models;\nusing Conductor.Client.Extensions;\nusing Conductor.Client.Interfaces;\n\nusing Task = System.Threading.Tasks.Task;\nusing Conductor.Client;\nusing System.Collections.Concurrent;\n\nnamespace TestOrkesSDK\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            new HostBuilder()\n                 .ConfigureServices((ctx, services) =>\n                 {\n                    // First argument is optional headers which client wasnt to pass.\n                     Configuration configuration = new Configuration(new ConcurrentDictionary<string, string>(), \n                         \"KEY\",\n                         \"SECRET\");\n                     services.AddConductorWorker(configuration);\n                     services.AddConductorWorkflowTask<MyWorkflowTask>();\n                     services.AddHostedService<WorkflowsWorkerService>();\n                 })\n                 .ConfigureLogging(logging =>\n                 {\n                     logging.SetMinimumLevel(LogLevel.Debug);\n                     logging.AddConsole();\n                 })\n                 .RunConsoleAsync();\n            Console.ReadLine();\n        }\n    }\n\n    internal class MyWorkflowTask : IWorkflowTask\n    {\n        public MyWorkflowTask() { }\n\n        public string TaskType => \"my_ctask\";\n        public int? Priority => null;\n\n        public async Task<TaskResult> Execute(Conductor.Client.Models.Task task, CancellationToken token)\n        {\n            Dictionary<string, object> newOutput = new Dictionary<string, object>();\n            newOutput.Add(\"output\", 1);\n            return task.Completed(task.OutputData.MergeValues(newOutput));\n        }\n    }\n}\n```\n\n####Note:\nReplace KEY and SECRET by obtaining a new key and secret from [Orkes Playground](https://play.orkes.io/)\n\nSee [Generating Access Keys for Programmatic Access](https://orkes.io/content/docs/getting-started/concepts/access-control#access-keys) for details./\n\n```\n    internal class WorkflowsWorkerService : BackgroundService\n    {\n        private readonly IWorkflowTaskCoordinator workflowTaskCoordinator;\n        private readonly IEnumerable<IWorkflowTask> workflowTasks;\n\n        public WorkflowsWorkerService(\n            IWorkflowTaskCoordinator workflowTaskCoordinator,\n            IEnumerable<IWorkflowTask> workflowTasks\n        )\n        {\n            this.workflowTaskCoordinator = workflowTaskCoordinator;\n            this.workflowTasks = workflowTasks;\n        }\n\n        protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n        {\n            foreach (var worker in workflowTasks)\n            {\n                workflowTaskCoordinator.RegisterWorker(worker);\n            }\n            // start all the workers so that it can poll for the tasks\n            await workflowTaskCoordinator.Start();\n        }\n    }\n```\n\n### Worker Configurations\nWorker configuration is handled via Configuration object passed when initializing TaskHandler.\n```\nConfiguration configuration = \n    new Configuration(new ConcurrentDictionary<string, string>(), \"KEY\", \"SECRET\", \"https://play.orkes.io/\");\n```\n\n### Registering and starting the workflow using SDK.\n\nBelow is the code snippet that shows how to register a simple workflow and start execution:\n\n```\nIDictionary<string, string> optionalHeaders = new ConcurrentDictionary<string, string>();\nConfiguration configuration = new Configuration(optionalHeaders, \"keyId\", \"keySecret\");\n\n//Create task definition\nMetadataResourceApi metadataResourceApi = new MetadataResourceApi(configuration);\nTaskDef taskDef = new TaskDef(name: \"test_task\");\ntaskDef.OwnerEmail = \"test@test.com\";\nmetadataResourceApi.RegisterTaskDef(new List<TaskDef>() { taskDef});\n\n//Create workflow definition\nWorkflowDef workflowDef = new WorkflowDef();\nworkflowDef.Name = \"test_workflow\";\nworkflowDef.OwnerEmail = \"test@test.com\";\nworkflowDef.SchemaVersion = 2;\n\nWorkflowTask workflowTask = new WorkflowTask();\nworkflowTask.Type = \"HTTP\";\nworkflowTask.Name = \"test_\"; //Same as registered task definition.\nIDictionary<string, string> requestParams = new Dictionary<string, string>();\nrequestParams.Add(\"uri\", \"https://www.google.com\"); //adding a key/value using the Add() method\nrequestParams.Add(\"method\", \"GET\");\nDictionary<string, object> request = new Dictionary<string, object>();\nrequest.Add(\"http_request\", requestParams);\nworkflowTask.InputParameters = request;\nworkflowDef.Tasks = new List<WorkflowTask>() { workflowTask };\n//Run a workflow\nWorkflowResourceApi workflowResourceApi = new WorkflowResourceApi(configuration);\nDictionary<string, Object> input = new Dictionary<string, Object>();\n//Fill the input map which workflow consumes.\nworkflowResourceApi.StartWorkflow(\"test_workflow\", input, 1);\nConsole.ReadLine();\n```\nPlease see [Conductor.Api](https://github.com/conductor-sdk/conductor-csharp/tree/main/Api) for the APIs."
  },
  {
    "path": "docs/docs/how-tos/go-sdk.md",
    "content": "# Netflix Conductor Go SDK\n\nThe code for the Golang SDk is available on [Github](https://github.com/conductor-sdk/conductor-go). Please feel free to file PRs, issues, etc. there.\n\n\n## Quick Start\n\n1. [Setup conductor-go package](#Setup-conductor-go-package)\n2. [Create and run Task Workers](https://github.com/conductor-sdk/conductor-go/blob/main/workers_sdk.md)\n3. [Create workflows using Code](https://github.com/conductor-sdk/conductor-go/blob/main/workflow_sdk.md)\n4. [API Documentation](https://github.com/conductor-sdk/conductor-go/blob/main/docs/)\n   \n### Setup conductor go package\n\nCreate a folder to build your package:\n```shell\nmkdir quickstart/\ncd quickstart/\ngo mod init quickstart\n```\n\nGet Conductor Go SDK\n\n```shell\ngo get github.com/conductor-sdk/conductor-go\n```\n## Configuration\n\n### Authentication settings (optional)\nUse if your conductor server requires authentication\n* keyId: Key\n* keySecret: Secret for the Key\n\n```go\nauthenticationSettings := settings.NewAuthenticationSettings(\n  \"keyId\",\n  \"keySecret\",\n)\n```\n\n### Access Control Setup\nSee [Access Control](https://orkes.io/content/docs/getting-started/concepts/access-control) for more details on role based access control with Conductor and generating API keys for your environment.\n\n### Configure API Client\n```go\n\napiClient := client.NewAPIClient(\n    settings.NewAuthenticationSettings(\n        KEY,\n        SECRET,\n    ),\n    settings.NewHttpSettings(\n        \"https://play.orkes.io\",\n    ),\n)\n\t\n```\n\n### Setup Logging\nSDK uses [logrus](https://github.com/sirupsen/logrus) for the logging.\n\n```go\nfunc init() {\n\tlog.SetFormatter(&log.TextFormatter{})\n\tlog.SetOutput(os.Stdout)\n\tlog.SetLevel(log.DebugLevel)\n}\n```\n\n### Next: [Create and run Task Workers](https://github.com/conductor-sdk/conductor-go/blob/main/workers_sdk.md)"
  },
  {
    "path": "docs/docs/how-tos/java-sdk.md",
    "content": "# Conductor Java SDK\n\nConductor provides the following java clients to interact with the various APIs\n\n| Client          | Usage                                                                     |\n|-----------------|---------------------------------------------------------------------------|\n| Metadata Client | Register / Update workflow and task definitions                           |\n| Workflow Client | Start a new workflow / Get execution status of a workflow                 |\n| Task Client     | Poll for task / Update task result after execution / Get status of a task |\n\n## Java\n\n#### Worker\nConductor provides an automated framework to poll for tasks, manage the execution thread and update the status of the execution back to the server.\n\nImplement the [Worker](https://github.com/Netflix/conductor/blob/main/client/src/main/java/com/netflix/conductor/client/worker/Worker.java) interface to execute the task.\n\n#### TaskRunnerConfigurer  \nThe TaskRunnerConfigurer can be used to register the worker(s) and initialize the polling loop.  \nManages the task workers thread pool and server communication (poll and task update).  \n\nUse the [Builder](https://github.com/Netflix/conductor/blob/master/client/src/main/java/com/netflix/conductor/client/automator/TaskRunnerConfigurer.java#L62) to create an instance of the TaskRunnerConfigurer. The builder accepts the following parameters:\n\nInitialize the Builder with the following:\n\n TaskClient \n | TaskClient used to communicate to the Conductor server |\n| Workers | Workers that will be used for polling work and task execution. |\n\n| Parameter                      | Description                                                                                                                                                                                                                    | Default                      |\n|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|\n| withEurekaClient               | EurekaClient is used to identify if the server is in discovery or not.  When the server goes out of discovery, the polling is stopped unless `pollOutOfDiscovery` is set to true. If passed null, discovery check is not done. | provided by platform         |\n| withThreadCount                | Number of threads assigned to the workers. Should be at-least the size of taskWorkers to avoid starvation in a busy system.                                                                                                    | Number of registered workers |\n| withSleepWhenRetry             | Time in milliseconds, for which the thread should sleep when task update call fails, before retrying the operation.                                                                                                            | 500                          |\n| withUpdateRetryCount           | Number of attempts to be made when updating task status when update status call fails.                                                                                                                                         | 3                            |\n| withWorkerNamePrefix           | String prefix that will be used for all the workers.                                                                                                                                                                           | workflow-worker-             |\n| withShutdownGracePeriodSeconds | Waiting seconds before forcing shutdown of your worker                                                                                                                                                                         | 10                           |\n\nOnce an instance is created, call `init()` method to initialize the TaskPollExecutor and begin the polling and execution of tasks.\n\n!!! tip \"Note\"\n    To ensure that the TaskRunnerConfigurer stops polling for tasks when the instance becomes unhealthy, call the provided `shutdown()` hook in a `PreDestroy` block.\n\n**Properties**\nThe worker behavior can be further controlled by using these properties:\n\n| Property           | Type    | Description                                                                                                                                | Default |\n|--------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------|---------|\n| paused             | boolean | If set to true, the worker stops polling.                                                                                                  | false   |\n| pollInterval       | int     | Interval in milliseconds at which the server should be polled for tasks.                                                                   | 1000    |\n| pollOutOfDiscovery | boolean | If set to true, the instance will poll for tasks regardless of the discovery  <br/> status. This is useful while running on a dev machine. | false   |\n\nFurther, these properties can be set either by Worker implementation or by setting the following system properties in the JVM:\n\n| Name                                        | Description                                                      |\n|---------------------------------------------|------------------------------------------------------------------|\n| `conductor.worker.<property>`               | Applies to ALL the workers in the JVM.                           |\n| `conductor.worker.<taskDefName>.<property>` | Applies to the specified worker.  Overrides the global property. |\n\n**Examples**\n\n* [Sample Worker Implementation](https://github.com/Netflix/conductor/blob/main/client/src/test/java/com/netflix/conductor/client/sample/SampleWorker.java)\n* [Example](https://github.com/Netflix/conductor/blob/main/client/src/test/java/com/netflix/conductor/client/sample/Main.java)\n\n"
  },
  {
    "path": "docs/docs/how-tos/python-sdk.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Python SDK\n\nSoftware Development Kit for Netflix Conductor, written on and providing support for Python.\n\nThe code for the Python SDk is available on [Github](https://github.com/conductor-sdk/conductor-python). Please feel free to file PRs, issues, etc. there.\n\n## Quick Guide\n\n1. Create a virtual environment\n\n        $ virtualenv conductor\n        $ source conductor/bin/activate\n        $ python3 -m pip list\n        Package    Version\n        ---------- -------\n        pip        22.0.3\n        setuptools 60.6.0\n        wheel      0.37.1\n\n2. Install latest version of `conductor-python` from pypi\n\n        $ python3 -m pip install conductor-python\n        Collecting conductor-python\n        Collecting certifi>=14.05.14\n        Collecting urllib3>=1.15.1\n        Requirement already satisfied: setuptools>=21.0.0 in ./conductor/lib/python3.8/site-packages (from conductor-python) (60.6.0)\n        Collecting six>=1.10\n        Installing collected packages: certifi, urllib3, six, conductor-python\n        Successfully installed certifi-2021.10.8 conductor-python-1.0.7 six-1.16.0 urllib3-1.26.8\n\n3. Create a worker capable of executing a `Task`. Example:\n\n        from conductor.client.worker.worker_interface import WorkerInterface\n\n        class SimplePythonWorker(WorkerInterface):\n            def execute(self, task):\n                task_result = self.get_task_result_from_task(task)\n                task_result.add_output_data('key', 'value')\n                task_result.status = 'COMPLETED'\n                return task_result\n\n\n    * The `add_output_data` is the most relevant part, since you can store information in a dictionary, which will be sent within `TaskResult` as your execution response to Conductor\n\n4. Create a main method to start polling tasks to execute with your worker. Example:\n\n        from conductor.client.automator.task_handler import TaskHandler\n        from conductor.client.configuration.configuration import Configuration\n        from conductor.client.worker.sample.faulty_execution_worker import FaultyExecutionWorker\n        from conductor.client.worker.sample.simple_python_worker import SimplePythonWorker\n\n\n        def main():\n            configuration = Configuration(debug=True)\n            task_definition_name = 'python_example_task'\n            workers = [\n                SimplePythonWorker(task_definition_name),\n                FaultyExecutionWorker(task_definition_name)\n            ]\n            with TaskHandler(workers, configuration) as task_handler:\n                task_handler.start()\n\n\n        if __name__ == '__main__':\n            main()\n    \n    * This example contains two workers, each with a different execution method, capable of running the same `task_definition_name`\n\n5. Now that you have implemented the example, you can start the Conductor server locally:\n      1. Clone [Netflix Conductor repository](https://github.com/Netflix/conductor):\n\n            $ git clone https://github.com/Netflix/conductor.git\n            $ cd conductor/\n\n      2. Start the Conductor server:\n        \n            /conductor$ ./gradlew bootRun\n        \n      3. Start Conductor UI:\n\n            /conductor$ cd ui/\n            /conductor/ui$ yarn install\n            /conductor/ui$ yarn run start\n\n      You should be able to access:\n      * Conductor API:\n        * http://localhost:8080/swagger-ui/index.html\n      * Conductor UI:\n        * http://localhost:5000\n\n6. Create a `Task` within `Conductor`. Example:\n\n        $ curl -X 'POST' \\\n            'http://localhost:8080/api/metadata/taskdefs' \\\n            -H 'accept: */*' \\\n            -H 'Content-Type: application/json' \\\n            -d '[\n            {\n                \"name\": \"python_task_example\",\n                \"description\": \"Python task example\",\n                \"retryCount\": 3,\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 10,\n                \"timeoutSeconds\": 300,\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"responseTimeoutSeconds\": 180,\n                \"ownerEmail\": \"example@example.com\"\n            }\n            ]'\n\n7. Create a `Workflow` within `Conductor`. Example:\n\n        $ curl -X 'POST' \\\n            'http://localhost:8080/api/metadata/workflow' \\\n            -H 'accept: */*' \\\n            -H 'Content-Type: application/json' \\\n            -d '{\n            \"createTime\": 1634021619147,\n            \"updateTime\": 1630694890267,\n            \"name\": \"workflow_with_python_task_example\",\n            \"description\": \"Workflow with Python Task example\",\n            \"version\": 1,\n            \"tasks\": [\n                {\n                \"name\": \"python_task_example\",\n                \"taskReferenceName\": \"python_task_example_ref_1\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\"\n                }\n            ],\n            \"inputParameters\": [],\n            \"outputParameters\": {\n                \"workerOutput\": \"${python_task_example_ref_1.output}\"\n            },\n            \"schemaVersion\": 2,\n            \"restartable\": true,\n            \"ownerEmail\": \"example@example.com\",\n            \"timeoutPolicy\": \"ALERT_ONLY\",\n            \"timeoutSeconds\": 0\n            }'\n\n8. Start a new workflow:\n\n        $ curl -X 'POST' \\\n            'http://localhost:8080/api/workflow/workflow_with_python_task_example' \\\n            -H 'accept: text/plain' \\\n            -H 'Content-Type: application/json' \\\n            -d '{}'\n\n    You should receive a *Workflow ID* at the *Response body*\n    * *Workflow ID* example: `8ff0bc06-4413-4c94-b27a-b3210412a914`\n    \n    Now you must be able to see its execution through the UI.\n    * Example: `http://localhost:5000/execution/8ff0bc06-4413-4c94-b27a-b3210412a914`\n\n9. Run your Python file with the `main` method\n\n### Unit Tests\n\n#### Simple validation\n\n```shell\n/conductor-python/src$ python3 -m unittest -v\ntest_execute_task (tst.automator.test_task_runner.TestTaskRunner) ... ok\ntest_execute_task_with_faulty_execution_worker (tst.automator.test_task_runner.TestTaskRunner) ... ok\ntest_execute_task_with_invalid_task (tst.automator.test_task_runner.TestTaskRunner) ... ok\n\n----------------------------------------------------------------------\nRan 3 tests in 0.001s\n\nOK\n```\n\n#### Run with code coverage\n\n```shell\n/conductor-python/src$ python3 -m coverage run --source=conductor/ -m unittest\n```\n\nReport:\n\n```shell\n/conductor-python/src$ python3 -m coverage report\n```\n\nVisual coverage results:\n\n```shell\n/conductor-python/src$ python3 -m coverage html\n```\n\n"
  },
  {
    "path": "docs/docs/index.md",
    "content": "<div class=\"container hero\">\n  <div class=\"row justify-content-center align-items-center\">\n    <div class=\"col-6\">\n      <div class=\"heading\">\n        Scalable Workflow Orchestration\n      </div>\n      <div class=\"caption pt-3\">\n        Conductor is a platform created by <b>Netflix</b> to orchestrate workflows that span across microservices.\n      </div>\n      <div class=\"mt-5\"> \n      <a type=\"button\" class=\"btn btn-primary\" href=\"/gettingstarted/local.html\">Get Started</a>\n      </div>\n    </div>\n    <div class=\"col-6\">\n      <img src=\"/img/workflow.svg\" class=\"illustration\">\n    </div>\n  </div>\n</div>\n\n\n<div class=\"container bullets\">\n  <div class=\"row justify-content-center\">\n    <div class=\"col-4\">\n      <div class=\"heading\">\n        <img src=\"/img/icons/osi.svg\" class=\"icon\"/> Open Source\n      </div>\n      <div class=\"caption\">\n        Apache-2.0 license for commercial and non-commerical use. Freedom to deploy, modify and contribute back.\n      </div>\n    </div>\n    <div class=\"col-4\">\n      <div class=\"heading\">\n        <img src=\"/img/icons/modular.svg\" class=\"icon\"/> Modular\n      </div>\n      <div class=\"caption\">\n        A fully abstracted backend enables you choose your own database persistence layer and queueing service.\n      </div>\n    </div>\n    <div class=\"col-4\">\n      <div class=\"heading\">\n        <img src=\"/img/icons/shield.svg\" class=\"icon\"/> Proven\n      </div>\n      <div class=\"caption\">        \n        Enterprise ready, Java Spring based platform that has been battle tested in production systems at Netflix and elsewhere.\n      </div>\n    </div>\n  </div>\n  \n  \n  <div class=\"row justify-content-center\">\n    <div class=\"col-4\">\n      <div class=\"heading\">\n         <img src=\"/img/icons/wrench.svg\" class=\"icon\"/> Control\n      </div>\n      <div class=\"caption\">        \n        Powerful flow control constructs including Decisions, Dynamic Fork-Joins and Subworkflows. Variables and templates are supported.\n      </div>\n    </div>\n    <div class=\"col-4\">\n      <div class=\"heading\">\n        <img src=\"/img/icons/brackets.svg\" class=\"icon\"/> Polyglot\n      </div>\n      <div class=\"caption\">        \n        Client libraries in multiple languages allows workers to be implemented in Java, Node JS, Python and C#.\n      </div>\n    </div>\n    <div class=\"col-4\">\n      <div class=\"heading\">\n         <img src=\"/img/icons/server.svg\" class=\"icon\" /> Scalable\n      </div>\n      <div class=\"caption\">        \n        Distributed architecture for both orchestrator and workers scalable from a single workflow to millions of concurrent processes.\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"container module\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-6\">\n      <div class=\"heading\">\n        Developer Experience\n      </div>\n      <div class=\"caption\">        \n        <ul>\n          <li>Discover and visualize the process flows from the bundled UI</li>\n          <li>Integrated interface to create, refine and validate workflows</li>          \n          <li>JSON based workflow definition DSL</li>\n          <li>Full featured API for custom automation</li>\n        </ui>\n      </div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"screenshot\" style=\"background-image: url(/img/tutorial/Switch_UPS.png);\"></div>\n    </div>\n  </div>\n</div>\n\n<div class=\"container module\">\n  <div class=\"row\">\n    <div class=\"col-6\">\n      <div class=\"heading\">\n        Observability\n      </div>\n      <div class=\"caption\">    \n        <ul>\n          <li>Understand, debug and iterate on task and workflow executions.</li>\n          <li>Fine grain operational control over workflows with the ability to pause, resume, restart, retry and terminate</li>\n        </ul>\n      </div>\n    </div>\n    <div class=\"col-6\">\n      <div class=\"screenshot\" style=\"background-image: url(/img/timeline.png);\"></div>\n    </div>\n  </div>\n</div>\n\n\n<div class=\"compare\">\n  <div class=\"container\">\n    <div class=\"row\">\n      <div class=\"col-12\">\n        <h2 class=\"heading\">Why Conductor?</h2>\n      </div>\n    </div>\n    <div class=\"row align-items-stretch\">\n      <div class=\"col-6\">\n      <div class=\"bubble\">\n        <h3 class=\"heading\">\n           <img src=\"/img/favicon.svg\" class=\"icon\"/> Service Orchestration\n        </h3>\n        <div class=\"caption\">        \n          <p>Workflow definitions are decoupled from task implementations. This allows the creation of process flows in which each individual task can be implemented \n          by an encapsulated microservice.</p>\n          <p>Designing a workflow orchestrator that is resilient and horizontally scalable is not a simple problem. At Netflix we have developed a solution in <b>Conductor</b>.</p>\n        </div>\n        </div>\n      </div>\n      <div class=\"col-6\">\n      <div class=\"bubble\">\n        <h3 class=\"heading\">\n          <img src=\"/img/icons/network.svg\" class=\"icon\"/> Service Choreography\n        </h3>\n        <div class=\"caption\">        \n          Process flows are implicitly defined across multiple service implementations, often with\n          tight peer-to-peer coupling between services. Multiple event buses and complex\n          pub/sub models limit observability around process progress and capacity. \n        </div>\n      </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "docs/docs/labs/beginner.md",
    "content": "# Beginner Lab\n## Hands on mode\nPlease feel free to follow along using any of these resources:\n\n- Using cURL\n- Postman or similar REST client\n\n## Creating a Workflow\n\nLet's create a simple workflow that adds Netflix Idents to videos. We'll be mocking the adding Idents part and focusing on actually executing this process flow.\n\n!!!info \"What are Netflix Idents?\" \n    Netflix Idents are those 4 second videos with Netflix logo, which appears at the beginning and end of shows.  You might have also noticed they're different for Animation and several other genres.\n\n!!!warning \"Disclaimer\"\n    Obviously, this is not how Netflix adds Idents. Those Workflows are indeed very complex. But, it should give you an idea about how Conductor can be used to implement similar features.\n\nThe workflow in this lab will look like this:\n\n![img](img/bgnr_complete_workflow.png)\n\nThis workflow contains the following:\n\n* Worker Task `verify_if_idents_are_added` to verify if Idents are already added.\n\n* [Switch Task](/reference-docs/switch-task.html) that takes output from the previous task, and decides whether to schedule the `add_idents` task.\n\n* `add_idents` task which is another worker Task.\n\n### Creating Task definitions\n\n\nLet's create the [task definition](/configuration/taskdef.html) for `verify_if_idents_are_added` in JSON. This task will be a *SIMPLE* task which is supposed to be executed by an Idents microservice. We'll be mocking the Idents microservice part.\n\n\n\n**Note** that at this point, we don't have to specify whether it is a System task or Worker task. We are only specifying the required configurations for the task, like number of times it should be retried, timeouts etc. We shall start by using `name` parameter for task name.\n```json\n{\n  \"name\": \"verify_if_idents_are_added\"\n}\n```\n\nWe'd like this task to be retried 3 times on failure.\n\n```json\n{\n  \"name\": \"verify_if_idents_are_added\",\n  \"retryCount\": 3,\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 10\n}\n```\n\nAnd to timeout after 300 seconds.  \ni.e. if the task doesn't finish execution within this time limit after transitioning to `IN_PROGRESS` state, the Conductor server cancels this task and schedules a new execution of this task in the queue.\n\n```json\n{\n  \"name\": \"verify_if_idents_are_added\",\n  \"retryCount\": 3,\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 10,\n  \"timeoutSeconds\": 300,\n  \"timeoutPolicy\": \"TIME_OUT_WF\"\n}\n```\n\nAnd a [responseTimeout](/architecture/tasklifecycle.html#response-timeout-seconds) of 180 seconds.\n\n```json\n{\n  \"name\": \"verify_if_idents_are_added\",\n  \"retryCount\": 3,\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 10,\n  \"timeoutSeconds\": 300,\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"responseTimeoutSeconds\": 180\n}\n```\n\n\nWe can define several other fields defined [here](/configuration/taskdef.html), but this is a good place to start with.\n\n\nSimilarly, create another task definition: `add_idents`.\n\n```json\n{\n  \"name\": \"add_idents\",\n  \"retryCount\": 3,\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 10,\n  \"timeoutSeconds\": 300,\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"responseTimeoutSeconds\": 180\n}\n```\n\nSend a `POST` request to `/metadata/taskdefs` endpoint to register these tasks. You can use Swagger, Postman, CURL or similar tools.\n\n!!!info \"Why is the Switch Task not registered?\"\n    System Tasks that are part of control flow do not need to be registered. However, some system tasks where the retries, rate limiting and other mechanisms are required, like `HTTP` Task, are to be registered though.\n\n!!! Important\n    Task and Workflow Definition names are unique. The names we use below might have already been registered. For this lab, add a prefix with your username, `{my_username}_verify_if_idents_are_added` for example. This is definitely not recommended for Production usage though.\n\n\n**Example**\n```\ncurl -X POST \\\n  http://localhost:8080/api/metadata/taskdefs \\\n  -H 'Content-Type: application/json' \\\n  -d '[\n\t{\n\t  \"name\": \"verify_if_idents_are_added\",\n\t  \"retryCount\": 3,\n\t  \"retryLogic\": \"FIXED\",\n\t  \"retryDelaySeconds\": 10,\n\t  \"timeoutSeconds\": 300,\n\t  \"timeoutPolicy\": \"TIME_OUT_WF\",\n\t  \"responseTimeoutSeconds\": 180,\n\t  \"ownerEmail\": \"type your email here\"\n\t},\n\t{\n\t  \"name\": \"add_idents\",\n\t  \"retryCount\": 3,\n\t  \"retryLogic\": \"FIXED\",\n\t  \"retryDelaySeconds\": 10,\n\t  \"timeoutSeconds\": 300,\n\t  \"timeoutPolicy\": \"TIME_OUT_WF\",\n\t  \"responseTimeoutSeconds\": 180,\n\t  \"ownerEmail\": \"type your email here\"\n\t}\n]'\n```\n\n### Creating Workflow Definition\n\nCreating Workflow definition is almost similar. We shall use the Task definitions created above. Note that same Task definitions can be used in multiple workflows, or for multiple times in same Workflow (that's where `taskReferenceName` is useful).\n\nA workflow without any tasks looks like this:\n```json\n{\n    \"name\": \"add_netflix_identation\",\n    \"description\": \"Adds Netflix Identation to video files.\",\n    \"version\": 1,\n    \"schemaVersion\": 2,\n    \"tasks\": []\n}\n```\n\nAdd the first task that this workflow has to execute. All the tasks must be added to the `tasks` array.\n\n```json\n{\n    \"name\": \"add_netflix_identation\",\n    \"description\": \"Adds Netflix Identation to video files.\",\n    \"version\": 1,\n    \"schemaVersion\": 2,\n    \"tasks\": [\n        {\n    \t\t\"name\": \"verify_if_idents_are_added\",\n\t\t    \"taskReferenceName\": \"ident_verification\",\n\t\t    \"inputParameters\": {\n\t\t        \"contentId\": \"${workflow.input.contentId}\"\n\t\t    },\n\t\t    \"type\": \"SIMPLE\"\n    \t}\n    ]\n}\n```\n\n**Wiring Input/Outputs**\n\nNotice how we were using `${workflow.input.contentId}` to pass inputs to this task. Conductor can wire inputs between workflow and tasks, and between tasks.  \ni.e The task `verify_if_idents_are_added` is wired to accept inputs from the workflow input using JSONPath expression `${workflow.input.param}`.\n\n\nLearn more about wiring inputs and outputs [here](/configuration/workflowdef.html#wiring-inputs-and-outputs).\n\nLet's define `decisionCases` now. \n\n\n>Note: in earlier versions of this tutorial, the \"decision\" task was used. This has been deprecated.\n\nCheckout the Switch task structure [here](/reference-docs/switch-task.html).\n\nA Switch task is specified by the `evaulatorType`, `expression` (the expression that defines the Switch) and `decisionCases` which lists all the branches of Switch task.  \n\nIn this case, we'll use `\"evaluatorType\": \"value-param\"`, meaning that we'll just use the value inputted to make the decision.  Alternatively, there is a `\"evaluatorType\": \"JavaScript\"` that can be used for more complicated evaluations.\n\nAdding the switch task (without any decision cases):\n```json\n{\n    \"name\": \"add_netflix_identation\",\n    \"description\": \"Adds Netflix Identation to video files.\",\n    \"version\": 2,\n    \"schemaVersion\": 2,\n    \"tasks\": [\n    \t{\n    \t\t\"name\": \"verify_if_idents_are_added\",\n\t\t    \"taskReferenceName\": \"ident_verification\",\n\t\t    \"inputParameters\": {\n\t\t        \"contentId\": \"${workflow.input.contentId}\"\n\t\t    },\n\t\t    \"type\": \"SIMPLE\"\n    \t},\n        {\n            \"name\": \"switch_task\",\n            \"taskReferenceName\": \"is_idents_added\",\n            \"inputParameters\": {\n                \"case_value_param\": \"${ident_verification.output.is_idents_added}\"\n            },\n            \"type\": \"SWITCH\",\n            \"evaluatorType\": \"value-param\",\n            \"expression\": \"case_value_param\",\n            \"decisionCases\": {\n                \n            }\n        }\n    ]\n}\n```\n\nEach switch task can have multiple tasks, so it has to be defined as an array.\n```json\n{\n    \"name\": \"add_netflix_identation\",\n    \"description\": \"Adds Netflix Identation to video files.\",\n    \"version\": 2,\n    \"schemaVersion\": 2,\n    \"tasks\": [\n    \t{\n    \t\t\"name\": \"verify_if_idents_are_added\",\n\t\t    \"taskReferenceName\": \"ident_verification\",\n\t\t    \"inputParameters\": {\n\t\t        \"contentId\": \"${workflow.input.contentId}\"\n\t\t    },\n\t\t    \"type\": \"SIMPLE\"\n    \t},\n        {\n            \"name\": \"switch_task\",\n            \"taskReferenceName\": \"is_idents_added\",\n            \"inputParameters\": {\n                \"case_value_param\": \"${ident_verification.output.is_idents_added}\"\n            },\n            \"type\": \"SWITCH\",\n            \"evaluatorType\": \"value-param\",\n            \"expression\": \"case_value_param\",\n            \"decisionCases\": {\n                \"false\": [\n                    {\n                        \"name\": \"add_idents\",\n                        \"taskReferenceName\": \"add_idents_by_type\",\n                        \"inputParameters\": {\n                        \t\"identType\": \"${workflow.input.identType}\",\n                        \t\"contentId\": \"${workflow.input.contentId}\"\n                        },\n                        \"type\": \"SIMPLE\"\n                    }\n                ]\n            }\n        }\n    ]\n}\n```\n\nJust like the task definitions, register this workflow definition by sending a POST request to `/workflow` endpoint.\n\n**Example**\n```\ncurl -X POST \\\n  http://localhost:8080/api/metadata/workflow \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"name\": \"add_netflix_identation\",\n    \"description\": \"Adds Netflix Identation to video files.\",\n    \"version\": 2,\n    \"schemaVersion\": 2,\n    \"tasks\": [\n    \t{\n    \t\t\"name\": \"verify_if_idents_are_added\",\n\t\t    \"taskReferenceName\": \"ident_verification\",\n\t\t    \"inputParameters\": {\n\t\t        \"contentId\": \"${workflow.input.contentId}\"\n\t\t    },\n\t\t    \"type\": \"SIMPLE\"\n    \t},\n        {\n            \"name\": \"switch_task\",\n            \"taskReferenceName\": \"is_idents_added\",\n            \"inputParameters\": {\n                \"case_value_param\": \"${ident_verification.output.is_idents_added}\"\n            },\n            \"type\": \"SWITCH\",\n            \"evaluatorType\": \"value-param\",\n            \"expression\": \"case_value_param\",\n            \"decisionCases\": {\n                \"false\": [\n                    {\n                        \"name\": \"add_idents\",\n                        \"taskReferenceName\": \"add_idents_by_type\",\n                        \"inputParameters\": {\n                        \t\"identType\": \"${workflow.input.identType}\",\n                        \t\"contentId\": \"${workflow.input.contentId}\"\n                        },\n                        \"type\": \"SIMPLE\"\n                    }\n                ]\n            }\n        }\n    ]\n}'\n```\n\n### Starting the Workflow\n\nSend a `POST` request to `/workflow` with:\n```json\n{\n    \"name\": \"add_netflix_identation\",\n    \"version\": 2,\n    \"correlationId\": \"my_netflix_identation_workflows\",\n    \"input\": {\n        \"identType\": \"animation\",\n\t    \"contentId\": \"my_unique_content_id\"\n    }\n}\n```\n\nExample:\n```\ncurl -X POST \\\n  http://localhost:8080/api/workflow/add_netflix_identation \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n\t\"identType\": \"animation\",\n\t\"contentId\": \"my_unique_content_id\"\n}'\n```\n\nSuccessful POST request should return a workflow Id, which you can use to find the execution in the UI.\n\n### Conductor User Interface\n\nOpen the UI and navigate to the RUNNING tab, the Workflow should be in the state as below:\n\n![img](img/bgnr_state_scheduled.png)\n\nFeel free to explore the various functionalities that the UI exposes. To elaborate on a few:\n\n* Workflow Task modals (Opens on clicking any of the tasks in the workflow), which includes task I/O, logs and task JSON.\n* Task Details tab, which shows the sequence of task execution, status, start/end time, and link to worker details which executed the task.\n* Input/Output tab shows workflow input and output.\n\n\n### Poll for Worker task\n\nNow that `verify_if_idents_are_added` task is in `SCHEDULED` state, it is the worker's turn to fetch the task, execute it and update Conductor with final status of the task.\n\nIdeally, the workers implementing the [Client](/gettingstarted/client.html#worker) interface would do this process, executing the tasks on real microservices. But, let's mock this part.\n\nSend a `GET` request to `/poll` endpoint with your task type.\n\nFor example:\n\n```\ncurl -X GET \\\n    http://localhost:8080/api/tasks/poll/verify_if_idents_are_added\n```\n\n\n### Return response, add logs\n\nWe can respond to Conductor with any of the following states:\n\n* Task has COMPLETED.\n* Task has FAILED.\n* Call back after seconds [Process the task at a later time].\n\nConsidering our Ident Service has verified that the Ident's are not yet added to given Content Id, let's return the task status by sending the below `POST` request to `/tasks` endpoint, with payload:\n\n```json\n{\n  \"workflowInstanceId\": \"{workflowId}\",\n  \"taskId\": \"{taskId}\",\n  \"reasonForIncompletion\": \"\",\n  \"callbackAfterSeconds\": 0,\n  \"workerId\": \"localhost\",\n  \"status\": \"COMPLETED\",\n  \"outputData\": {\n  \t\"is_idents_added\": false\n  }\n}\n```\n\nExample:\n\n```\ncurl -X POST \\\n  http://localhost:8080/api/tasks \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"workflowInstanceId\": \"cb7c5041-aa85-4940-acb4-3bdcfa9f5c5c\",\n    \"taskId\": \"741f362b-ee9a-47b6-81b5-9bbbd5c4c992\",\n    \"reasonForIncompletion\": \"\",\n    \"callbackAfterSeconds\": 0,\n    \"workerId\": \"string\",\n    \"status\": \"COMPLETED\",\n    \"outputData\": {\n        \"is_idents_added\": false\n    },\n    \"logs\": [\n        {\n            \"log\": \"Ident verification successful for title: {some_title_name}, with Id: {some_id}\",\n            \"createdTime\": 1550178825\n        }\t\n    ]\n  }'\n```\n\n!!! Info \"Check logs in UI\"\n    You can find the logs we just sent by clicking the `verify_if_idents_are_added`, upon which a modal should open with `Logs` tab.\n\n### Why is System task executed, but Worker task is Scheduled.\n\nYou will notice that Workflow is in the state as below after sending the POST request:\n\n![img](img/bgnr_systask_state.png)\n\nConductor has executed `is_idents_added` all through it's lifecycle, without us polling, or returning the status of Task. If it is still unclear, `is_idents_added` is a System task, and System tasks are executed by Conductor Server.\n\nBut, `add_idents` is a SIMPLE task. So, the complete lifecyle of this task (Poll, Update) should be handled by a worker to continue with W\\workflow execution. When Conductor has finished executing all the tasks in given flow, the workflow will reach Terminal state (COMPLETED, FAILED, TIMED_OUT etc.)\n\n## Next steps\n\nYou can play around this workflow by failing one of the Tasks, restarting or retrying the Workflow, or by tuning the number of retries, timeoutSeconds etc.\n"
  },
  {
    "path": "docs/docs/labs/eventhandlers.md",
    "content": "# Events and Event Handlers\n## About\n\nIn this Lab, we shall:\n\n* Publish an Event to Conductor using `Event` task.\n* Subscribe to Events, and perform actions:\n    * Start a Workflow\n    * Complete Task\n\nConductor Supports Eventing with two Interfaces:\n\n* [Event Task](/configuration/systask.html#event)\n* [Event Handlers](/configuration/eventhandlers.html#event-handler)\n\nWe shall create a simple cyclic workflow similar to this:\n\n![img](img/EventHandlerCycle.png)\n\n## Create Workflow Definitions\n\nLet's create two workflows:\n\n* `test_workflow_for_eventHandler` which will have an `Event` task to start another workflow, and a `WAIT` System task that will be completed by an event.\n* `test_workflow_startedBy_eventHandler` which will have an `Event` task to generate an event to complete `WAIT` task in the above workflow.\n\nSend `POST` requests to `/metadata/workflow` endpoint with below payloads:\n\n```json\n{\n  \"name\": \"test_workflow_for_eventHandler\",\n  \"description\": \"A test workflow to start another workflow with EventHandler\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"test_start_workflow_event\",\n      \"taskReferenceName\": \"start_workflow_with_event\",\n      \"type\": \"EVENT\",\n      \"sink\": \"conductor\"\n    },\n    {\n      \"name\": \"test_task_tobe_completed_by_eventHandler\",\n      \"taskReferenceName\": \"test_task_tobe_completed_by_eventHandler\",\n      \"type\": \"WAIT\"\n    }\n  ],\n  \"ownerEmail\": \"example@email.com\"\n}\n```\n\n```json\n{\n  \"name\": \"test_workflow_startedBy_eventHandler\",\n  \"description\": \"A test workflow which is started by EventHandler, and then goes on to complete task in another workflow.\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"test_complete_task_event\",\n      \"taskReferenceName\": \"complete_task_with_event\",\n      \"inputParameters\": {\n        \"sourceWorkflowId\": \"${workflow.input.sourceWorkflowId}\"\n      },\n      \"type\": \"EVENT\",\n      \"sink\": \"conductor\"\n    }\n  ],\n  \"ownerEmail\": \"example@email.com\"\n}\n```\n\n### Event Tasks in Workflow\n\n`EVENT` task is a System task, and we shall define it just like other Tasks in Workflow, with `sink` parameter. Also, `EVENT` task doesn't have to be registered before using in Workflow. This is also true for the `WAIT` task.  \nHence, we will not be registering any tasks for these workflows.\n\n## Events are sent, but they're not handled (yet)\n\nOnce you try to start `test_workflow_for_eventHandler` workflow, you would notice that the event is sent successfully, but the second worflow `test_workflow_startedBy_eventHandler` is not started. We have sent the Events, but we also need to define `Event Handlers` for Conductor to take any `actions` based on the Event. Let's create `Event Handlers`.\n\n## Create Event Handlers\n\nEvent Handler definitions are pretty much like Task or Workflow definitions. We start by name:\n\n```json\n{\n  \"name\": \"test_start_workflow\"\n}\n```\n\nEvent Handler should know the Queue it has to listen to. This should be defined in `event` parameter.\n\nWhen using Conductor queues, define `event` with format: \n\n```conductor:{workflow_name}:{taskReferenceName}```\n\nAnd when using SQS, define with format: \n\n```sqs:{my_sqs_queue_name}```\n\n```json\n{\n  \"name\": \"test_start_workflow\",\n  \"event\": \"conductor:test_workflow_for_eventHandler:start_workflow_with_event\"\n}\n```\n\nEvent Handler can perform a list of actions defined in `actions` array parameter, for this particular `event` queue.\n\n```json\n{\n  \"name\": \"test_start_workflow\",\n  \"event\": \"conductor:test_workflow_for_eventHandler:start_workflow_with_event\",\n  \"actions\": [\n      \"<insert-actions-here>\"\n  ],\n  \"active\": true\n}\n```\n\nLet's define `start_workflow` action. We shall pass the name of workflow we would like to start. The `start_workflow` parameter can use any of the values from the general [Start Workflow Request](/gettingstarted/startworkflow.html). Here we are passing in the workflowId, so that the Complete Task Event Handler can use it.\n\n```json\n{\n    \"action\": \"start_workflow\",\n    \"start_workflow\": {\n        \"name\": \"test_workflow_startedBy_eventHandler\",\n        \"input\": {\n            \"sourceWorkflowId\": \"${workflowInstanceId}\"\n        }\n    }\n}\n```\n\nSend a `POST` request to `/event` endpoint:\n\n```json\n{\n  \"name\": \"test_start_workflow\",\n  \"event\": \"conductor:test_workflow_for_eventHandler:start_workflow_with_event\",\n  \"actions\": [\n    {\n      \"action\": \"start_workflow\",\n      \"start_workflow\": {\n        \"name\": \"test_workflow_startedBy_eventHandler\",\n        \"input\": {\n          \"sourceWorkflowId\": \"${workflowInstanceId}\"\n        }\n      }\n    }\n  ],\n  \"active\": true\n}\n```\n\nSimilarly, create another Event Handler to complete task.\n\n```json\n{\n  \"name\": \"test_complete_task_event\",\n  \"event\": \"conductor:test_workflow_startedBy_eventHandler:complete_task_with_event\",\n  \"actions\": [\n    {\n    \t\"action\": \"complete_task\",\n    \t\"complete_task\": {\n\t        \"workflowId\": \"${sourceWorkflowId}\",\n\t        \"taskRefName\": \"test_task_tobe_completed_by_eventHandler\"\n\t     }\n    }\n  ],\n  \"active\": true\n}\n```\n\n## Final flow of Workflow\n\nAfter wiring all of the above, starting the `test_workflow_for_eventHandler` should:\n\n1. Start `test_workflow_startedBy_eventHandler` workflow.\n2. Sets `test_task_tobe_completed_by_eventHandler` WAIT task `IN_PROGRESS`.\n3. `test_workflow_startedBy_eventHandler` event task would publish an Event to complete the WAIT task above.\n4. Both the workflows would move to `COMPLETED` state.\n"
  },
  {
    "path": "docs/docs/labs/kitchensink.md",
    "content": "# Kitchen Sink\nAn example kitchensink workflow that demonstrates the usage of all the schema constructs.\n\n### Definition\n\n```json\n{\n  \"name\": \"kitchensink\",\n  \"description\": \"kitchensink workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"task_1\",\n      \"taskReferenceName\": \"task_1\",\n      \"inputParameters\": {\n        \"mod\": \"${workflow.input.mod}\",\n        \"oddEven\": \"${workflow.input.oddEven}\"\n      },\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"event_task\",\n      \"taskReferenceName\": \"event_0\",\n      \"inputParameters\": {\n        \"mod\": \"${workflow.input.mod}\",\n        \"oddEven\": \"${workflow.input.oddEven}\"\n      },\n      \"type\": \"EVENT\",\n      \"sink\": \"conductor\"\n    },\n    {\n      \"name\": \"dyntask\",\n      \"taskReferenceName\": \"task_2\",\n      \"inputParameters\": {\n        \"taskToExecute\": \"${workflow.input.task2Name}\"\n      },\n      \"type\": \"DYNAMIC\",\n      \"dynamicTaskNameParam\": \"taskToExecute\"\n    },\n    {\n      \"name\": \"oddEvenDecision\",\n      \"taskReferenceName\": \"oddEvenDecision\",\n      \"inputParameters\": {\n        \"oddEven\": \"${task_2.output.oddEven}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"oddEven\",\n      \"decisionCases\": {\n        \"0\": [\n          {\n            \"name\": \"task_4\",\n            \"taskReferenceName\": \"task_4\",\n            \"inputParameters\": {\n              \"mod\": \"${task_2.output.mod}\",\n              \"oddEven\": \"${task_2.output.oddEven}\"\n            },\n            \"type\": \"SIMPLE\"\n          },\n          {\n            \"name\": \"dynamic_fanout\",\n            \"taskReferenceName\": \"fanout1\",\n            \"inputParameters\": {\n              \"dynamicTasks\": \"${task_4.output.dynamicTasks}\",\n              \"input\": \"${task_4.output.inputs}\"\n            },\n            \"type\": \"FORK_JOIN_DYNAMIC\",\n            \"dynamicForkTasksParam\": \"dynamicTasks\",\n            \"dynamicForkTasksInputParamName\": \"input\"\n          },\n          {\n            \"name\": \"dynamic_join\",\n            \"taskReferenceName\": \"join1\",\n            \"type\": \"JOIN\"\n          }\n        ],\n        \"1\": [\n          {\n            \"name\": \"fork_join\",\n            \"taskReferenceName\": \"forkx\",\n            \"type\": \"FORK_JOIN\",\n            \"forkTasks\": [\n              [\n                {\n                  \"name\": \"task_10\",\n                  \"taskReferenceName\": \"task_10\",\n                  \"type\": \"SIMPLE\"\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf3\",\n                  \"inputParameters\": {\n                    \"mod\": \"${task_1.output.mod}\",\n                    \"oddEven\": \"${task_1.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ],\n              [\n                {\n                  \"name\": \"task_11\",\n                  \"taskReferenceName\": \"task_11\",\n                  \"type\": \"SIMPLE\"\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf4\",\n                  \"inputParameters\": {\n                    \"mod\": \"${task_1.output.mod}\",\n                    \"oddEven\": \"${task_1.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ]\n            ]\n          },\n          {\n            \"name\": \"join\",\n            \"taskReferenceName\": \"join2\",\n            \"type\": \"JOIN\",\n            \"joinOn\": [\n              \"wf3\",\n              \"wf4\"\n            ]\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"search_elasticsearch\",\n      \"taskReferenceName\": \"get_es_1\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"http://localhost:9200/conductor/_search?size=10\",\n          \"method\": \"GET\"\n        }\n      },\n      \"type\": \"HTTP\"\n    },\n    {\n      \"name\": \"task_30\",\n      \"taskReferenceName\": \"task_30\",\n      \"inputParameters\": {\n        \"statuses\": \"${get_es_1.output..status}\",\n        \"workflowIds\": \"${get_es_1.output..workflowId}\"\n      },\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"outputParameters\": {\n    \"statues\": \"${get_es_1.output..status}\",\n    \"workflowIds\": \"${get_es_1.output..workflowId}\"\n  },\n  \"ownerEmail\": \"example@email.com\",\n  \"schemaVersion\": 2\n}\n```\n### Visual Flow\n![img](/img/kitchensink.png)\n\n### Running Kitchensink Workflow\n1. Start the server as documented [here](/gettingstarted/docker.html).  Use ```-DloadSample=true``` java system property when launching the server.  This will create a kitchensink workflow, related task definitions and kick off an instance of kitchensink workflow.\n2. Once the workflow has started, the first task remains in the ```SCHEDULED``` state.  This is because no workers are currently polling for the task.\n3. We will use the REST endpoints directly to poll for tasks and updating the status.\n\n#### Start workflow execution\nStart the execution of the kitchensink workflow by posting the following:\n\n```shell\ncurl -X POST --header 'Content-Type: application/json' --header 'Accept: text/plain' 'http://localhost:8080/api/workflow/kitchensink' -d '\n{\n\t\"task2Name\": \"task_5\" \n}\n'\n```\nThe response is a text string identifying the workflow instance id.\n\n#### Poll for the first task:\n  \n```shell\ncurl http://localhost:8080/api/tasks/poll/task_1\n```\n   \nThe response should look something like:\n   \n```json\n{\n    \"taskType\": \"task_1\",\n    \"status\": \"IN_PROGRESS\",\n    \"inputData\": {\n        \"mod\": null,\n        \"oddEven\": null\n    },\n    \"referenceTaskName\": \"task_1\",\n    \"retryCount\": 0,\n    \"seq\": 1,\n    \"pollCount\": 1,\n    \"taskDefName\": \"task_1\",\n    \"scheduledTime\": 1486580932471,\n    \"startTime\": 1486580933869,\n    \"endTime\": 0,\n    \"updateTime\": 1486580933902,\n    \"startDelayInSeconds\": 0,\n    \"retried\": false,\n    \"callbackFromWorker\": true,\n    \"responseTimeoutSeconds\": 3600,\n    \"workflowInstanceId\": \"b0d1a935-3d74-46fd-92b2-0ca1e388659f\",\n    \"taskId\": \"b9eea7dd-3fbd-46b9-a9ff-b00279459476\",\n    \"callbackAfterSeconds\": 0,\n    \"polledTime\": 1486580933902,\n    \"queueWaitTime\": 1398\n}\n```\n#### Update the task status\n* Note the values for ```taskId``` and ```workflowInstanceId``` fields from the poll response\n* Update the status of the task as ```COMPLETED``` as below:\n\n```json\ncurl -H 'Content-Type:application/json' -H 'Accept:application/json' -X POST http://localhost:8080/api/tasks/ -d '\n{\n\t\"taskId\": \"b9eea7dd-3fbd-46b9-a9ff-b00279459476\",\n\t\"workflowInstanceId\": \"b0d1a935-3d74-46fd-92b2-0ca1e388659f\",\n\t\"status\": \"COMPLETED\",\n\t\"outputData\": {\n\t    \"mod\": 5,\n\t    \"taskToExecute\": \"task_1\",\n\t    \"oddEven\": 0,\n\t    \"dynamicTasks\": [\n\t        {\n\t            \"name\": \"task_1\",\n\t            \"taskReferenceName\": \"task_1_1\",\n\t            \"type\": \"SIMPLE\"\n\t        },\n\t        {\n\t            \"name\": \"sub_workflow_4\",\n\t            \"taskReferenceName\": \"wf_dyn\",\n\t            \"type\": \"SUB_WORKFLOW\",\n\t            \"subWorkflowParam\": {\n\t                \"name\": \"sub_flow_1\"\n\t            }\n\t        }\n\t    ],\n\t    \"inputs\": {\n\t        \"task_1_1\": {},\n\t        \"wf_dyn\": {}\n\t    }\n\t}\n}'\n```\n\nThis will mark the task_1 as completed and schedule ```task_5``` as the next task.  \nRepeat the same process for the subsequently scheduled tasks until the completion.\n"
  },
  {
    "path": "docs/docs/labs/running-first-workflow.md",
    "content": "# A First Workflow\n\nIn this article we will explore how we can run a really simple workflow that runs without deploying any new microservice. \n\nConductor can orchestrate HTTP services out of the box without implementing any code.  We will use that to create and run the first workflow.\n\nSee [System Task](/configuration/systask.html) for the list of such built-in tasks.\nUsing system tasks is a great way to run a lot of our code in production.\n\nTo bring up a local instance of Conductor follow one of the recommended steps:\n\n1. [Running Locally - From Code](/gettingstarted/local.html)\n2. [Running Locally - Docker Compose](/gettingstarted/docker.html)\n\n---\n\n## Configuring our First Workflow\n\nThis is a sample workflow that we can leverage for our test.\n\n```json\n{\n  \"name\": \"first_sample_workflow\",\n  \"description\": \"First Sample Workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"get_population_data\",\n      \"taskReferenceName\": \"get_population_data\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n          \"method\": \"GET\"\n        }\n      },\n      \"type\": \"HTTP\"\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"data\": \"${get_population_data.output.response.body.data}\",\n    \"source\": \"${get_population_data.output.response.body.source}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"ownerEmail\": \"example@email.com\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0\n}\n```\n\nThis is an example workflow that queries a publicly available JSON API to retrieve some data. This workflow doesn’t\nrequire any worker implementation as the tasks in this workflow are managed by the system itself. This is an awesome\nfeature of Conductor. For a lot of typical work, we won’t have to write any code at all.\n\nLet's talk about this workflow a little more so that we can gain some context.\n\n```json\n\"name\" : \"first_sample_workflow\"\n```\n\nThis line here is how we name our workflow. In this case our workflow name is `first_sample_workflow`\n\nThis workflow contains just one worker. The workers are defined under the key `tasks`. Here is the worker definition\nwith the most important values:\n\n```json\n{\n  \"name\": \"get_population_data\",\n  \"taskReferenceName\": \"get_population_data\",\n  \"inputParameters\": {\n    \"http_request\": {\n      \"uri\": \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n      \"method\": \"GET\"\n    }\n  },\n  \"type\": \"HTTP\"\n}\n```\n\nHere is a list of fields and what it does:\n\n1. `\"name\"` : Name of our worker\n2. `\"taskReferenceName\"` : This is a reference to this worker in this specific workflow implementation. We can have multiple\n   workers of the same name in our workflow, but we will need a unique task reference name for each of them. Task\n   reference name should be unique across our entire workflow.\n3. `\"inputParameters\"` : These are the inputs into our worker. We can hard code inputs as we have done here. We can\n   also provide dynamic inputs such as from the workflow input or based on the output of another worker. We can find\n   examples of this in our documentation.\n4. `\"type\"` : This is what defines what the type of worker is. In our example - this is `HTTP`. There are more task\n   types which we can find in the Conductor documentation.\n5. `\"http_request\"` : This is an input that is required for tasks of type `HTTP`. In our example we have provided a well\n   known internet JSON API url and the type of HTTP method to invoke - `GET`\n\nWe haven't talked about the other fields that we can use in our definitions as these are either just\nmetadata or more advanced concepts which we can learn more in the detailed documentation.\n\nOk, now that we have walked through our workflow details, let's run this and see how it works.\n\nTo configure the workflow, head over to the swagger API of conductor server and access the metadata workflow create API:\n\n[http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config#/metadata-resource/create](http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config#/metadata-resource/create)\n\nIf the link doesn’t open the right Swagger section, we can navigate to Metadata-Resource\n→ `POST /api/metadata/workflow`\n\n![Swagger UI - Metadata - Workflow](/img/tutorial/metadataWorkflowPost.png)\n\nPaste the workflow payload into the Swagger API and hit Execute.\n\nNow if we head over to the UI, we can see this workflow definition created:\n\n![Conductor UI - Workflow Definition](/img/tutorial/uiWorkflowDefinition.png)\n\nIf we click through we can see a visual representation of the workflow:\n\n![Conductor UI - Workflow Definition - Visual Flow](/img/tutorial/uiWorkflowDefinitionVisual.png)\n\n## 2. Running our First Workflow\n\nLet’s run this workflow. To do that we can use the swagger API under the workflow-resources\n\n[http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config#/workflow-resource/startWorkflow_1](http://localhost:8080/swagger-ui/index.html?configUrl=/api-docs/swagger-config#/workflow-resource/startWorkflow_1)\n\n![Swagger UI - Metadata - Workflow - Run](/img/tutorial/metadataWorkflowRun.png)\n\nHit **Execute**!\n\nConductor will return a workflow id. We will need to use this id to load this up on the UI. If our UI installation has\nsearch enabled we wouldn't need to copy this. If we don't have search enabled (using Elasticsearch) copy it from the\nSwagger UI.\n\n![Swagger UI - Metadata - Workflow - Run](/img/tutorial/workflowRunIdCopy.png)\n\nOk, we should see this running and get completed soon. Let’s go to the UI to see what happened.\n\nTo load the workflow directly, use this URL format:\n\n```\nhttp://localhost:5000/execution/<WORKFLOW_ID>\n```\n\nReplace `<WORKFLOW_ID>` with our workflow id from the previous step. We should see a screen like below. Click on the\ndifferent tabs to see all inputs and outputs and task list etc. Explore away!\n\n![Conductor UI - Workflow Run](/img/tutorial/workflowLoaded.png)\n\n## Summary\n\nIn this blog post — we learned how to run a sample workflow in our Conductor installation. Concepts we touched on:\n\n1. Workflow creation\n2. System tasks such as HTTP\n3. Running a workflow via API\n\nThank you for reading, and we hope you found this helpful. Please feel free to reach out to us for any questions and we\nare happy to help in any way we can.\n\n"
  },
  {
    "path": "docs/docs/metrics/client.md",
    "content": "# Client Metrics\n\nWhen using the Java client, the following metrics are published:\n\n| Name        | Purpose           | Tags  |\n| ------------- |:-------------| -----|\n| task_execution_queue_full | Counter to record execution queue has saturated | taskType|\n| task_poll_error | Client error when polling for a task queue | taskType, includeRetries, status |\n| task_paused | Counter for number of times the task has been polled, when the worker has been paused | taskType |\n| task_execute_error | Execution error | taskType|\n| task_ack_failed | Task ack failed | taskType |\n| task_ack_error | Task ack has encountered an exception | taskType |\n| task_update_error | Task status cannot be updated back to server  | taskType |\n| task_poll_counter | Incremented each time polling is done  | taskType |\n| task_poll_time | Time to poll for a batch of tasks | taskType |\n| task_execute_time | Time to execute a task  | taskType |\n| task_result_size | Records output payload size of a task | taskType |\n| workflow_input_size | Records input payload size of a workflow | workflowType, workflowVersion |\n| external_payload_used | Incremented each time external payload storage is used | name, operation, payloadType | \n\nMetrics on client side supplements the one collected from server in identifying the network as well as client side issues.\n\n[1]: https://github.com/Netflix/spectator\n"
  },
  {
    "path": "docs/docs/metrics/server.md",
    "content": "# Server Metrics\n\nConductor uses [spectator](https://github.com/Netflix/spectator) to collect the metrics.\n\n- To enable conductor serve to publish metrics, add this [dependency](http://netflix.github.io/spectator/en/latest/registry/metrics3/) to your build.gradle.\n- Conductor Server enables you to load additional modules dynamically, this feature can be controlled using this [configuration](https://github.com/Netflix/conductor/blob/master/server/README.md#additional-modules-optional).\n- Create your own AbstractModule that overides configure function and registers the Spectator metrics registry.\n- Initialize the Registry and add it to the global registry via ```((CompositeRegistry)Spectator.globalRegistry()).add(...)```.\n\nThe following metrics are published by the server. You can use these metrics to configure alerts for your workflows and tasks.\n\n| Name        | Purpose           | Tags  |\n| ------------- |:-------------| -----|\n| workflow_server_error | Rate at which server side error is happening | methodName|\n| workflow_failure | Counter for failing workflows|workflowName, status|\n| workflow_start_error | Counter for failing to start a workflow|workflowName|\n| workflow_running | Counter for no. of running workflows | workflowName, version|\n| workflow_execution | Timer for Workflow completion | workflowName, ownerApp |\n| task_queue_wait | Time spent by a task in queue | taskType|\n| task_execution | Time taken to execute a task | taskType, includeRetries, status |\n| task_poll | Time taken to poll for a task | taskType|\n| task_poll_count | Counter for number of times the task is being polled | taskType, domain |\n| task_queue_depth | Pending tasks queue depth | taskType, ownerApp |\n| task_rate_limited | Current number of tasks being rate limited | taskType |\n| task_concurrent_execution_limited | Current number of tasks being limited by concurrent execution limit | taskType |\n| task_timeout | Counter for timed out tasks | taskType |\n| task_response_timeout | Counter for tasks timedout due to responseTimeout | taskType |\n| task_update_conflict | Counter for task update conflicts. Eg: when the workflow is in terminal state | workflowName, taskType, taskStatus, workflowStatus |\n| event_queue_messages_processed | Counter for number of messages fetched from an event queue | queueType, queueName |\n| observable_queue_error | Counter for number of errors encountered when fetching messages from an event queue | queueType |\n| event_queue_messages_handled | Counter for number of messages executed from an event queue | queueType, queueName |\n| external_payload_storage_usage | Counter for number of times external payload storage was used | name, operation, payloadType |\n\n[1]: https://github.com/Netflix/spectator\n\n## Collecting metrics with Log4j\n\nOne way of collecting metrics is to push them into the logging framework (log4j).\nLog4j supports various appenders that can print metrics into a console/file or even send them to remote metrics collectors over e.g. syslog channel.\n\nConductor provides optional modules that connect metrics registry with the logging framework.\nTo enable these modules, configure following additional modules property in config.properties:\n\n    conductor.metrics-logger.enabled = true\n    conductor.metrics-logger.reportPeriodSeconds = 15\n    \nThis will push all available metrics into log4j every 15 seconds.\n\nBy default, the metrics will be handled as a regular log message (just printed to console with default log4j.properties).\nIn order to change that, you can use following log4j configuration that prints metrics into a dedicated file:\n\n    log4j.rootLogger=INFO,console,file\n    \n    log4j.appender.console=org.apache.log4j.ConsoleAppender\n    log4j.appender.console.layout=org.apache.log4j.PatternLayout\n    log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n    \n    log4j.appender.file=org.apache.log4j.RollingFileAppender\n    log4j.appender.file.File=/app/logs/conductor.log\n    log4j.appender.file.MaxFileSize=10MB\n    log4j.appender.file.MaxBackupIndex=10\n    log4j.appender.file.layout=org.apache.log4j.PatternLayout\n    log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n    \n    # Dedicated file appender for metrics\n    log4j.appender.fileMetrics=org.apache.log4j.RollingFileAppender\n    log4j.appender.fileMetrics.File=/app/logs/metrics.log\n    log4j.appender.fileMetrics.MaxFileSize=10MB\n    log4j.appender.fileMetrics.MaxBackupIndex=10\n    log4j.appender.fileMetrics.layout=org.apache.log4j.PatternLayout\n    log4j.appender.fileMetrics.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n    \n    log4j.logger.ConductorMetrics=INFO,console,fileMetrics\n    log4j.additivity.ConductorMetrics=false\n\nThis configuration is bundled with conductor-server in file: log4j-file-appender.properties and can be utilized by setting env var:\n\n    LOG4J_PROP=log4j-file-appender.properties\n    \nThis variable is used by _startup.sh_ script.\n\n### Integration with logstash using a log file\n\nThe metrics collected by log4j can be further processed and pushed into a central collector such as ElasticSearch.\nOne way of achieving this is to use: log4j file appender -> logstash -> ElasticSearch.\n\nConsidering the above setup, you can deploy logstash to consume the contents of /app/logs/metrics.log file, process it and send further to elasticsearch.\n\nFollowing configuration needs to be used in logstash to achieve it:\n\npipeline.yml:\n\n    - pipeline.id: conductor_metrics\n      path.config: \"/usr/share/logstash/pipeline/logstash_metrics.conf\"\n      pipeline.workers: 2\n\nlogstash_metrics.conf\n\n    input {\n    \n     file {\n      path => [\"/conductor-server-logs/metrics.log\"]\n      codec => multiline {\n          pattern => \"^%{TIMESTAMP_ISO8601} \"\n          negate => true\n          what => previous\n      }\n     }\n    }\n    \n    filter {\n        kv {\n            field_split => \", \"\n            include_keys => [ \"name\", \"type\", \"count\", \"value\" ]\n        }\n        mutate {\n            convert => {\n              \"count\" => \"integer\"\n              \"value\" => \"float\"\n            }\n          }\n    }\n    \n    output {\n     elasticsearch {\n      hosts => [\"elasticsearch:9200\"]\n     }\n    }\n\nNote: In addition to forwarding the metrics into ElasticSearch, logstash will extract following fields from each metric: name, type, count, value and set proper types\n\n### Integration with fluentd using a syslog channel\n\nAnother example of metrics collection uses: log4j syslog appender -> fluentd -> prometheus.\n\nIn this case, a specific log4j properties file needs to be used so that metrics are pushed into a syslog channel:\n\n```\n    log4j.rootLogger=INFO,console,file\n    \n    log4j.appender.console=org.apache.log4j.ConsoleAppender\n    log4j.appender.console.layout=org.apache.log4j.PatternLayout\n    log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n    \n    log4j.appender.file=org.apache.log4j.RollingFileAppender\n    log4j.appender.file.File=/app/logs/conductor.log\n    log4j.appender.file.MaxFileSize=10MB\n    log4j.appender.file.MaxBackupIndex=10\n    log4j.appender.file.layout=org.apache.log4j.PatternLayout\n    log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n    \n    # Syslog based appender streaming metrics into fluentd\n    log4j.appender.server=org.apache.log4j.net.SyslogAppender\n    log4j.appender.server.syslogHost=fluentd:5170\n    log4j.appender.server.facility=LOCAL1\n    log4j.appender.server.layout=org.apache.log4j.PatternLayout\n    log4j.appender.server.layout.ConversionPattern=%d{ISO8601} %5p [%t] (%C) - %m%n\n    \n    log4j.logger.ConductorMetrics=INFO,console,server\n    log4j.additivity.ConductorMetrics=false\n```\n\nAnd on the fluentd side you need following configuration:\n\n```\n    <source>\n      @type prometheus\n    </source>\n    \n    <source>\n      @type syslog\n      port 5170\n      bind 0.0.0.0\n      tag conductor\n        <parse>\n         ; only allow TIMER metrics of workflow execution and extract tenant ID\n          @type regexp\n          expression /^.*type=TIMER, name=workflow_execution.class-WorkflowMonitor.+workflowName-(?<tenant>.*)_(?<workflow>.+), count=(?<count>\\d+), min=(?<min>[\\d.]+), max=(?<max>[\\d.]+), mean=(?<mean>[\\d.]+).*$/\n          types count:integer,min:float,max:float,mean:float\n        </parse>\n    </source>\n    \n    <filter conductor.local1.info>\n        @type prometheus\n        <metric>\n          name conductor_workflow_count\n          type gauge\n          desc The total number of executed workflows\n          key count\n          <labels>\n            workflow ${workflow}\n            tenant ${tenant}\n            user ${email}\n          </labels>\n        </metric>\n        <metric>\n          name conductor_workflow_max_duration\n          type gauge\n          desc Max duration in millis for a workflow\n          key max\n          <labels>\n            workflow ${workflow}\n            tenant ${tenant}\n            user ${email}\n          </labels>\n        </metric>\n        <metric>\n          name conductor_workflow_mean_duration\n          type gauge\n          desc Mean duration in millis for a workflow\n          key mean\n          <labels>\n            workflow ${workflow}\n            tenant ${tenant}\n            user ${email}\n          </labels>\n        </metric>\n    </filter>\n    \n    <match **>\n      @type stdout\n    </match>\n```\n\nWith above configuration, fluentd will:\n- Listen to raw metrics on 0.0.0.0:5170\n- Collect only workflow_execution TIMER metrics\n- Process the raw metrics and expose 3 prometheus specific metrics\n- Expose prometheus metrics on http://fluentd:24231/metrics \n\n## Collecting metrics with Prometheus\nAnother way to collect metrics is using Prometheus client to push them to Prometheus server.\n\nConductor provides optional modules that connect metrics registry with Prometheus.\nTo enable these modules, configure following additional module property in config.properties:\n\n    conductor.metrics-prometheus.enabled = true\n    \nThis will simply push these metrics via Prometheus collector.\nHowever, you need to configure your own Prometheus collector and expose the metrics via an endpoint.\n"
  },
  {
    "path": "docs/docs/reference-docs/annotation-processor.md",
    "content": "# Annotation Processor\n\n- Original Author: Vicent Martí - https://github.com/vmg\n- Original Repo: https://github.com/vmg/protogen\n\nThis module is strictly for code generation tasks during builds based on annotations.\nCurrently supports `protogen`\n\n### Usage\n\nSee example below\n\n### Example\n\nThis is an actual example of this module which is implemented in common/build.gradle\n\n```groovy\ntask protogen(dependsOn: jar, type: JavaExec) {\n    classpath configurations.annotationsProcessorCodegen\n    main = 'com.netflix.conductor.annotationsprocessor.protogen.ProtoGenTask'\n    args(\n            \"conductor.proto\",\n            \"com.netflix.conductor.proto\",\n            \"github.com/netflix/conductor/client/gogrpc/conductor/model\",\n            \"${rootDir}/grpc/src/main/proto\",\n            \"${rootDir}/grpc/src/main/java/com/netflix/conductor/grpc\",\n            \"com.netflix.conductor.grpc\",\n            jar.archivePath,\n            \"com.netflix.conductor.common\",\n    )\n}\n```\n\n"
  },
  {
    "path": "docs/docs/reference-docs/archival-of-workflows.md",
    "content": "# Archival Of Workflows\n\nConductor has support for archiving workflow upon termination or completion. Enabling this will delete the workflow from the configured database, but leave the associated data in Elasticsearch so it is still searchable. \n\nTo enable, set the `conductor.workflow-status-listener.type` property to `archive`.\n\nA number of additional properties are available to control archival.\n\n| Property | Default Value | Description |\n| -- | -- | -- |\n| conductor.workflow-status-listener.archival.ttlDuration\t| 0s | The time to live in seconds for workflow archiving module. Currently, only RedisExecutionDAO supports this |\n| conductor.workflow-status-listener.archival.delayQueueWorkerThreadCount\t| 5 | The number of threads to process the delay queue in workflow archival |\n| conductor.workflow-status-listener.archival.delaySeconds |\t60 | The time to delay the archival of workflow |\n"
  },
  {
    "path": "docs/docs/reference-docs/azureblob-storage.md",
    "content": "# Azure Blob Storage\n\nThe [AzureBlob storage](https://github.com/Netflix/conductor/tree/main/azureblob-storage) module uses azure blob to store and retrieve workflows/tasks input/output payload that\nwent over the thresholds defined in properties named `conductor.[workflow|task].[input|output].payload.threshold.kb`.\n\n**Warning** Azure Java SDK use libs already present inside `conductor` like `jackson` and `netty`.\nYou may encounter deprecated issues, or conflicts and need to adapt the code if the module is not maintained along with `conductor`.\nIt has only been tested with **v12.2.0**.\n\n## Configuration\n\n### Usage\n\nCf. Documentation [External Payload Storage](https://netflix.github.io/conductor/externalpayloadstorage/#azure-blob-storage)\n\n### Example\n\n```properties\nconductor.additional.modules=com.netflix.conductor.azureblob.AzureBlobModule\nes.set.netty.runtime.available.processors=false\n\nworkflow.external.payload.storage=AZURE_BLOB\nworkflow.external.payload.storage.azure_blob.connection_string=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;EndpointSuffix=localhost\nworkflow.external.payload.storage.azure_blob.signedurlexpirationseconds=360\n```\n\n## Testing\n\nYou can use [Azurite](https://github.com/Azure/Azurite) to simulate an Azure Storage.\n\n### Troubleshoots\n\n* When using **es5 persistence** you will receive an `java.lang.IllegalStateException` because the Netty lib will call `setAvailableProcessors` two times. To resolve this issue you need to set the following system property\n\n```\nes.set.netty.runtime.available.processors=false\n```\n\nIf you want to change the default HTTP client of azure sdk, you can use `okhttp` instead of `netty`.\nFor that you need to add the following [dependency](https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/storage/azure-storage-blob#default-http-client).\n\n```\ncom.azure:azure-core-http-okhttp:${compatible version}\n```\n"
  },
  {
    "path": "docs/docs/reference-docs/directed-acyclic-graph.md",
    "content": "# Directed Acyclic Graph (DAG)\n## What is a Directed Acyclic Graph (DAG)?\nConductor workflows are directed acyclic graphs (DAGs). But, what exactly is a DAG?\n\nTo understand a DAG, we'll walk through each term (but not in order):\n\n### Graph\n\nA graph is \"a collection of vertices (or point) and edges (or lines) that indicate connections between the vertices.\"  \n\nBy this definition, this is a graph - just not exactly correct in the context of DAGs:\n\n<p align=\"center\"><img src=\"/img/pirate_graph.gif\" alt=\"pirate vs global warming graph\" width=\"500\" style={{paddingBottom: 40, paddingTop: 40}} /></p>\n\nBut in the context of workflows, we're thinking of a graph more like this:\n\n<p align=\"center\"><img src=\"/img/regular_graph.png\" alt=\"a regular graph (source: wikipedia)\" width=\"500\" style={{paddingBottom: 40, paddingTop: 40}} /></p>\n\nImagine each vertex as a microservice, and the lines are how the microservices are connected together. However, this graph is not a directed graph - as there is no direction given to each connection.\n\n### Directed\n\nA directed graph means that there is a direction to each connection. For example, this graph is directed:\n\n<p align=\"center\"><img src=\"/img/directed_graph.png\" alt=\"directed graph\" width=\"500\" style={{paddingBottom: 40, paddingTop: 40}} /></p>\n\nEach arrow has a direction, Point \"N\" can proceed directly to \"B\", but \"B\" cannot proceed to \"N\" in the opposite direction.  \n\n### Acyclic\n\nAcyclic means without circular or cyclic paths.  In the directed example above,  A -> B -> D -> A is a cyclic loop.  \n\nSo a Directed Acyclic Graph is a set of vertices where the connections are directed without any looping.  DAG charts can only \"move forward\" and cannot redo a step (or series of steps.)\n\nSince a Conductor workflow is a series of vertices that can connect in only a specific direction and cannot loop, a Conductor workflow is thus a directed acyclic graph:\n\n<p align=\"center\"><img src=\"/img/dag_workflow2.png\" alt=\"Conductor Dag\" width=\"300\" style={{paddingBottom: 40, paddingTop: 40}} /></p>\n\n### Can a workflow have loops and still be a DAG?\n\nYes. For example, Conductor workflows have Do-While loops:\n\n<p align=\"center\"><img src=\"/img/dag_workflow.png\" alt=\"Conductor Dag\" width=\"300\" style={{paddingBottom: 40, paddingTop: 40}} /></p>\n\nThis is still a DAG, because the loop is just shorthand for running the tasks inside the loop over and over again.  For example, if the 2nd loop in the above image is run 3 times, the workflow path will be:\n\n1. zero_offset_fix_1\n2. post_to_orbit_ref_1\n3. zero_offset_fix_2\n4. post_to_orbit_ref_2\n5. zero_offset_fix_3\n6. post_to_orbit_ref_3\n\nThe path is directed forward, and the loop just makes it easier to define the workflow.\n"
  },
  {
    "path": "docs/docs/reference-docs/do-while-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Do-While\n```json\n\"type\" : \"DO_WHILE\"\n```\n## Introduction\nSequentially execute a list of task as long as a condition is true. \nThe list of tasks is executed first, before the condition is checked (even for the first iteration).\n\nWhen scheduled, each task of this loop will see its `taskReferenceName` concatenated with __i, with i being the iteration number, starting at 1. Warning: taskReferenceName containing arithmetic operators must not be used.\n\nEach task output is stored as part of the DO_WHILE task, indexed by the iteration value (see example below), allowing the condition to reference the output of a task for a specific iteration (eg. $.LoopTask['iteration]['first_task'])\n\nThe DO_WHILE task is set to `FAILED` as soon as one of the loopOver fails. In such case retry, iteration starts from 1.\n\n### Limitations \n- Domain or isolation group execution is unsupported; \n- Nested DO_WHILE is unsupported, however, DO_WHILE task supports SUB_WORKFLOW as loopOver task, so we can achieve similar functionality.\n- Since loopover tasks will be executed in loop inside scope of parent do while task, crossing branching outside of DO_WHILE task is not respected.\n\n\nBranching inside loopOver task is supported.\n\n\n\n## Configuration\n\n### Input Parameters:\n\n| name          | type       | description                                                                                                                                                                                                             |\n|---------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| loopCondition | String     | Condition to be evaluated after every iteration. This is a Javascript expression, evaluated using the Nashorn engine. If an exception occurs during evaluation, the DO_WHILE task is set to FAILED_WITH_TERMINAL_ERROR. |\n| loopOver      | List[Task] | List of tasks that needs to be executed as long as the condition is true.                                                                                                                                               |\n\n### Output Parameters\n\n| name      | type             | description                                                                                                                                                                                           |\n|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| iteration | Integer          | Iteration number: the current one while executing; the final one once the loop is finished                                                                                                            |\n| `i`       | Map[String, Any] | Iteration number as a string, mapped to the task references names and their output.                                                                                                                   |\n| *         | Any              | Any state can be stored here if the `loopCondition` does so. For example `storage` will exist if `loopCondition` is `if ($.LoopTask['iteration'] <= 10) {$.LoopTask.storage = 3; true } else {false}` |\n\n## Examples\n\nThe following definition:\n```json\n{\n    \"name\": \"Loop Task\",\n    \"taskReferenceName\": \"LoopTask\",\n    \"type\": \"DO_WHILE\",\n    \"inputParameters\": {\n      \"value\": \"${workflow.input.value}\"\n    },\n    \"loopCondition\": \"if ( ($.LoopTask['iteration'] < $.value ) || ( $.first_task['response']['body'] > 10)) { false; } else { true; }\",\n    \"loopOver\": [\n        {\n            \"name\": \"first task\",\n            \"taskReferenceName\": \"first_task\",\n            \"inputParameters\": {\n                \"http_request\": {\n                    \"uri\": \"http://localhost:8082\",\n                    \"method\": \"POST\"\n                }\n            },\n            \"type\": \"HTTP\"\n        },{\n            \"name\": \"second task\",\n            \"taskReferenceName\": \"second_task\",\n            \"inputParameters\": {\n                \"http_request\": {\n                    \"uri\": \"http://localhost:8082\",\n                    \"method\": \"POST\"\n                }\n            },\n            \"type\": \"HTTP\"\n        }\n    ],\n    \"startDelay\": 0,\n    \"optional\": false\n}\n```\n\nwill produce the following execution, assuming 3 executions occurred (alongside `first_task__1`, `first_task__2`, `first_task__3`,\n`second_task__1`, `second_task__2` and `second_task__3`):\n\n```json\n{\n    \"taskType\": \"DO_WHILE\",\n    \"outputData\": {\n        \"iteration\": 3,\n        \"1\": {\n            \"first_task\": {\n                \"response\": {},\n                \"headers\": {\n                    \"Content-Type\": \"application/json\"\n                }\n            },\n            \"second_task\": {\n                \"response\": {},\n                \"headers\": {\n                    \"Content-Type\": \"application/json\"\n                }\n            }\n        },\n        \"2\": {\n            \"first_task\": {\n                \"response\": {},\n                \"headers\": {\n                    \"Content-Type\": \"application/json\"\n                }\n            },\n            \"second_task\": {\n                \"response\": {},\n                \"headers\": {\n                    \"Content-Type\": \"application/json\"\n                }\n            }\n        },\n        \"3\": {\n            \"first_task\": {\n                \"response\": {},\n                \"headers\": {\n                    \"Content-Type\": \"application/json\"\n                }\n            },\n            \"second_task\": {\n                \"response\": {},\n                \"headers\": {\n                    \"Content-Type\": \"application/json\"\n                }\n            }\n        }\n    }\n}\n```\n\n## Example using iteration key\n\nSometimes, you may want to use the iteration value/counter in the tasks used in the loop.  In this example, an API call is made to GitHub (to the Netflix Conductor repository), but each loop increases the pagination.\n\nThe Loop ```taskReferenceName``` is \"get_all_stars_loop_ref\".\n\nIn the ```loopCondition``` the term ```$.get_all_stars_loop_ref['iteration']``` is used.\n\nIn tasks embedded in the loop, ```${get_all_stars_loop_ref.output.iteration}``` is used.  In this case, it is used to define which page of results the API should return.\n\n```json\n{\n      \"name\": \"get_all_stars\",\n      \"taskReferenceName\": \"get_all_stars_loop_ref\",\n      \"inputParameters\": {\n        \"stargazers\": \"4000\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.get_all_stars_loop_ref['iteration'] < Math.ceil($.stargazers/100)) { true; } else { false; }\",\n      \"loopOver\": [\n        {\n          \"name\": \"100_stargazers\",\n          \"taskReferenceName\": \"hundred_stargazers_ref\",\n          \"inputParameters\": {\n            \"counter\": \"${get_all_stars_loop_ref.output.iteration}\",\n            \"http_request\": {\n              \"uri\": \"https://api.github.com/repos/ntflix/conductor/stargazers?page=${get_all_stars_loop_ref.output.iteration}&per_page=100\",\n              \"method\": \"GET\",\n              \"headers\": {\n                \"Authorization\": \"token ${workflow.input.gh_token}\",\n                \"Accept\": \"application/vnd.github.v3.star+json\"\n              }\n            }\n          },\n          \"type\": \"HTTP\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": [],\n          \"retryCount\": 3\n        }\n      ]\n    }\n\n```\n"
  },
  {
    "path": "docs/docs/reference-docs/dynamic-fork-task.md",
    "content": "# Dynamic Fork\n```json\n\"type\" : \"FORK_JOIN_DYNAMIC\"\n```\n\n## Introduction\n\nA Fork operation in conductor, lets you run a specified list of other tasks or sub workflows in parallel after the fork\ntask. A fork task is followed by a join operation that waits on the forked tasks or sub workflows to finish. The `JOIN`\ntask also collects outputs from each of the forked tasks or sub workflows.\n\nIn a regular fork operation (`FORK_JOIN` task), the list of tasks or sub workflows that need to be forked and run in\nparallel are already known at the time of workflow definition creation time. However, there are cases when that list can\nonly be determined at run-time and that is when the dynamic fork operation (FORK_JOIN_DYNAMIC task) is needed.\n\nThere are three things that are needed to configure a `FORK_JOIN_DYNAMIC` task.\n\n1. A list of tasks or sub-workflows that needs to be forked and run in parallel.\n2. A list of inputs to each of these forked tasks or sub-workflows\n3. A task prior to the `FORK_JOIN_DYNAMIC` tasks outputs 1 and 2 above that can be wired in as in input to\n   the `FORK_JOIN_DYNAMIC` tasks\n\n## Use Cases\n\nA `FORK_JOIN_DYNAMIC` is useful, when a set of tasks or sub-workflows needs to be executed and the number of tasks or\nsub-workflows are determined at run time. E.g. Let's say we have a task that resizes an image, and we need to create a\nworkflow that will resize an image into multiple sizes. In this case, a task can be created prior to\nthe `FORK_JOIN_DYNAMIC` task that will prepare the input that needs to be passed into the `FORK_JOIN_DYNAMIC` task. The\nsingle image resize task does one job. The `FORK_JOIN_DYNAMIC` and the following `JOIN` will manage the multiple\ninvokes of the single image resize task. Here, the responsibilities are clearly broken out, where the single image resize\ntask does the core job and `FORK_JOIN_DYNAMIC` manages the orchestration and fault tolerance aspects.\n\n## Configuration\n\nHere is an example of a `FORK_JOIN_DYNAMIC` task followed by a `JOIN` task\n\n```json\n{\n  \"inputParameters\": {\n    \"dynamicTasks\": \"${fooBarTask.output.dynamicTasksJSON}\",\n    \"dynamicTasksInput\": \"${fooBarTask.output.dynamicTasksInputJSON}\"\n  },\n  \"type\": \"FORK_JOIN_DYNAMIC\",\n  \"dynamicForkTasksParam\": \"dynamicTasks\",\n  \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\"\n},\n{\n\"name\": \"image_multiple_convert_resize_join\",\n\"taskReferenceName\": \"image_multiple_convert_resize_join_ref\",\n\"type\": \"JOIN\"\n}\n```\n\nDissecting into this example above, let's look at the three things that are needed to configured for\nthe `FORK_JOIN_DYNAMIC` task\n\n`dynamicForkTasksParam` This is a JSON array of task or sub-workflow objects that specifies the list of tasks or\nsub-workflows that needs to be forked and run in parallel `dynamicForkTasksInputParamName` This is a JSON map of task or\nsub-workflow objects that specifies the list of tasks or sub-workflows that needs to be forked and run in parallel\nfooBarTask This is a task that is defined prior to the FORK_JOIN_DYNAMIC in the workflow definition. This task will need\nto output (outputParameters) 1 and 2 above so that it can be wired into inputParameters of the FORK_JOIN_DYNAMIC\ntasks. (dynamicTasks and dynamicTasksInput)\n\n## Input Configuration\n\n\n| Attribute      | Description |\n| ----------- | ----------- |\n| name      | Task Name. A unique name that is descriptive of the task function      |\n| taskReferenceName   | Task Reference Name. A unique reference to this task. There can be multiple references of a task within the same workflow definition        |\n| type   | Task Type. In this case, `FORK_JOIN_DYNAMIC`        |\n| inputParameters   | The input parameters that will be supplied to this task.         |\n| dynamicForkTasksParam | This is a JSON array of tasks or sub-workflow objects that needs to be forked and run in parallel (Note: This has a different format for ```SUB_WORKFLOW``` compared to ```SIMPLE``` tasks.) |\n| dynamicForkTasksInputParamName | A JSON map, where the keys are task or sub-workflow names, and the values are its corresponding inputParameters | \n\n\n## Example\n\nLet's say we have a task that resizes an image, and we need to create a workflow that will resize an image into multiple sizes. In this case, a task can be created prior to\nthe `FORK_JOIN_DYNAMIC` task that will prepare the input that needs to be passed into the `FORK_JOIN_DYNAMIC` task. These will be:\n\n* ```dynamicForkTasksParam``` the JSON array of tasks/subworkflows to be run in parallel. Each JSON object will have: \n  * A unique ```taskReferenceName```.\n  * The name of the Task/Subworkflow to be called (note - the location of this key:value is different for a subworkflow).\n  * The type of the task (This is optional for SIMPLE tasks).\n* ```dynamicForkTasksInputParamName``` a JSON map of input parameters for each task. The keys will be the unique ```taskReferenceName``` defined in the first JSON array, and the values will be the specific input parameters for the task/subworkflow.\n\nThe ```image_resize``` task works to resize just one image. The `FORK_JOIN_DYNAMIC` and the following `JOIN` will manage the multiple invocations of the single ```image_resize``` task. The responsibilities are clearly broken out, where the individual  ```image_resize```\ntasks do the core job and `FORK_JOIN_DYNAMIC` manages the orchestration and fault tolerance aspects of handling multiple invocations of the task.\n\n## The workflow\n\nHere is an example of a `FORK_JOIN_DYNAMIC` task followed by a `JOIN` task.  The fork is named and given a taskReferenceName, but all of the input parameters are JSON variables that we will discuss next:\n\n```json\n{      \n  \"name\": \"image_multiple_convert_resize_fork\",\n  \"taskReferenceName\": \"image_multiple_convert_resize_fork_ref\",\n  \"inputParameters\": {\n    \"dynamicTasks\": \"${fooBarTask.output.dynamicTasksJSON}\",\n    \"dynamicTasksInput\": \"${fooBarTask.output.dynamicTasksInputJSON}\"\n  },\n  \"type\": \"FORK_JOIN_DYNAMIC\",\n  \"dynamicForkTasksParam\": \"dynamicTasks\",\n  \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\"\n},\n{\n\"name\": \"image_multiple_convert_resize_join\",\n\"taskReferenceName\": \"image_multiple_convert_resize_join_ref\",\n\"type\": \"JOIN\"\n}\n```\n\nThis appears in the UI as follows:\n\n![diagram of dynamic fork](/img/dynamic-task-diagram.png)\n\nLet's assume this data is sent to the workflow:\n\n```\n{\n\t\"fileLocation\": \"https://pbs.twimg.com/media/FJY7ud0XEAYVCS8?format=png&name=900x900\",\n\t\"outputFormats\": [\"png\",\"jpg\"],\n\t\n\t\"outputSizes\": [\n\t\t{\"width\":300,\n\t\t\"height\":300},\n\t\t{\"width\":200,\n\t\t\"height\":200}\n\t],\n\t\"maintainAspectRatio\": \"true\"\n}\n```\n\nWith 2 file formats and 2 sizes in the input, we'll be creating 4 images total.  The first task will generate the tasks and the parameters for these tasks:\n\n* `dynamicForkTasksParam` This is a JSON array of task or sub-workflow objects that specifies the list of tasks or sub-workflows that needs to be forked and run in parallel. This JSON varies depeding oon the type of task.  \n\n\n### ```dynamicForkTasksParam``` Simple task \nIn this case, our fork is running a SIMPLE task: ```image_convert_resize```:\n\n```\n{ \"dynamicTasks\": [\n  {\n    \"name\": :\"image_convert_resize\",\n    \"taskReferenceName\": \"image_convert_resize_png_300x300_0\",\n    ...\n  },\n  {\n    \"name\": :\"image_convert_resize\",\n    \"taskReferenceName\": \"image_convert_resize_png_200x200_1\",\n    ...\n  },\n  {\n    \"name\": :\"image_convert_resize\",\n    \"taskReferenceName\": \"image_convert_resize_jpg_300x300_2\",\n    ...\n  },\n  {\n    \"name\": :\"image_convert_resize\",\n    \"taskReferenceName\": \"image_convert_resize_jpg_200x200_3\",\n    ...\n  }\n]}\n```\n### ```dynamicForkTasksParam``` SubWorkflow task\nIn this case, our Dynamic fork is running a SUB_WORKFLOW task: ```image_convert_resize_subworkflow```\n\n```\n{ \"dynamicTasks\": [\n  {\n    \"subWorkflowParam\" : {\n      \"name\": :\"image_convert_resize_subworkflow\",\n      \"version\": \"1\"\n    },\n    \"type\" : \"SUB_WORKFLOW\",\n    \"taskReferenceName\": \"image_convert_resize_subworkflow_png_300x300_0\",\n    ...\n  },\n  {\n    \"subWorkflowParam\" : {\n      \"name\": :\"image_convert_resize_subworkflow\",\n      \"version\": \"1\"\n    },\n    \"type\" : \"SUB_WORKFLOW\",\n    \"taskReferenceName\": \"image_convert_resize_subworkflow_png_200x200_1\",\n    ...\n  },\n  {\n    \"subWorkflowParam\" : {\n      \"name\": :\"image_convert_resize_subworkflow\",\n      \"version\": \"1\"\n    },\n    \"type\" : \"SUB_WORKFLOW\",\n    \"taskReferenceName\": \"image_convert_resize_subworkflow_jpg_300x300_2\",\n    ...\n  },\n  {\n    \"subWorkflowParam\" : {\n      \"name\": :\"image_convert_resize_subworkflow\",\n      \"version\": \"1\"\n    },\n    \"type\" : \"SUB_WORKFLOW\",\n    \"taskReferenceName\": \"image_convert_resize_subworkflow_jpg_200x200_3\",\n    ...\n  }\n]}\n```\n\n\n\n* `dynamicForkTasksInputParamName` This is a JSON map of task or\nsub-workflow objects and all the input parameters that these tasks will need to run.\n\n```\n\"dynamicTasksInput\":{\n\"image_convert_resize_jpg_300x300_2\":{\n\"outputWidth\":300\n\"outputHeight\":300\n\"fileLocation\":\"https://pbs.twimg.com/media/FJY7ud0XEAYVCS8?format=png&name=900x900\"\n\"outputFormat\":\"jpg\"\n\"maintainAspectRatio\":true\n}\n\"image_convert_resize_jpg_200x200_3\":{\n\"outputWidth\":200\n\"outputHeight\":200\n\"fileLocation\":\"https://pbs.twimg.com/media/FJY7ud0XEAYVCS8?format=png&name=900x900\"\n\"outputFormat\":\"jpg\"\n\"maintainAspectRatio\":true\n}\n\"image_convert_resize_png_200x200_1\":{\n\"outputWidth\":200\n\"outputHeight\":200\n\"fileLocation\":\"https://pbs.twimg.com/media/FJY7ud0XEAYVCS8?format=png&name=900x900\"\n\"outputFormat\":\"png\"\n\"maintainAspectRatio\":true\n}\n\"image_convert_resize_png_300x300_0\":{\n\"outputWidth\":300\n\"outputHeight\":300\n\"fileLocation\":\"https://pbs.twimg.com/media/FJY7ud0XEAYVCS8?format=png&name=900x900\"\n\"outputFormat\":\"png\"\n\"maintainAspectRatio\":true\n}\n```\n\n### The Join\n\nThe [JOIN](/reference-docs/join-task.html) task will run after all of the dynamic tasks, collecting the output for all of the tasks."
  },
  {
    "path": "docs/docs/reference-docs/dynamic-task.md",
    "content": "# Dynamic\n```json\n\"type\" : \"DYNAMIC\"\n```\n\n### Introduction\nDynamic Task allows to execute one of the registered Tasks dynamically at run-time.\nIt accepts the task name to execute as `taskToExecute` in `inputParameters`.\n\n### Use Cases \n\nConsider a scenario, when we have to make decision of executing a task dynamically i.e. while the workflow is still\nrunning. In such cases, Dynamic Task would be useful.\n\n### Configuration\n\nDynamic task is defined directly inside the workflow with type `DYNAMIC`.\n\n#### Inputs\n\nFollowing are the input parameters :\n\n| name                 | description                                                                                                                                                               |\n|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| dynamicTaskNameParam | Name of the parameter from the task input whose value is used to schedule the task.  e.g. if the value of the parameter is ABC, the next task scheduled is of type 'ABC'. |\n\n#### Output\n\nTODO: Talk about output of the task, what to expect\n\n\n### Examples\n\nSuppose in a workflow, we have to take decision to ship the courier with the shipping\nservice providers on the basis of Post Code.\n\nFollowing task `shipping_info` generates an output on the basis of which decision would be\ntaken to run the next task.\n\n```json\n{\n  \"name\": \"shipping_info\",\n  \"retryCount\": 3,\n  \"timeoutSeconds\": 600,\n  \"pollTimeoutSeconds\": 1200,\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 300,\n  \"responseTimeoutSeconds\": 300,\n  \"concurrentExecLimit\": 100,\n  \"rateLimitFrequencyInSeconds\": 60,\n  \"ownerEmail\":\"abc@example.com\",\n  \"rateLimitPerFrequency\": 1\n}\n```\n\nFollowing are the two worker tasks, one among them would execute on the basis of output generated\nby the `shipping_info` task :\n\n```json\n{\n  \"name\": \"ship_via_fedex\",\n  \"retryCount\": 3,\n  \"timeoutSeconds\": 600,\n  \"pollTimeoutSeconds\": 1200,\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 300,\n  \"responseTimeoutSeconds\": 300,\n  \"concurrentExecLimit\": 100,\n  \"rateLimitFrequencyInSeconds\": 60,\n  \"ownerEmail\":\"abc@example.com\",\n  \"rateLimitPerFrequency\": 2\n},\n{\n  \"name\": \"ship_via_ups\",\n  \"retryCount\": 3,\n  \"timeoutSeconds\": 600,\n  \"pollTimeoutSeconds\": 1200,\n  \"timeoutPolicy\": \"TIME_OUT_WF\",\n  \"retryLogic\": \"FIXED\",\n  \"retryDelaySeconds\": 300,\n  \"responseTimeoutSeconds\": 300,\n  \"concurrentExecLimit\": 100,\n  \"rateLimitFrequencyInSeconds\": 60,\n  \"ownerEmail\":\"abc@example.com\",\n  \"rateLimitPerFrequency\": 2\n}\n```\n\n\nWe will create the Workflow with the following definition :\n\n```json\n{\n  \"name\": \"Shipping_Flow\",\n  \"description\": \"Ships smartly on the basis of Shipping info\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"shipping_info\",\n      \"taskReferenceName\": \"shipping_info\",\n      \"inputParameters\": {\n      },\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"shipping_task\",\n      \"taskReferenceName\": \"shipping_task\",\n      \"inputParameters\": {\n        \"taskToExecute\": \"${shipping_info.output.shipping_service}\"\n      },\n      \"type\": \"DYNAMIC\",\n      \"dynamicTaskNameParam\": \"taskToExecute\"\n    }\n\n  ],\n  \"restartable\": true,\n  \"ownerEmail\":\"abc@example.com\",\n  \"workflowStatusListenerEnabled\": true,\n  \"schemaVersion\": 2\n}\n```\n\nWorkflow is the created as shown in the below diagram.\n\n\n![Conductor UI - Workflow Diagram](/img/tutorial/ShippingWorkflow.png)\n\n\nNote : `shipping_task` is a `DYNAMIC` task and the `taskToExecute` parameter can be set\nwith input value provided while running the workflow or with the output of previous tasks.\nHere, it is set to the output provided by the previous task i.e.\n`${shipping_info.output.shipping_service}`.\n\nIf the input value is provided while running the workflow it can be accessed by\n`${workflow.input.shipping_service}`.\n\n```json\n{\n  \"shipping_service\": \"ship_via_fedex\"\n}\n```\n\nWe can see in the below example that on the basis of Post Code the shipping service is being\ndecided.\n\nBased on given set of inputs i.e. Post Code starts with '9' hence, `ship_via_fedex` is executed -\n\n![Conductor UI - Workflow Run](/img/tutorial/ShippingWorkflowRunning.png)\n\nIf the Post Code started with anything other than 9 `ship_via_ups` is executed -\n\n![Conductor UI - Workflow Run](/img/tutorial/ShippingWorkflowUPS.png)\n\nIf the incorrect task name or the task that doesn't exist is provided then the workflow fails and\nwe get the error `\"Invalid task specified. Cannot find task by name in the task definitions.\"`\n\nIf the null reference is provided in the task name then also the workflow fails and we get the\nerror `\"Cannot map a dynamic task based on the parameter and input. Parameter= taskToExecute, input= {taskToExecute=null}\"`\n"
  },
  {
    "path": "docs/docs/reference-docs/event-task.md",
    "content": "---\nsidebar_position: 4\n---\n\n# Event Task\n\n```json\n\"type\" : \"EVENT\"\n```\n\n### Introduction\nEVENT is a task used to publish an event into one of the supported eventing systems in Conductor.\nConductor supports the following eventing models:\n\n1. Conductor internal events (type: conductor)\n2. SQS (type: sqs)\n\n### Use Cases \nConsider a use case where at some point in the execution, an event is published to an external eventing system such as SQS.\nEvent tasks are useful for creating event based dependencies for workflows and tasks.\n\nConsider an example where we want to publish an event into SQS to notify an external system. \n\n```json\n{\n    \"type\": \"EVENT\",\n    \"sink\": \"sqs:sqs_queue_name\",\n    \"asyncComplete\": false\n}\n```\n\nAn example where we want to publish a message to conductor's internal queuing system.\n```json\n{\n    \"type\": \"EVENT\",\n    \"sink\": \"conductor:internal_event_name\",\n    \"asyncComplete\": false\n}\n```\n\n\n### Configuration\n\n#### Input Configuration\n\n| Attribute         | Description                                                                                                                                                                 |\n|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| name              | Task Name. A unique name that is descriptive of the task function                                                                                                           |\n| taskReferenceName | Task Reference Name. A unique reference to this task. There can be multiple references of a task within the same workflow definition                                        |\n| type              | Task Type. In this case, `EVENT`                                                                                                                                            |\n| sink              | External event queue in the format of `prefix:location`.  Prefix is either `sqs` or `conductor` and `location` specifies the actual queue name. e.g. \"sqs:send_email_queue\" |\n| asyncComplete     | Boolean                                                                                                                                                                     |\n\n#### asyncComplete\n* ```false``` to mark status COMPLETED upon execution \n* ```true``` to keep it IN_PROGRESS, wait for an external event (via Conductor or SQS or EventHandler) to complete it. \n\n#### Output Configuration\nTasks's output are sent as a payload to the external event. In case of SQS the task's output is sent to the SQS message a payload.\n\n\n| name               | type    | description                           |\n|--------------------|---------|---------------------------------------|\n| workflowInstanceId | String  | Workflow id                           |\n| workflowType       | String  | Workflow Name                         | \n| workflowVersion    | Integer | Workflow Version                      |\n| correlationId      | String  | Workflow CorrelationId                |\n| sink               | String  | Copy of the input data \"sink\"         |\n| asyncComplete      | Boolean | Copy of the input data \"asyncComplete |\n| event_produced     | String  | Name of the event produced            |\n\nThe published event's payload is identical to the output of the task (except \"event_produced\").\n\n\nWhen producing an event with Conductor as sink, the event name follows the structure:\n```conductor:<workflow_name>:<task_reference_name>```\n\nFor SQS, use the **name** of the queue and NOT the URI.  Conductor looks up the URI based on the name.\n\n!!!warning\n\tWhen using SQS add the [ContribsModule](https://github.com/Netflix/conductor/blob/master/contribs/src/main/java/com/netflix/conductor/contribs/ContribsModule.java) to the deployment.  The module needs to be configured with AWSCredentialsProvider for Conductor to be able to use AWS APIs.\n\n\n!!!warning\n    When using Conductor as sink, you have two options: defining the sink as `conductor` in which case the queue name will default to the taskReferenceName of the Event Task, or specifying the queue name in the sink, as `conductor:<queue_name>`. The queue name is in the `event` value of the event Handler, as `conductor:<workflow_name>:<queue_name>`.\n\n\n### Supported Queuing Systems\nConductor has support for the following external event queueing systems as part of the OSS build\n\n1. SQS (prefix: sqs)\n2. [NATS](https://github.com/Netflix/conductor/tree/main/contribs/src/main/java/com/netflix/conductor/contribs/queue/nats) (prefix: nats)\n3. [AMQP](https://github.com/Netflix/conductor/tree/main/contribs/src/main/java/com/netflix/conductor/contribs/queue/amqp) (prefix: amqp_queue or amqp_exchange)\n4. Internal Conductor (prefix: conductor) \nTo add support for other \n"
  },
  {
    "path": "docs/docs/reference-docs/fork-task.md",
    "content": "# Fork\n```json\n\"type\" : \"FORK_JOIN\"\n```\n\n## Introduction\n\nA Fork operation lets you run a specified list of tasks or sub workflows in parallel. A fork task is\nfollowed by a join operation that waits on the forked tasks or sub workflows to finish. The `JOIN`\ntask also collects outputs from each of the forked tasks or sub workflows.\n\n## Use Cases\n\n`FORK_JOIN` tasks are typically used when a list of tasks can be run in parallel. E.g In a notification workflow, there\ncould be multiple ways of sending notifications, i,e e-mail, SMS, HTTP etc.. These notifications are not dependent on\neach other, and so they can be run in parallel. In such cases, you can create 3 sub-lists of forked tasks for each of\nthese operations.\n\n## Configuration\n\nA `FORK_JOIN` task has a `forkTasks` attribute that expects an array. Each array is a sub-list of tasks. Each of these\nsub-lists are then invoked in parallel. The tasks defined within each sublist can be sequential or any other way as\ndesired.\n\nA FORK_JOIN task has to be followed by a JOIN operation. The `JOIN` operator specifies which of the forked tasks\nto `joinOn` (wait for completion)\nbefore moving to the next stage in the workflow.\n\n#### Input Configuration\n\n| Attribute         | Description                                                                                                                                   |\n|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|\n| name              | Task Name. A unique name that is descriptive of the task function                                                                             |\n| taskReferenceName | Task Reference Name. A unique reference to this task. There can be multiple references of a task within the same workflow definition          |\n| type              | Task Type. In this case, `FORK_JOIN`                                                                                                          |\n| inputParameters   | The input parameters that will be supplied to this task                                                                                       |\n| forkTasks         | A list of a list of tasks. Each of the outer list will be invoked in parallel. The inner list can be a graph of other tasks and sub-workflows |\n\n#### Output Configuration\n\nThis is the output configuration of the `JOIN` task that is used in conjunction with the `FORK_JOIN` task. The output of\nthe\n`JOIN` task is a map, where the keys are the names of the task reference names where were being `joinOn` and the keys\nare the corresponding outputs of those tasks.\n\n| Attribute       | Description                                                                         |\n|-----------------|-------------------------------------------------------------------------------------|\n| task_ref_name_1 | A task reference name that was being `joinOn`. The value is the output of that task |\n| task_ref_name_2 | A task reference name that was being `joinOn`. The value is the output of that task |\n| ...             | ...                                                                                 |\n| task_ref_name_N | A task reference name that was being `joinOn`. The value is the output of that task |\n\n\n\n### Example\n\nImagine a workflow that sends 3 notifications: email, SMS and HTTP. Since none of these steps are dependent on the others, they can be run in parallel with a fork.\n\nThe diagram will appear as:\n\n![fork diagram](/img/fork-task-diagram.png)\n\nHere's the JSON definition for the workflow:\n\n```json\n[\n  {\n    \"name\": \"fork_join\",\n    \"taskReferenceName\": \"my_fork_join_ref\",\n    \"type\": \"FORK_JOIN\",\n    \"forkTasks\": [\n      [\n        {\n          \"name\": \"process_notification_payload\",\n          \"taskReferenceName\": \"process_notification_payload_email\",\n          \"type\": \"SIMPLE\"\n        },\n        {\n          \"name\": \"email_notification\",\n          \"taskReferenceName\": \"email_notification_ref\",\n          \"type\": \"SIMPLE\"\n        }\n      ],\n      [\n        {\n          \"name\": \"process_notification_payload\",\n          \"taskReferenceName\": \"process_notification_payload_sms\",\n          \"type\": \"SIMPLE\"\n        },\n        {\n          \"name\": \"sms_notification\",\n          \"taskReferenceName\": \"sms_notification_ref\",\n          \"type\": \"SIMPLE\"\n        }\n      ],\n      [\n        {\n          \"name\": \"process_notification_payload\",\n          \"taskReferenceName\": \"process_notification_payload_http\",\n          \"type\": \"SIMPLE\"\n        },\n        {\n          \"name\": \"http_notification\",\n          \"taskReferenceName\": \"http_notification_ref\",\n          \"type\": \"SIMPLE\"\n        }\n      ]\n    ]\n  },\n  {\n    \"name\": \"notification_join\",\n    \"taskReferenceName\": \"notification_join_ref\",\n    \"type\": \"JOIN\",\n    \"joinOn\": [\n      \"email_notification_ref\",\n      \"sms_notification_ref\"\n    ]\n  }\n]\n```\n> Note: There are three parallel 'tines' to this fork, but only two of the outputs are required for the JOIN to continue. The diagram *does* draw an arrow from ```http_notification_ref``` to the ```notification_join```, but it is not required for the workflow to continue. \n\nHere is how the output of notification_join will look like. The output is a map, where the keys are the names of task\nreferences that were being `joinOn`. The corresponding values are the outputs of those tasks.\n\n```json\n\n{\n  \"email_notification_ref\": {\n    \"email_sent_at\": \"2021-11-06T07:37:17+0000\",\n    \"email_sent_to\": \"test@example.com\"\n  },\n  \"sms_notification_ref\": {\n    \"smm_sent_at\": \"2021-11-06T07:37:17+0129\",\n    \"sms_sen\": \"+1-425-555-0189\"\n  }\n}\n```\n\nSee [JOIN](/reference-docs/join-task.html) for more details on the JOIN aspect of the FORK.\n"
  },
  {
    "path": "docs/docs/reference-docs/http-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# HTTP Task\n\n```json\n\"type\" : \"HTTP\"\n```\n\n### Introduction\n\nAn HTTP task is useful when you have a requirements such as:\n\n1. Making calls to another service that exposes an API via HTTP\n2. Fetch any resource or data present on an endpoint\n\n### Use Cases\n\nIf we have a scenario where we need to make an HTTP call into another service, we can make use of HTTP tasks. You can\nuse the data returned from the HTTP call in your subsequent tasks as inputs. Using HTTP tasks you can avoid having to\nwrite the code that talks to these services and instead let Conductor manage it directly. This can reduce the code you\nhave to maintain and allows for a lot of flexibility.\n\n### Configuration\n\nHTTP task is defined directly inside the workflow with the task type `HTTP`.\n\n| name         | type        | description             |\n|--------------|-------------|-------------------------|\n| http_request | HttpRequest | JSON object (see below) |\n\n#### Inputs\n\n| Name                | Type             | Description                                                                                                                                                                |\n|---------------------|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| uri                 | String           | URI for the service. Can be a partial when using vipAddress or includes the server address.                                                                                |\n| method              | String           | HTTP method. GET, PUT, POST, DELETE, OPTIONS, HEAD                                                                                                                         |\n| accept              | String           | Accept header. Default:  ```application/json```                                                                                                                            |\n| contentType         | String           | Content Type - supported types are ```text/plain```, ```text/html```, and ```application/json``` (Default)                                                                 |\n| headers             | Map[String, Any] | A map of additional http headers to be sent along with the request.                                                                                                        |\n| body                | Map[]            | Request body                                                                                                                                                               |\n| vipAddress          | String           | When using discovery based service URLs.                                                                                                                                   |\n| asyncComplete       | Boolean          | ```false``` to mark status COMPLETED upon execution ; ```true``` to keep it IN_PROGRESS, wait for an external event (via Conductor or SQS or EventHandler) to complete it. |\n| oauthConsumerKey    | String           | [OAuth](https://oauth.net/core/1.0/) client consumer key                                                                                                                   |\n| oauthConsumerSecret | String           | [OAuth](https://oauth.net/core/1.0/) client consumer secret                                                                                                                |\n| connectionTimeOut   | Integer          | Connection Time Out in milliseconds. If set to 0, equivalent to infinity. Default: 100.                                                                                    |\n| readTimeOut         | Integer          | Read Time Out in milliseconds. If set to 0, equivalent to infinity. Default: 150.                                                                                          |\n\n#### Output\n\n| name         | type             | description                                                                 |\n|--------------|------------------|-----------------------------------------------------------------------------|\n| response     | Map              | JSON body containing the response if one is present                         |\n| headers      | Map[String, Any] | Response Headers                                                            |\n| statusCode   | Integer          | [Http Status Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) |\n| reasonPhrase | String           | Http Status Code's reason phrase                                            |\n\n### Examples\n\nFollowing is the example of HTTP task with `GET` method.\n\nWe can use variables in our URI as show in the example below. \n\n```json\n{\n  \"name\": \"Get Example\",\n  \"taskReferenceName\": \"get_example\",\n  \"inputParameters\": {\n    \"http_request\": {\n      \"uri\": \"https://jsonplaceholder.typicode.com/posts/${workflow.input.queryid}\",\n      \"method\": \"GET\"\n    }\n  },\n  \"type\": \"HTTP\"\n}\n```\n\nFollowing is the example of HTTP task with `POST` method.\n\n> Here we are using variables for our POST body which happens to be data from a previous task. This is an example of how you can **chain** HTTP calls to make complex flows happen without writing any additional code.\n\n```json\n{\n  \"name\": \"http_post_example\",\n  \"taskReferenceName\": \"post_example\",\n  \"inputParameters\": {\n    \"http_request\": {\n      \"uri\": \"https://jsonplaceholder.typicode.com/posts/\",\n      \"method\": \"POST\",\n      \"body\": {\n        \"title\": \"${get_example.output.response.body.title}\",\n        \"userId\": \"${get_example.output.response.body.userId}\",\n        \"action\": \"doSomething\"\n      }\n    }\n  },\n  \"type\": \"HTTP\"\n}\n```\n\nFollowing is the example of HTTP task with `PUT` method.\n\n```json\n{\n  \"name\": \"http_put_example\",\n  \"taskReferenceName\": \"put_example\",\n  \"inputParameters\": {\n    \"http_request\": {\n      \"uri\": \"https://jsonplaceholder.typicode.com/posts/1\",\n      \"method\": \"PUT\",\n      \"body\": {\n        \"title\": \"${get_example.output.response.body.title}\",\n        \"userId\": \"${get_example.output.response.body.userId}\",\n        \"action\": \"doSomethingDifferent\"\n      }\n    }\n  },\n  \"type\": \"HTTP\"\n}\n```\n\nFollowing is the example of HTTP task with `DELETE` method.\n\n```json\n{\n  \"name\": \"DELETE Example\",\n  \"taskReferenceName\": \"delete_example\",\n  \"inputParameters\": {\n    \"http_request\": {\n      \"uri\": \"https://jsonplaceholder.typicode.com/posts/1\",\n      \"method\": \"DELETE\"\n    }\n  },\n  \"type\": \"HTTP\"\n}\n```\n\n### Best Practices\n\n1. Why are my HTTP tasks not getting picked up?\n    1. We might have too many HTTP tasks in the queue. There is a concept called Isolation Groups that you can rely on\n       for prioritizing certain HTTP tasks over others. Read more here: [Isolation Groups](/configuration/isolationgroups.html)\n   \n"
  },
  {
    "path": "docs/docs/reference-docs/human-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Human\n```json\n\"type\" : \"HUMAN\"\n```\n### Introduction\n\nHUMAN is used when the workflow needs to be paused for an external signal by a human to continue.\n\n### Use Cases\nHUMAN is used when the workflow needs to wait and pause for  human intervention \n(like manual approval) or an event coming from external source such as Kafka, SQS or Conductor's internal queueing mechanism.\n\nSome use cases where HUMAN task is used:\n1. To add a human approval task.  When the task is approved/rejected by HUMAN task is updated using `POST /tasks` API to completion.\n\n\n### Configuration\n* taskType: HUMAN\n* There are no other configurations required\n\n\n"
  },
  {
    "path": "docs/docs/reference-docs/inline-task.md",
    "content": "---\nsidebar_position: 11\n---\n\n# Inline Task\n\n```json\n\"type\": \"INLINE\"\n```\n### Introduction\n\nInline Task helps execute necessary logic at Workflow run-time,\nusing an evaluator. There are two supported evaluators as of now:\n\n### Configuration\n| name        | description                                       |\n|-------------|---------------------------------------------------|\n| value-param | Use a parameter directly as the value             |\n| javascript  | Evaluate Javascript expressions and compute value |\n\n\n### Use Cases\n\nConsider a scenario, we have to run simple evaluations in\nConductor server while creating Workers. Inline task can be used to run these\nevaluations using an evaluator engine.\n\n### Example 1\n\n```json\n{\n  \"name\": \"inline_task_example\",\n  \"taskReferenceName\": \"inline_task_example\",\n  \"type\": \"INLINE\",\n  \"inputParameters\": {\n      \"value\": \"${workflow.input.value}\",\n      \"evaluatorType\": \"javascript\",\n      \"expression\": \"function e() { if ($.value == 1){return {\\\"result\\\": true}} else { return {\\\"result\\\": false}}} e();\"\n  }\n}\n```\n\nFollowing are the parameters in the above example :\n\n1. `\"evaluatorType\"` - Type of the evaluator. \nSupported evaluators: value-param, javascript which evaluates \njavascript expression.\t\n\n2. `\"expression\"` - Expression associated with the type of evaluator. \nFor javascript evaluator, Javascript evaluation engine is used to \nevaluate expression defined as a string. Must return a value.\t\n\nBesides expression, any of the properties in the input values is accessible as `$.value` for the expression\nto evaluate. \n\nThe task output can then be referenced in downstream tasks \nlike: `\"${inline_test.output.result}\"`\n\n### Example 2 \n\nPerhaps a weather API sometimes returns Celcius, and sometimes returns Fahrenheit temperature values.  This task ensures that the downstream tasks ONLY receive Celcius values:\n\n```\n{\n  \"name\": \"INLINE_TASK\",\n  \"taskReferenceName\": \"inline_test\",\n  \"type\": \"INLINE\",\n  \"inputParameters\": {\n      \"scale\": \"${workflow.input.tempScale}\",\n\t  \"temperature\": \"${workflow.input.temperature}\",\n      \"evaluatorType\": \"javascript\",\n      \"expression\": \"function SIvaluesOnly(){if ($.scale === \"F\"){ centigrade = ($.temperature -32)*5/9; return {temperature: centigrade} } else { return \n      {temperature: $.temperature} }} SIvaluesOnly();\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/docs/reference-docs/join-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Join\n```json\n\"type\" : \"JOIN\"\n```\n\n### Introduction\n\nA `JOIN` task is used in conjunction with a `FORK_JOIN` or `FORK_JOIN_DYNAMIC` task. When `JOIN` is used along with\na `FORK_JOIN` task, tt waits for a list of zero or more of the forked tasks to be completed. However, when used with\na `FORK_JOIN_DYNAMIC` task, it implicitly waits for all of the dynamically forked tasks to complete.\n\n### Use Cases\n\n[FORK_JOIN](/reference-docs/fork-task.html) and [FORK_JOIN_DYNAMIC](/reference-docs/dynamic-fork-task.html) task are used to execute a collection of other tasks or sub workflows in parallel. In\nsuch cases, there is a need for these forked tasks to complete before moving to the next stage in the workflow. \n\n### Configuration\n\n#### Input Configuration\n\n| Attribute         | Description                                                                                                                          |\n|-------------------|--------------------------------------------------------------------------------------------------------------------------------------|\n| name              | Task Name. A unique name that is descriptive of the task function                                                                    |\n| taskReferenceName | Task Reference Name. A unique reference to this task. There can be multiple references of a task within the same workflow definition |\n| type              | Task Type. In this case, `JOIN`                                                                                                      |\n| joinOn            | A list of task reference names, that this `JOIN` task will wait for completion                                                       |\n\n#### Output Configuration\n\n| Attribute       | Description                                                                         |\n|-----------------|-------------------------------------------------------------------------------------|\n| task_ref_name_1 | A task reference name that was being `joinOn`. The value is the output of that task |\n| task_ref_name_2 | A task reference name that was being `joinOn`. The value is the output of that task |\n| ...             | ...                                                                                 |\n| task_ref_name_N | A task reference name that was being `joinOn`. The value is the output of that task |\n\n\n\n### Examples\n\n#### Simple Example\nHere is an example of a _`JOIN`_ task. This task will wait for the completion of tasks `my_task_ref_1`\nand `my_task_ref_2` as specified by the `joinOn` attribute.\n\n```json\n{\n  \"name\": \"join_task\",\n  \"taskReferenceName\": \"my_join_task_ref\",\n  \"type\": \"JOIN\",\n  \"joinOn\": [\n    \"my_task_ref_1\",\n    \"my_task_ref_2\"\n  ]\n}\n```\n\n\n#### Example - ignoring one fork\nHere is an example of a `JOIN` task used in conjunction with a `FORK_JOIN` task. The 'FORK_JOIN' spawns 3 tasks.\nAn `email_notification` task, a `sms_notification` task, and a `http_notification` task. Email and SMS are usually best\neffort delivery systems. However, in case of a http based notification you get a return code and you can retry until it\nsucceeds or eventually give up. When you setup a notification workflow, you may decide to continue, if you kicked off an\nemail and sms notification. In that case, you can decide to `joinOn` those specific tasks. However,\nthe `http_notification` task will still continue to execute, but it will not block the rest of the workflow from\nproceeding.\n\n```json\n[\n  {\n    \"name\": \"fork_join\",\n    \"taskReferenceName\": \"my_fork_join_ref\",\n    \"type\": \"FORK_JOIN\",\n    \"forkTasks\": [\n      [\n        {\n          \"name\": \"email_notification\",\n          \"taskReferenceName\": \"email_notification_ref\",\n          \"type\": \"SIMPLE\"\n        }\n      ],\n      [\n        {\n          \"name\": \"sms_notification\",\n          \"taskReferenceName\": \"sms_notification_ref\",\n          \"type\": \"SIMPLE\"\n        }\n      ],\n      [\n        {\n          \"name\": \"http_notification\",\n          \"taskReferenceName\": \"http_notification_ref\",\n          \"type\": \"SIMPLE\"\n        }\n      ]\n    ]\n  },\n  {\n    \"name\": \"notification_join\",\n    \"taskReferenceName\": \"notification_join_ref\",\n    \"type\": \"JOIN\",\n    \"joinOn\": [\n      \"email_notification_ref\",\n      \"sms_notification_ref\"\n    ]\n  }\n]\n```\n\nHere is how the output of notification_join will look like. The output is a map, where the keys are the names of task\nreferences that were being `joinOn`. The corresponding values are the outputs of those tasks.\n\n```json\n\n{\n  \"email_notification_ref\": {\n    \"email_sent_at\": \"2021-11-06T07:37:17+0000\",\n    \"email_sent_to\": \"test@example.com\"\n  },\n  \"sms_notification_ref\": {\n    \"smm_sent_at\": \"2021-11-06T07:37:17+0129\",\n    \"sms_sen\": \"+1-425-555-0189\"\n  }\n}\n\n```\n\n"
  },
  {
    "path": "docs/docs/reference-docs/json-jq-transform-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# JSON JQ Transform Task\n\n```json\n\"type\" : \"JSON_JQ_TRANSFORM\"\n```\n### Introduction\n\nJSON_JQ_TRANSFORM_TASK is a System task that allows processing of JSON data that is supplied to the task, by using the\npopular JQ processing tool’s query expression language.\n\nCheck the [JQ Manual](https://stedolan.github.io/jq/manual/v1.5/), and the\n[JQ Playground](https://jqplay.org/) for more information on JQ\n\n### Use Cases\n\nJSON is a popular format of choice for data-interchange. It is widely used in web and server applications, document\nstorage, API I/O etc. It’s also used within Conductor to define workflow and task definitions and passing data and state\nbetween tasks and workflows. This makes a tool like JQ a natural fit for processing task related data. Some common\nusages within Conductor includes, working with HTTP task, JOIN tasks or standalone tasks that try to transform data from\nthe output of one task to the input of another.\n\n### Configuration\n\n\n| Attribute                           | Description                                                                                                                                                                                                                                                             |\n|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| name                                | Task Name. A unique name that is descriptive of the task function                                                                                                                                                                                                       |\n| taskReferenceName                   | Task Reference Name. A unique reference to this task. There can be multiple references of a task within the same workflow definition                                                                                                                                    |\n| type                                | Task Type. In this case, JSON_JQ_TRANSFORM                                                                                                                                                                                                                              |\n| inputParameters                     | The input parameters that will be supplied to this task. The parameters will be a JSON object of atleast 2 attributes, one of which will be called queryExpression. The others are user named attributes. These attributes will be accessible by the JQ query processor |\n| inputParameters/user-defined-key(s) | User defined key(s) along with values.                                                                                                                                                                                                                                  |\n| inputParameters/queryExpression     | A JQ query expression                                                                                                                                                                                                                                                   |\n\n#### Output Configuration\n\n| Attribute  | Description                                                               |\n|------------|---------------------------------------------------------------------------|\n| result     | The first results returned by the JQ expression                           |\n| resultList | A List of results returned by the JQ expression                           |\n| error      | An optional error message, indicating that the JQ query failed processing |\n\n### Example\n\n\nHere is an example of a _`JSON_JQ_TRANSFORM`_ task. The `inputParameters` attribute is expected to have a value object\nthat has the following\n\n1. A list of key value pair objects denoted key1/value1, key2/value2 in the example below. Note the key1/value1 are\n   arbitrary names used in this example.\n\n2. A key with the name `queryExpression`, whose value is a JQ expression. The expression will operate on the value of\n   the `inputParameters` attribute. In the example below, the `inputParameters` has 2 inner objects named by attributes\n   `key1` and `key2`, each of which has an object that is named `value1` and `value2`. They have an associated array of\n   strings as values, `\"a\", \"b\"` and `\"c\", \"d\"`. The expression `key3: (.key1.value1 + .key2.value2)` concat's the 2\n   string arrays into a single array against an attribute named `key3`\n\n```json\n{\n  \"name\": \"jq_example_task\",\n  \"taskReferenceName\": \"my_jq_example_task\",\n  \"type\": \"JSON_JQ_TRANSFORM\",\n  \"inputParameters\": {\n    \"key1\": {\n      \"value1\": [\n        \"a\",\n        \"b\"\n      ]\n    },\n    \"key2\": {\n      \"value2\": [\n        \"c\",\n        \"d\"\n      ]\n    },\n    \"queryExpression\": \"{ key3: (.key1.value1 + .key2.value2) }\"\n  }\n}\n```\n\nThe execution of this example task above will provide the following output. The `resultList` attribute stores the full\nlist of the `queryExpression` result. The `result` attribute stores the first element of the resultList. An\noptional `error`\nattribute along with a string message will be returned if there was an error processing the query expression.\n\n```json\n{\n  \"result\": {\n    \"key3\": [\n      \"a\",\n      \"b\",\n      \"c\",\n      \"d\"\n    ]\n  },\n  \"resultList\": [\n    {\n      \"key3\": [\n        \"a\",\n        \"b\",\n        \"c\",\n        \"d\"\n      ]\n    }\n  ]\n}\n```\n\n## Example JQ transforms\n\n### Cleaning up a JSON response\n\nA HTTP Task makes an API call to GitHub to request a list of \"stargazers\" (users who have starred a repository).  The API response (for just one user) looks like:\n\n\nSnippet of ```${hundred_stargazers_ref.output}```\n\n``` JSON \n  \n\"body\":[\n  {\n  \"starred_at\":\"2016-12-14T19:55:46Z\",\n  \"user\":{\n    \"login\":\"lzehrung\",\n    \"id\":924226,\n    \"node_id\":\"MDQ6VXNlcjkyNDIyNg==\",\n    \"avatar_url\":\"https://avatars.githubusercontent.com/u/924226?v=4\",\n    \"gravatar_id\":\"\",\n    \"url\":\"https://api.github.com/users/lzehrung\",\n    \"html_url\":\"https://github.com/lzehrung\",\n    \"followers_url\":\"https://api.github.com/users/lzehrung/followers\",\n    \"following_url\":\"https://api.github.com/users/lzehrung/following{/other_user}\",\n    \"gists_url\":\"https://api.github.com/users/lzehrung/gists{/gist_id}\",\n    \"starred_url\":\"https://api.github.com/users/lzehrung/starred{/owner}{/repo}\",\n    \"subscriptions_url\":\"https://api.github.com/users/lzehrung/subscriptions\",\n    \"organizations_url\":\"https://api.github.com/users/lzehrung/orgs\",\n    \"repos_url\":\"https://api.github.com/users/lzehrung/repos\",\n    \"events_url\":\"https://api.github.com/users/lzehrung/events{/privacy}\",\n    \"received_events_url\":\"https://api.github.com/users/lzehrung/received_events\",\n    \"type\":\"User\",\n    \"site_admin\":false\n  }\n}\n]\n\n```\n\nWe only need the ```starred_at``` and ```login``` parameters for users who starred the repository AFTER a given date (provided as an input to the workflow ```${workflow.input.cutoff_date}```).  We'll use the JQ Transform to simplify the output:\n\n```JSON\n{\n          \"name\": \"jq_cleanup_stars\",\n          \"taskReferenceName\": \"jq_cleanup_stars_ref\",\n          \"inputParameters\": {\n            \"starlist\": \"${hundred_stargazers_ref.output.response.body}\",\n            \"queryExpression\": \"[.starlist[] | select (.starred_at > \\\"${workflow.input.cutoff_date}\\\") |{occurred_at:.starred_at, member: {github:  .user.login}}]\"\n          },\n          \"type\": \"JSON_JQ_TRANSFORM\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n```\n\nThe JSON is stored in ```starlist```.  The ```queryExpression``` reads in the JSON, selects only entries where the ```starred_at``` value meets the date criteria, and generates output JSON of the form:\n\n```JSON\n{\n  \"occurred_at\": \"date from JSON\",\n  \"member\":{\n    \"github\" : \"github Login from JSON\"\n  }\n}\n```\n\nThe entire expression is wrapped in [] to indicate that the response should be an array.\n\n\n\n\n\n"
  },
  {
    "path": "docs/docs/reference-docs/kafka-publish-task.md",
    "content": "---\nsidebar_position: 13\n---\n\n# Kafka Publish Task\n```json\n\"type\" : \"KAFKA_PUBLISH\"\n```\n\n### Introduction\n\nA Kafka Publish task is used to push messages to another microservice via Kafka.\n\n### Configuration\nThe task expects an input parameter named ```kafka_request``` as part of the task's input with the following details:\n\n| name             | description                                                                                                                                                                                                                                                                                                                  |\n|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| bootStrapServers | bootStrapServers for connecting to given kafka.                                                                                                                                                                                                                                                                              |\n| key              | Key to be published                                                                                                                                                                                                                                                                                                          |\n| keySerializer    | Serializer used for serializing the key published to kafka.  One of the following can be set : <br/> 1. org.apache.kafka.common.serialization.IntegerSerializer<br/>2. org.apache.kafka.common.serialization.LongSerializer<br/>3. org.apache.kafka.common.serialization.StringSerializer. <br/>Default is String serializer |\n| value            | Value published to kafka                                                                                                                                                                                                                                                                                                     |\n| requestTimeoutMs | Request timeout while publishing to kafka. If this value is not given the value is read from the property `kafka.publish.request.timeout.ms`. If the property is not set the value defaults to 100 ms                                                                                                                        |\n| maxBlockMs       | maxBlockMs while publishing to kafka. If this value is not given the value is read from the property `kafka.publish.max.block.ms`. If the property is not set the value defaults to 500 ms                                                                                                                                   |\n| headers          | A map of additional kafka headers to be sent along with the request.                                                                                                                                                                                                                                                         |\n| topic            | Topic to publish                                                                                                                                                                                                                                                                                                             |\n\n### Examples\n\nSample Task \n\n\n```json\n{\n  \"name\": \"call_kafka\",\n  \"taskReferenceName\": \"call_kafka\",\n  \"inputParameters\": {\n    \"kafka_request\": {\n      \"topic\": \"userTopic\",\n      \"value\": \"Message to publish\",\n      \"bootStrapServers\": \"localhost:9092\",\n      \"headers\": {\n    \"x-Auth\":\"Auth-key\"    \n      },\n      \"key\": \"123\",\n      \"keySerializer\": \"org.apache.kafka.common.serialization.IntegerSerializer\"\n    }\n  },\n  \"type\": \"KAFKA_PUBLISH\"\n}\n```\n\nThe task expects an input parameter named `\"kafka_request\"` as part\nof the task's input with the following details:\n\n1. `\"bootStrapServers\"` - bootStrapServers for connecting to given kafka.\n2. `\"key\"` - Key to be published.\n3. `\"keySerializer\"` - Serializer used for serializing the key published to kafka. \nOne of the following can be set :\na. org.apache.kafka.common.serialization.IntegerSerializer\nb. org.apache.kafka.common.serialization.LongSerializer\nc. org.apache.kafka.common.serialization.StringSerializer.\nDefault is String serializer.\n4. `\"value\"` - Value published to kafka\n5. `\"requestTimeoutMs\"` - Request timeout while publishing to kafka. \nIf this value is not given the value is read from the property \nkafka.publish.request.timeout.ms. If the property is not set the value\ndefaults to 100 ms.\n6. `\"maxBlockMs\"` - maxBlockMs while publishing to kafka. If this value is\nnot given the value is read from the property kafka.publish.max.block.ms.\nIf the property is not set the value defaults to 500 ms.\n7. `\"headers\"` - A map of additional kafka headers to be sent along with\nthe request.\n8. `\"topic\"` - Topic to publish.\n\nThe producer created in the kafka task is cached. By default\nthe cache size is 10 and expiry time is 120000 ms. To change the\ndefaults following can be modified \nkafka.publish.producer.cache.size,\nkafka.publish.producer.cache.time.ms respectively.\n\n#### Kafka Task Output\n\nTask status transitions to `COMPLETED`.\n\nThe task is marked as `FAILED` if the message could not be published to\nthe Kafka queue.\n"
  },
  {
    "path": "docs/docs/reference-docs/redis.md",
    "content": "# Redis\n\nBy default conductor runs with an in-memory Redis mock. However, you\ncan change the configuration by setting the properties `conductor.db.type` and `conductor.redis.hosts`.\n\n## `conductor.db.type`\n\n| Value                          | Description                                                                            |\n|--------------------------------|----------------------------------------------------------------------------------------|\n| dynomite                       | Dynomite Cluster. Dynomite is a proxy layer that provides sharding and replication.    |\n| memory                         | Uses an in-memory Redis mock. Should be used only for development and testing purposes.|\n| redis_cluster                  | Redis Cluster configuration.                                                           |\n| redis_sentinel                 | Redis Sentinel configuration.                                                          |\n| redis_standalone               | Redis Standalone configuration.                                                        |\n\n\n\n## `conductor.redis.hosts`\n\nExpected format is `host:port:rack` separated by semicolon, e.g.: \n\n```properties\nconductor.redis.hosts=host0:6379:us-east-1c;host1:6379:us-east-1c;host2:6379:us-east-1c\n```\n\n### Auth Support\n\nPassword authentication is supported. The password should be set as the 4th param of the first host `host:port:rack:password`, e.g.:\n\n```properties\nconductor.redis.hosts=host0:6379:us-east-1c:my_str0ng_pazz;host1:6379:us-east-1c;host2:6379:us-east-1c\n```\n\n\n**Notes**\n\n- In a cluster, all nodes use the same password.\n- In a sentinel configuration, sentinels and redis nodes use the same password.\n"
  },
  {
    "path": "docs/docs/reference-docs/set-variable-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Set Variable\n\n```json\n\"type\" : \"SET_VARIABLE\"\n```\n### Introduction\nSet Variable allows us to set workflow variables by creating or updating them\nwith new values.\n\n### Use Cases\n\nVariables can be initialized in the workflow definition as well as during\nthe workflow run. Once a variable was initialized it can be read or\noverwritten with a new value by any other task.\n\n### Configuration\n\nSet Variable task is defined directly inside the workflow with type\n`SET_VARIABLE`.\n\n## Examples\n\nSuppose in a workflow, we have to store a value in a variable and then later in\nworkflow reuse the value stored in the variable just as we do in programming, in such\nscenarios `Set Variable` task can be used.\n\nFollowing is the workflow definition with `SET_VARIABLE` task.\n\n```json\n{\n  \"name\": \"Set_Variable_Workflow\",\n  \"description\": \"Set a value to a variable and then reuse it later in the workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"Set_Name\",\n      \"taskReferenceName\": \"Set_Name\",\n      \"type\": \"SET_VARIABLE\",\n      \"inputParameters\": {\n        \"name\": \"Foo\"\n      }\n    },\n    {\n      \"name\": \"Read_Name\",\n      \"taskReferenceName\": \"Read_Name\",\n      \"inputParameters\": {\n        \"var_name\" : \"${workflow.variables.name}\"\n      },\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"restartable\": true,\n  \"ownerEmail\":\"abc@example.com\",\n  \"workflowStatusListenerEnabled\": true,\n  \"schemaVersion\": 2\n}\n```\n\nIn the above example, it can be seen that the task `Set_Name` is a Set Variable Task and\nthe variable `name` is set to `Foo` and later in the workflow it is referenced by\n`\"${workflow.variables.name}\"` in another task.\n"
  },
  {
    "path": "docs/docs/reference-docs/start-workflow-task.md",
    "content": "---\nsidebar_position: 1\n---\n# Start Workflow\n```json\n\"type\" : \"START_WORKFLOW\"\n```\n### Introduction\n\nStart Workflow starts another workflow. Unlike `SUB_WORKFLOW`, `START_WORKFLOW` does\nnot create a relationship between starter and the started workflow. It also does not wait for the started workflow to complete. A `START_WORKFLOW` is \nconsidered successful once the requested workflow is started successfully. In other words, `START_WORKFLOW` is marked as COMPLETED, once the started \nworkflow is in RUNNING state.\n\n### Use Cases\n\nWhen another workflow needs to be started from a workflow, `START_WORKFLOW` can be used. \n\n### Configuration\n\nStart Workflow task is defined directly inside the workflow with type `START_WORKFLOW`.\n\n#### Input\n\n**Parameters:**\n\n| name          | type             | description                                                                                                         |\n|---------------|------------------|---------------------------------------------------------------------------------------------------------------------|\n| startWorkflow | Map[String, Any] | The value of this parameter is [Start Workflow Request](/gettingstarted/startworkflow.html#start-workflow-request). |\n\n#### Output\n\n| name       | type   | description                    |\n|------------|--------|--------------------------------|\n| workflowId | String | The id of the started workflow |\n"
  },
  {
    "path": "docs/docs/reference-docs/sub-workflow-task.md",
    "content": "---\nsidebar_position: 1\n---\n# Sub Workflow\n```json\n\"type\" : \"SUB_WORKFLOW\"\n```\n### Introduction\nSub Workflow task allows for nesting a workflow within another workflow. Nested workflows contain a reference to their parent.\n\n### Use Cases\n\nSuppose we want to include another workflow inside our current workflow. In that\ncase, Sub Workflow Task would be used.\n\n### Configuration\n\nSub Workflow task is defined directly inside the workflow with type `SUB_WORKFLOW`.\n\n#### Input\n\n**Parameters:**\n\n| name             | type             | description |\n|------------------|------------------|-------------|\n| subWorkflowParam | Map[String, Any] | See below   |\n\n**subWorkflowParam**\n\n| name               | type                                                  | description                                                                                                                                                         |\n|--------------------|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| name               | String                                                | Name of the workflow to execute                                                                                                                                     |\n| version            | Integer                                               | Version of the workflow to execute                                                                                                                                  |\n| taskToDomain       | Map[String, String]                                   | Allows scheduling the sub workflow's tasks per given mappings. <br/> See [Task Domains](/configuration/taskdomains.html) for instructions to configure taskDomains. |\n| workflowDefinition | [WorkflowDefinition](/configuration/workflowdef.html) | Allows starting a subworkflow with a dynamic workflow definition.                                                                                                   |\n\n#### Output\n\n| name          | type   | description                                                       |\n|---------------|--------|-------------------------------------------------------------------|\n| subWorkflowId | String | Sub-workflow execution Id generated when running the sub-workflow |\n\n\n### Examples\n\n\nImagine we have a workflow that has a fork in it. In the example below, we input one image, but using a fork to create 2 images simultaneously:\n\n\n![workflow with fork](/img/workflow_fork.png)\n\nThe left fork will create a JPG, and the right fork a WEBP image. Maintaining this workflow might be difficult, as changes made to one side of the fork do not automatically propagate the other.  Rather than using 2 tasks, we can define a ```image_convert_resize``` workflow that we can call for both forks as a sub-workflow:\n\n\n```json\n\n{{\n\t\"name\": \"image_convert_resize_subworkflow1\",\n\t\"description\": \"Image Processing Workflow\",\n\t\"version\": 1,\n\t\"tasks\": [{\n\t\t\t\"name\": \"image_convert_resize_multipleformat_fork\",\n\t\t\t\"taskReferenceName\": \"image_convert_resize_multipleformat_ref\",\n\t\t\t\"inputParameters\": {},\n\t\t\t\"type\": \"FORK_JOIN\",\n\t\t\t\"decisionCases\": {},\n\t\t\t\"defaultCase\": [],\n\t\t\t\"forkTasks\": [\n\t\t\t\t[{\n\t\t\t\t\t\"name\": \"image_convert_resize_sub\",\n\t\t\t\t\t\"taskReferenceName\": \"subworkflow_jpg_ref\",\n\t\t\t\t\t\"inputParameters\": {\n\t\t\t\t\t\t\"fileLocation\": \"${workflow.input.fileLocation}\",\n\t\t\t\t\t\t\"recipeParameters\": {\n\t\t\t\t\t\t\t\"outputSize\": {\n\t\t\t\t\t\t\t\t\"width\": \"${workflow.input.recipeParameters.outputSize.width}\",\n\t\t\t\t\t\t\t\t\"height\": \"${workflow.input.recipeParameters.outputSize.height}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"outputFormat\": \"jpg\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"type\": \"SUB_WORKFLOW\",\n\t\t\t\t\t\"subWorkflowParam\": {\n\t\t\t\t\t\t\"name\": \"image_convert_resize\",\n\t\t\t\t\t\t\"version\": 1\n\t\t\t\t\t}\n\t\t\t\t}],\n\t\t\t\t[{\n\t\t\t\t\t\t\"name\": \"image_convert_resize_sub\",\n\t\t\t\t\t\t\"taskReferenceName\": \"subworkflow_webp_ref\",\n\t\t\t\t\t\t\"inputParameters\": {\n\t\t\t\t\t\t\t\"fileLocation\": \"${workflow.input.fileLocation}\",\n\t\t\t\t\t\t\t\"recipeParameters\": {\n\t\t\t\t\t\t\t\t\"outputSize\": {\n\t\t\t\t\t\t\t\t\t\"width\": \"${workflow.input.recipeParameters.outputSize.width}\",\n\t\t\t\t\t\t\t\t\t\"height\": \"${workflow.input.recipeParameters.outputSize.height}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"outputFormat\": \"webp\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"type\": \"SUB_WORKFLOW\",\n\t\t\t\t\t\t\"subWorkflowParam\": {\n\t\t\t\t\t\t\t\"name\": \"image_convert_resize\",\n\t\t\t\t\t\t\t\"version\": 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"name\": \"image_convert_resize_multipleformat_join\",\n\t\t\t\"taskReferenceName\": \"image_convert_resize_multipleformat_join_ref\",\n\t\t\t\"inputParameters\": {},\n\t\t\t\"type\": \"JOIN\",\n\t\t\t\"decisionCases\": {},\n\t\t\t\"defaultCase\": [],\n\t\t\t\"forkTasks\": [],\n\t\t\t\"startDelay\": 0,\n\t\t\t\"joinOn\": [\n\t\t\t\t\"subworkflow_jpg_ref\",\n\t\t\t\t\"upload_toS3_webp_ref\"\n\t\t\t],\n\t\t\t\"optional\": false,\n\t\t\t\"defaultExclusiveJoinTask\": [],\n\t\t\t\"asyncComplete\": false,\n\t\t\t\"loopOver\": []\n\t\t}\n\t],\n\t\"inputParameters\": [],\n\t\"outputParameters\": {\n\t\t\"fileLocationJpg\": \"${subworkflow_jpg_ref.output.fileLocation}\",\n\t\t\"fileLocationWebp\": \"${subworkflow_webp_ref.output.fileLocation}\"\n\t},\n\t\"schemaVersion\": 2,\n\t\"restartable\": true,\n\t\"workflowStatusListenerEnabled\": true,\n\t\"ownerEmail\": \"conductor@example.com\",\n\t\"timeoutPolicy\": \"ALERT_ONLY\",\n\t\"timeoutSeconds\": 0,\n\t\"variables\": {},\n\t\"inputTemplate\": {}\n}\n```\n\nNow our diagram will appear as:\n![workflow with 2 subworkflows](/img/subworkflow_diagram.png)\n\n\n\nThe inputs to both sides of the workflow are identical before and after - but we've abstracted the tasks into the sub-workflow. Any change to the sub-workflow will automatically occur in bth sides of the fork.\n\n\nLooking at the subworkflow (the WEBP version):\n\n```\n{\n                        \"name\": \"image_convert_resize_sub\",\n                        \"taskReferenceName\": \"subworkflow_webp_ref\",\n                        \"inputParameters\": {\n                            \"fileLocation\": \"${workflow.input.fileLocation}\",\n                            \"recipeParameters\": {\n                                \"outputSize\": {\n                                    \"width\": \"${workflow.input.recipeParameters.outputSize.width}\",\n                                    \"height\": \"${workflow.input.recipeParameters.outputSize.height}\"\n                                },\n                                \"outputFormat\": \"webp\"\n                            }\n                        },\n                        \"type\": \"SUB_WORKFLOW\",\n                        \"subWorkflowParam\": {\n                            \"name\": \"image_convert_resize\",\n                            \"version\": 1\n                        }\n                    }\n```\n\nThe ```subWorkflowParam``` tells conductor which workflow to call. The task is marked as completed upon the completion of the spawned workflow. \nIf the sub-workflow is terminated or fails the task is marked as failure and retried if configured. \n\n### Optional Sub Workflow Task\nIf the Sub Workflow task is defined as optional in the parent workflow task definition, the parent workflow task will not be retried if sub-workflow is terminated or failed.\nIn addition, even if the sub-workflow is retried/rerun/restarted after reaching to a terminal status, the parent workflow task status will remain as it is.\n"
  },
  {
    "path": "docs/docs/reference-docs/switch-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Switch\n\n```json\n\"type\" : \"SWITCH\"\n```\n### Introduction\nA switch task is similar to `case...switch` statement in a programming language. The `switch` expression, is\na configuration on the `SWITCH` task type. Currently, two evaluators are supported:\n\n### Configuration\n\nFollowing are the task configuration parameters :\n\n| name          | type                    | description                                                                                                                                          |\n|---------------|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|\n| evaluatorType | String                  | [evaluatortType values](#evaluator-types)                                                                                                            |\n| expression    | String                  | Depends on the [evaluatortType value](#evaluator-types)                                                                                              |\n| decisionCases | Map[String, List[task]] | Map where key is possible values that can result from `expression` being evaluated by `evaluatorType` with value being list of tasks to be executed. |\n| defaultCase   | List[task]              | List of tasks to be executed when no matching value is found in decision case (default condition)                                                    |\n\n\n#### Evaluator Types\n| name        | description                                       | expression            |\n|-------------|---------------------------------------------------|-----------------------|\n| value-param | Use a parameter directly as the value             | input parameter       |\n| javascript  | Evaluate JavaScript expressions and compute value | JavaScript expression |\n\n### Use Cases\n\nUseful in any situation where we have to execute one of many task options.\n\n\n### Output\n\nFollowing is/are output generated by the `Switch` Task.\n\n| name             | type         | description                                                   |\n|------------------|--------------|---------------------------------------------------------------|\n| evaluationResult | List[String] | A List of string representing the list of cases that matched. |\n\n\n### Examples\n\nIn this example workflow, we have to ship a package with the shipping service providers on the basis of input provided\nwhile running the workflow.\n\nLet's create a Workflow with the following switch task definition that uses `value-param` evaluatorType:\n\n```json\n{\n  \"name\": \"switch_task\",\n  \"taskReferenceName\": \"switch_task\",\n  \"inputParameters\": {\n    \"switchCaseValue\": \"${workflow.input.service}\"\n  },\n  \"type\": \"SWITCH\",\n  \"evaluatorType\": \"value-param\",\n  \"expression\": \"switchCaseValue\",\n  \"defaultCase\": [\n    {\n      ...\n    }\n  ],\n  \"decisionCases\": {\n    \"fedex\": [\n      {\n        ...\n      }\n    ],\n    \"ups\": [\n      {\n        ...\n      }\n    ]\n  }\n}\n```\n\nIn the definition above the value of the parameter `switch_case_value`\nis used to determine the switch-case. The evaluator type is `value-param` and the expression is a direct reference to\nthe name of an input parameter. If the value of `switch_case_value` is `fedex` then the decision case `ship_via_fedex`is\nexecuted as shown below.\n\n![Conductor UI - Workflow Run](/img/Switch_Fedex.png)\n\nIn a similar way - if the input was `ups`, then `ship_via_ups` will be executed. If none of the cases match then the\ndefault option is executed.\n\nHere is an example using the `javascript` evaluator type:\n\n```json\n{\n  \"name\": \"switch_task\",\n  \"taskReferenceName\": \"switch_task\",\n  \"inputParameters\": {\n    \"inputValue\": \"${workflow.input.service}\"\n  },\n  \"type\": \"SWITCH\",\n  \"evaluatorType\": \"javascript\",\n  \"expression\": \"$.inputValue == 'fedex' ? 'fedex' : 'ups'\",\n  \"defaultCase\": [\n    {\n      ...\n    }\n  ],\n  \"decisionCases\": {\n    \"fedex\": [\n      {\n        ...\n      }\n    ],\n    \"ups\": [\n      {\n        ...\n      }\n    ]\n  }\n}\n```\n"
  },
  {
    "path": "docs/docs/reference-docs/terminate-task.md",
    "content": "---\nsidebar_position: 1\n---\n# Terminate\n\n```json\n\"type\" : \"TERMINATE\"\n```\n### Introduction\nTask that can terminate a workflow with a given status and modify the workflow's output with a given parameter, \nit can act as a `return` statement for conditions where you simply want to terminate your workflow. \n\n### Use Cases\nUse it when you want to terminate the workflow without continuing the execution.  \nFor example, if you have a decision where the first condition is met, you want to execute some tasks, \notherwise you want to finish your workflow.\n\n### Configuration\n\nTerminate task is defined directly inside the workflow with type\n`TERMINATE`.\n\n```json\n{\n  \"name\": \"terminate\",\n  \"taskReferenceName\": \"terminate0\",\n  \"inputParameters\": {\n      \"terminationStatus\": \"COMPLETED\",\n      \"workflowOutput\": \"${task0.output}\"\n  },\n  \"type\": \"TERMINATE\",\n  \"startDelay\": 0,\n  \"optional\": false\n}\n```\n\n#### Inputs\n\n**Parameters:**\n\n| name              | type   | description                             | notes                   |\n|-------------------|--------|-----------------------------------------|-------------------------|\n| terminationStatus | String | can only accept \"COMPLETED\" or \"FAILED\" | task cannot be optional |\n| workflowOutput    | Any    | Expected workflow output                ||\n|terminationReason|String| For failed tasks, this reason is passed to a failureWorkflow|\n\n### Output\n\n**Outputs:**\n\n| name   | type | description                                                                                               |\n|--------|------|-----------------------------------------------------------------------------------------------------------|\n| output | Map  | The content of `workflowOutput` from the inputParameters. An empty object if `workflowOutput` is not set. |\n\n### Examples\n\nLet's consider the same example we had in [Switch Task](/reference-docs/switch-task.html).\n\nSuppose in a workflow, we have to take decision to ship the courier with the shipping\nservice providers on the basis of input provided while running the workflow.\nIf the input provided while running workflow does not match with the available\nshipping providers then the workflow will fail and return. If input provided \nmatches then it goes ahead.\n\nHere is a snippet that shows the default switch case terminating the workflow:\n\n```json\n{\n  \"name\": \"switch_task\",\n  \"taskReferenceName\": \"switch_task\",\n  \"type\": \"SWITCH\",\n  \"defaultCase\": [\n      {\n      \"name\": \"terminate\",\n      \"taskReferenceName\": \"terminate\",\n      \"type\": \"TERMINATE\",\n      \"inputParameters\": {\n          \"terminationStatus\": \"FAILED\",\n          \"terminationReason\":\"Shipping provider not found.\"\n      }      \n    }\n   ]\n}\n```\n\nWorkflow gets created as shown in the diagram.\n\n![Conductor UI - Workflow Diagram](/img/Terminate_Task.png)\n\n\n### Best Practices\n1. Include termination reason when terminating the workflow with failure status to make it easy to understand the cause.\n2. Include any additional details (e.g. output of the tasks, switch case etc) that helps understand the path taken to termination.\n"
  },
  {
    "path": "docs/docs/reference-docs/wait-task.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Wait\n```json\n\"name\": \"waiting_task\",\n\"taskReferenceName\":\"waiting_task_ref\",\n\"type\" : \"WAIT\"\n```\n## Introduction\n\nWAIT is used when the workflow needs to be paused for an external signal to continue.\n\n## Use Cases\nWAIT is used when the workflow needs to wait and pause for an external signal such as a human intervention \n(like manual approval) or an event coming from external source such as Kafka, SQS or Conductor's internal queueing mechanism.\n\nSome use cases where WAIT task is used:\n\n1. Wait for a certain amount of time (e.g. 2 minutes) or until a certain date time (e.g. 12/25/2022 00:00)\n2. To wait for and external signal coming from an event queue mechanism supported by Conductor\n\n## Configuration\n* taskType: WAIT\n* Wait for a specific amount of time\nformat: short: **D**d**H**h**M**m or full:  **D**days**H**hours**M**minutes \n* The following are the accepted units: *days*, *d*, *hrs*, *hours*, *h*, *minutes*, *mins*, *m*, *seconds*, *secs*, *s*\n```json\n{\n  \"taskType\": \"WAIT\",\n  \"inputParameters\": {\n    \"duration\": \"2 days 3 hours\"  \n  }\n}\n```\n* Wait until specific date/time\n* e.g. the following Wait task remains blocked until Dec 25, 2022 9am PST\n* The date/time can be supplied in one of the following formats: \n**yyyy-MM-dd HH:mm**, **yyyy-MM-dd HH:mm**, **yyyy-MM-dd**\n```json\n{\n  \"name\":\"wait_until_date\",\n  \"taskReferenceName\":\"wait_until_date_ref\",\n  \"taskType\": \"WAIT\",\n  \"inputParameters\": {\n    \"until\": \"2022-12-25 09:00 PST\"\n  }\n}\n```\n\n## Ending a WAIT when there is no time duration specified\n\nTo conclude a WAIT task, there are three endpoints that can be used. \nYou'll need the  ```workflowId```, ```taskRefName``` or ```taskId``` and the task status (generally ```COMPLETED``` or ```FAILED```).\n\n1. POST ```/api/tasks```\n2. POST ```api/queue/update/{workflowId}/{taskRefName}/{status}``` \n3. POST ```api/queue/update/{workflowId}/task/{taskId}/{status}``` \n\nAny parameter that is sent in the body of the POST message will be repeated as the output of the task.  For example, if we send a COMPLETED message as follows:\n\n```bash\ncurl -X \"POST\" \"https://play.orkes.io/api/queue/update/{workflowId}/waiting_around_ref/COMPLETED\" -H 'Content-Type: application/json' -d '{\"data_key\":\"somedatatoWait1\",\"data_key2\":\"somedatatoWAit2\"}'\n```\n\nThe output of the task will be:\n\n```json\n{\n  \"data_key\":\"somedatatoWait1\",\n  \"data_key2\":\"somedatatoWAit2\"\n}\n```"
  },
  {
    "path": "docs/docs/resources/code-of-conduct.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at netflixoss@netflix.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "docs/docs/resources/contributing.md",
    "content": "# Contributing\nThanks for your interest in Conductor!\nThis guide helps to find the most efficient way to contribute, ask questions, and report issues.\n\nCode of conduct\n-----\n\nPlease review our [code of conduct](code-of-conduct.md).\n\nI have a question!\n-----\n\nWe have a dedicated [discussion forum](https://github.com/Netflix/conductor/discussions) for asking \"how to\" questions and to discuss ideas. The discussion forum is a great place to start if you're considering creating a feature request or work on a Pull Request.\n*Please do not create issues to ask questions.*\n\nI want to contribute!\n------\n\nWe welcome Pull Requests and already had many outstanding community contributions!\nCreating and reviewing Pull Requests take considerable time. This section helps you to set up a smooth Pull Request experience.\n\nThe stable branch is [main](https://github.com/Netflix/conductor/tree/main).\n\nPlease create pull requests for your contributions against [main](https://github.com/Netflix/conductor/tree/main) only.\n\nIt's a great idea to discuss the new feature you're considering on the [discussion forum](https://github.com/Netflix/conductor/discussions) before writing any code. There are often different ways you can implement a feature. Getting some discussion about different options helps shape the best solution. When starting directly with a Pull Request, there is the risk of having to make considerable changes. Sometimes that is the best approach, though! Showing an idea with code can be very helpful; be aware that it might be throw-away work. Some of our best Pull Requests came out of multiple competing implementations, which helped shape it to perfection.\n\nAlso, consider that not every feature is a good fit for Conductor. A few things to consider are:\n\n* Is it increasing complexity for the user, or might it be confusing?\n* Does it, in any way, break backward compatibility (this is seldom acceptable)\n* Does it require new dependencies (this is rarely acceptable for core modules)\n* Should the feature be opt-in or enabled by default. For integration with a new Queuing recipe or persistence module, a separate module which can be optionally enabled is the right choice.  \n* Should the feature be implemented in the main Conductor repository, or would it be better to set up a separate repository? Especially for integration with other systems, a separate repository is often the right choice because the life-cycle of it will be different.\n\nOf course, for more minor bug fixes and improvements, the process can be more light-weight.\n\nWe'll try to be responsive to Pull Requests. Do keep in mind that because of the inherently distributed nature of open source projects, responses to a PR might take some time because of time zones, weekends, and other things we may be working on.\n\nI want to report an issue\n-----\n\nIf you found a bug, it is much appreciated if you create an issue. Please include clear instructions on how to reproduce the issue, or even better, include a test case on a branch. Make sure to come up with a descriptive title for the issue because this helps while organizing issues.\n\nI have a great idea for a new feature\n----\nMany features in Conductor have come from ideas from the community. If you think something is missing or certain use cases could be supported better, let us know! You can do so by opening a discussion on the [discussion forum](https://github.com/Netflix/conductor/discussions). Provide as much relevant context to why and when the feature would be helpful. Providing context is especially important for \"Support XYZ\" issues since we might not be familiar with what \"XYZ\" is and why it's useful. If you have an idea of how to implement the feature, include that as well.\n\nOnce we have decided on a direction, it's time to summarize the idea by creating a new issue.\n\n## Code Style\nWe use [spotless](https://github.com/diffplug/spotless) to enforce consistent code style for the project, so make sure to run `gradlew spotlessApply` to fix any violations after code changes.\n\n## License\n\nBy contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/Netflix/conductor/blob/master/LICENSE\n\nAll files are released with the Apache 2.0 license, and the following license header will be automatically added to your new file if none present:\n\n```\n/**\n * Copyright $YEAR Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\n```\n"
  },
  {
    "path": "docs/docs/resources/license.md",
    "content": "# License\n\nCopyright 2022 Netflix, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "docs/docs/resources/related.md",
    "content": "# Community projects related to Conductor\n\n## Client SDKs\n\nFurther, all of the (non-Java) SDKs have a new GitHub home: the Conductor SDK repository is your new source for Conductor SDKs:\n\n* [Golang](https://github.com/conductor-sdk/conductor-go)\n* [Python](https://github.com/conductor-sdk/conductor-python)\n* [C#](https://github.com/conductor-sdk/conductor-csharp)\n* [Clojure](https://github.com/conductor-sdk/conductor-clojure)\n\nAll contributions on the above client sdks can be made on [Conductor SDK](https://github.com/conductor-sdk) repository.\n\n## Microservices operations\n\n* https://github.com/flaviostutz/schellar - Schellar is a scheduler tool for instantiating Conductor workflows from time to time, mostly like a cron job, but with transport of input/output variables between calls.\n\n* https://github.com/flaviostutz/backtor - Backtor is a backup scheduler tool that uses Conductor workers to handle backup operations and decide when to expire backups (ex.: keep backup 3 days, 2 weeks, 2 months, 1 semester)\n\n* https://github.com/cquon/conductor-tools - Conductor CLI for launching workflows, polling tasks, listing running tasks etc\n\n\n## Conductor deployment\n\n* https://github.com/flaviostutz/conductor-server - Docker container for running Conductor with  Prometheus metrics plugin installed and some tweaks to ease provisioning of workflows from json files embedded to the container\n\n* https://github.com/flaviostutz/conductor-ui - Docker container for running Conductor UI so that you can easily scale UI independently\n\n* https://github.com/flaviostutz/elasticblast - \"Elasticsearch to Bleve\" bridge tailored for running Conductor on top of Bleve indexer. The footprint of Elasticsearch may cost too much for small deployments on Cloud environment.\n\n* https://github.com/mohelsaka/conductor-prometheus-metrics - Conductor plugin for exposing Prometheus metrics over path '/metrics'\n\n## OAuth2.0 Security Configuration\nForked Repository - [Conductor (Secure)](https://github.com/maheshyaddanapudi/conductor/tree/oauth2)\n\n[OAuth2.0 Role Based Security!](https://github.com/maheshyaddanapudi/conductor/blob/oauth2/SECURITY.md) - Spring Security with easy configuration to secure the Conductor server APIs.\n\nDocker image published to [Docker Hub](https://hub.docker.com/repository/docker/conductorboot/server)\n\n## Conductor Worker utilities\n\n* https://github.com/ggrcha/conductor-go-client - Conductor Golang client for writing Workers in Golang\n\n* https://github.com/courosh12/conductor-dotnet-client - Conductor DOTNET client for writing Workers in DOTNET\n  * https://github.com/TwoUnderscorez/serilog-sinks-conductor-task-log - Serilog sink for sending worker log events to Netflix Conductor\n\n* https://github.com/davidwadden/conductor-workers - Various ready made Conductor workers for common operations on some platforms (ex.: Jira, Github, Concourse)\n\n## Conductor Web UI\n\n* https://github.com/maheshyaddanapudi/conductor-ng-ui - Angular based - Conductor Workflow Management UI\n\n## Conductor Persistence\n\n### Mongo Persistence\n\n* https://github.com/maheshyaddanapudi/conductor/tree/mongo_persistence - With option to use Mongo Database as persistence unit.\n  * Mongo Persistence / Option to use Mongo Database as persistence unit.\n  * Docker Compose example with MongoDB Container.\n\n### Oracle Persistence\n\n* https://github.com/maheshyaddanapudi/conductor/tree/oracle_persistence - With option to use Oracle Database as persistence unit.\n  * Oracle Persistence / Option to use Oracle Database as persistence unit : version > 12.2 - Tested well with 19C\n  * Docker Compose example with Oracle Container.\n\n## Schedule Conductor Workflow\n* https://github.com/jas34/scheduledwf - It solves the following problem statements:\n\t* At times there are use cases in which we need to run some tasks/jobs only at a scheduled time.\n\t* In microservice architecture maintaining schedulers in various microservices is a pain.\n\t* We should have a central dedicate service that can do scheduling for us and provide a trigger to a microservices at expected time.\n* It offers an additional module `io.github.jas34.scheduledwf.config.ScheduledWfServerModule` built on the existing core \nof conductor and does not require deployment of any additional service.\nFor more details refer: [Schedule Conductor Workflows](https://jas34.github.io/scheduledwf) and [Capability In Conductor To Schedule Workflows](https://github.com/Netflix/conductor/discussions/2256)"
  },
  {
    "path": "docs/docs/technicaldetails.md",
    "content": "# Technical Details\n\n### gRPC Framework\nAs part of this addition, all of the modules and bootstrap code within them were refactored to leverage providers, which facilitated moving  the Jetty server into a separate module and the conformance to Guice guidelines and best practices. \nThis feature constitutes a server-side gRPC implementation along with protobuf RPC schemas for the workflow, metadata and task APIs that can be run concurrently with the Jersey-based HTTP/REST server. The protobuf models for all the types are exposed through the API. gRPC java clients for the workflow, metadata and task APIs are also available for use. Another valuable addition is an idiomatic Go gRPC client implementation for the worker API.\nThe proto models are auto-generated at compile time using this ProtoGen library. This custom library adds messageInput and messageOutput fields to all proto tasks and task definitions. The goal of these fields is providing a type-safe way to pass input and input metadata through tasks that use the gRPC API. These fields use the Any protobuf type which can store any arbitrary message type in a type-safe way, without the server needing to know the exact serialization format of the message. In order to expose these Any objects in the REST API, a custom encoding is used that contains the raw data of the serialized message by converting it into a dictionary with '@type' and '@value' keys, where '@type' is identical to the canonical representation and '@value' contains a base64 encoded string with the binary data of the serialized message. The JsonMapperProvider provides the object mapper initialized with this module to enable serialization/deserialization of these JSON objects.\n\n\n### Cassandra Persistence\nThe Cassandra persistence layer currently provides a partial implementation of the ExecutionDAO that supports all the CRUD operations for tasks and workflow execution. The data modelling is done in a denormalized manner and stored in two tables. The “workflows” table houses all the information for a workflow execution including all its tasks and is the source of truth for all the information regarding a workflow and its tasks. The “task_lookup” table, as the name suggests stores a lookup of taskIds to workflowId. This table facilitates the fast retrieval of task data given a taskId. \nAll the datastore operations that are used during the critical execution path of a workflow have been implemented currently. Few of the operational abilities of the ExecutionDAO are yet to be implemented. This module also does not provide implementations for QueueDAO, PollDataDAO and RateLimitingDAO. We envision using the Cassandra DAO with an external queue implementation, since implementing a queuing recipe on top of Cassandra is an anti-pattern that we want to stay away from.\n\n\n### External Payload Storage\nThe implementation of this feature is such that the externalization of payloads is fully transparent and automated to the user. Conductor operators can configure the usage of this feature and is completely abstracted and hidden from the user, thereby allowing the operators full control over the barrier limits. Currently, only AWS S3 is supported as a storage system, however, as with all other Conductor components, this is pluggable and can be extended to enable any other object store to be used as an external payload storage system.\nThe externalization of payloads is enforced using two kinds of [barriers](/externalpayloadstorage.html). Soft barriers are used when the  payload size is warranted enough to be stored as part of workflow execution. These payloads will be stored in external storage and used during execution. Hard barriers are enforced to safeguard against voluminous data, and such payloads are rejected and the workflow execution is failed.\nThe payload size is evaluated in the client before being sent over the wire to the server. If the payload size exceeds the configured soft limit, the client makes a request to the server for the location at which the payload is to be stored. In this case where S3 is being used, the server returns a signed url for the location and the client uploads the payload using this signed url. The relative path to the payload object is then stored in the workflow/task metadata. The server can then download this payload from this path and use as needed during execution. This allows the server to control access to the S3 bucket, thereby making the user applications where the worker processes are run completely agnostic of the permissions needed to access this location.\n\n\n### Dynamic Workflow Executions\nIn the earlier version (v1.x), Conductor allowed the execution of workflows referencing the workflow and task definitions stored as metadata in the system. This meant that a workflow execution with 10 custom tasks to run entailed:\n\n- Registration of the 10 task definitions if they don't exist (assuming workflow task type SIMPLE for simplicity)\n- Registration of the workflow definition\n- Each time a definition needs to be retrieved, a call to the metadata store needed to be performed\n- In addition to that, the system allowed current metadata that is in use to be altered, leading to possible inconsistencies/race conditions\n\nTo eliminate these pain points, the execution was changed such that the workflow definition is embedded within the workflow execution and the task definitions are themselves embedded within this workflow definition. This enables the concept of ephemeral/dynamic workflows and tasks. Instead of fetching metadata definitions throughout the execution, the definitions are fetched and embedded into the execution at the start of the workflow execution. This also enabled the StartWorkflowRequest to be extended to provide the complete workflow definition that will be used during execution, thus removing the need for pre-registration. The MetadataMapperService prefetches the workflow and task definitions and embeds these within the workflow data, if not provided in the StartWorkflowRequest.\n\nFollowing benefits are seen as a result of these changes:\n\n- Grants immutability of the definition stored within the execution data against modifications to the metadata store\n- Better testability of workflows with faster experimental changes to definitions\n- Reduced stress on the datastore due to prefetching the metadata only once at the start\n\n\n### Decoupling Elasticsearch from Persistence\nIn the earlier version (1.x), the indexing logic was imbibed within the persistence layer, thus creating a tight coupling between the primary datastore and the indexing engine. This meant that the primary datastore determines how we orchestrate between the storage (redis, mysql, etc) and the indexer(elastic search). The main disadvantage of this approach is the lack of flexibility, that is, we cannot run an in-memory database and external elastic search or vice-versa.\nWe plan to improve this further by removing the indexing from the critical path of workflow execution, thus reducing possible points of failure during execution.\n\n\n### Elasticsearch 5/6 Support\nIndexing workflow execution is one of the primary features of Conductor. This enables archival of terminal state workflows from the primary data store, along with providing a clean search capability from the UI. \nIn Conductor 1.x, we supported both versions 2 and 5 of Elasticsearch by shadowing version 5 and all its dependencies. This proved to be rather tedious increasing build times by over 10 minutes. In Conductor 2.x, we have removed active support for ES 2.x, because of valuable community contributions for elasticsearch 5 and elasticsearch 6 modules. Unlike Conductor 1.x, Conductor 2.x supports elasticsearch 5 by default, which can easily be replaced with version 6 by following the simple instructions [here](https://github.com/Netflix/conductor/tree/master/es6-persistence#build).\n\n### Maintaining workflow consistency with distributed locking and fencing tokens\n\n#### Problem\n\nConductor’s Workflow decide is the core logic which recursively evaluates the state of the workflow, schedules tasks, persists workflow and task(s) state at several checkpoints, and progresses the workflow.\n \nIn a multi-node Conductor server deployment, the decide on a workflow can be triggered concurrently. For example, the worker can update Conductor server with latest task state, which calls decide, while the sweeper service (which periodically evaluates the workflow state to progress from task timeouts) would also call the decide on a different instance. The decide can be run concurrently in two different jvm nodes with two different workflow states, and based on the workflow configuration and current state, the result could be inconsistent.\n\n#### A two-part solution to maintain Workflow Consistency\n\n**Preventing concurrent decides with distributed locking:**\nThe goal is to allow only one decide to run on a workflow at any given time across the whole Conductor Server cluster. This can be achieved by plugging in distributed locking implementations like Zookeeper, Redlock etc. A Zookeeper module implementing Conductor’s Locking service is provided.\n\n**Preventing stale data updates with fencing tokens:**\nWhile the locking service helps to run one decide at a time, it might still be possible for nodes with timed out locks to reactivate and continue execution from where it left off (usually with stale data). This can be avoided with fencing tokens, which basically is an incrementing counter on workflow state with read-before-write support in a transaction or similar construct.\n\n*At Netflix, we use Cassandra. Considering the tradeoffs of Cassandra’s Lightweight Transactions (LWT) and the probability of this stale updates happening, and our testing results, we’ve decided to first only rollout distributed locking with Zookeeper. We'll monitor our system and add C* LWT if needed.\n\n#### Setting up desired level of consistency\n\nBased on your requirements, it is possible to use none, one or both of the distributed locking and fencing tokens implementations.\n\n#### Alternative solution to distributed \"decide\" evaluation\n\nAs mentioned in the previous section, the \"decide\" logic is triggered from multiple places in a conductor instance. Either a direct trigger such as user starting a workflow or a timed trigger from the Sweeper service.\n\n> Sweeper service is responsible for continually checking state of all workflows executions and trigger the \"decide\" logic which in turn can time the workflow out.\n\nIn a single node deployment (single dynomite rack and single conductor server) this shouldn't be a problem. But when running multiple replicated dynomite racks and a conductor server on top of each rack, this might trigger the race condition described in previous section.\n\n> Dynomite rack is a single or multiple instance dynomite setup that holds all the data.\n\n> More on dynomite HA setup: (https://netflixtechblog.com/introducing-dynomite-making-non-distributed-databases-distributed-c7bce3d89404)\n\nIn a cluster deployment, the default behavior for Dyno Queues is such, that it distributes the workload (round-robin style) to all the conductor servers.\nThis can create a situation where the first task to be executed is queued for conductor server #1 but the sweeper service is queued for conductor server #2.\n\n##### More on dyno queues\n\nDyno queues are the default queuing mechanism of conductor.\n\nQueues are allocated and used for:\n* Task execution - each task type gets a queue\n* Workflow execution - single queue with all currently executing workflows (deciderQueue)\n  * This queue is used by SweeperService\n\n**Each conductor server instance gets its own set of queues**. Or more precisely a queue shard of its own.\nThis means that if you have 2 task types, you end up with 6 queues altogether e.g.\n\n```\nconductor_queues.test.QUEUE._deciderQueue.c\nconductor_queues.test.QUEUE._deciderQueue.d\nconductor_queues.test.QUEUE.HTTP.c\nconductor_queues.test.QUEUE.HTTP.d\nconductor_queues.test.QUEUE.LAMBDA.c\nconductor_queues.test.QUEUE.LAMBDA.d\n```\n\n> The \"c\" and \"d\" suffixes are the shards identifying conductor server instace #1 and instance #2 respectively.\n\n> The shard names are extracted from dynomite rack name such as us-east-1c that is set in \"LOCAL_RACK\" or \"EC2_AVAILABILTY_ZONE\"\n\nConsidering an execution of a simple workflow with just 2 tasks: [HTTP, LAMBDA], you should end up with queues being filled as follows:\n\n```\nWorkflow execution    -> conductor_queues.test.QUEUE._deciderQueue.c\nHTTP taks execution   -> conductor_queues.test.QUEUE.HTTP.d\nLAMBDA task execution -> conductor_queues.test.QUEUE.LAMBDA.c\n```\n\nWhich means that SweeperService in conductor instance #1 is responsible for sweeping the workflow, conductor #2 is responsible for executing HTTP task and conductor #1 again responsible for executing LAMBDA task.\n\nThis illustrates the race condition: If the HTTP task completion in instance #2 happens at the same time as sweep in instance #1 ... you can end up with 2 different updates to a workflow execution: one update timing workflow out while the other completing the task and scheduling next.\n\n> The round-robin strategy responsible for work distribution is defined [here](https://github.com/Netflix/dyno-queues/blob/1cde55bbb69acd631c671a0cb2f9db2419163e33/dyno-queues-redis/src/main/java/com/netflix/dyno/queues/redis/sharding/RoundRobinStrategy.java)\n\n##### Back to alternative solution\n\nThe alternative solution here is **Switching round-robin queue allocation for a local-only strategy**.\nMeaning that a workflow and its task executions are queued only for the conductor instance which started the workflow.\n\nThis completely avoids the race condition for the price of removing task execution distribution.\n\nSince all tasks and the sweeper service read/write only from/to \"local\" queues, it is impossible to run into a race condition between conductor instances.\n\nThe downside here is that the workload is not distributed across all conductor servers. Which might be an advantage in active-standby deployments.\n\nConsidering other downsides ...\n\nConsidering a situation where a conductor instance goes down:\n* With local-only strategy, the workflow executions from failed conductor instance will not progress until:\n  * The conductor instance is restarted or\n  * The executions are manually terminated and restarted from a different node\n* With round-robin strategy, there is a chance the tasks will be rescheduled on a different conductor node\n  * This is nondeterministic though\n  \n**Enabling local only queue allocation strategy for dyno queues:**\n\nJust enable following setting the config.properties:\n\n```\nworkflow.dyno.queue.sharding.strategy=localOnly\n```\n\n> The default is roundRobin\n"
  },
  {
    "path": "docs/kitchensink.json",
    "content": "{\n  \"name\": \"kitchensink\",\n  \"description\": \"kitchensink workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"search_elasticsearch\",\n      \"taskReferenceName\": \"get_es_0\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"http://localhost:9200/conductor/workflow/_search?q=status:COMPLETED&size=10\",\n          \"method\": \"GET\"\n        }\n      },\n      \"type\": \"HTTP\"\n    },\n    {\n      \"name\": \"task_1\",\n      \"taskReferenceName\": \"task_1\",\n      \"inputParameters\": {\n        \"mod\": \"${workflow.input.mod}\",\n        \"oddEven\": \"${workflow.input.oddEven}\"\n      },\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"dyntask\",\n      \"taskReferenceName\": \"task_2\",\n      \"inputParameters\": {\n        \"taskToExecute\": \"${workflow.input.task2Name}\"\n      },\n      \"type\": \"DYNAMIC\",\n      \"dynamicTaskNameParam\": \"taskToExecute\"\n    },\n    {\n      \"name\": \"oddEvenDecision\",\n      \"taskReferenceName\": \"oddEvenDecision\",\n      \"inputParameters\": {\n        \"oddEven\": \"${task_2.output.oddEven}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"oddEven\",\n      \"decisionCases\": {\n        \"0\": [\n          {\n            \"name\": \"task_4\",\n            \"taskReferenceName\": \"task_4\",\n            \"inputParameters\": {\n              \"mod\": \"${task_2.output.mod}\",\n              \"oddEven\": \"${task_2.output.oddEven}\"\n            },\n            \"type\": \"SIMPLE\"\n          },\n          {\n            \"name\": \"dynamic_fanout\",\n            \"taskReferenceName\": \"fanout1\",\n            \"inputParameters\": {\n              \"dynamicTasks\": \"${task_4.output.dynamicTasks}\",\n              \"input\": \"${task_4.output.inputs}\"\n            },\n            \"type\": \"FORK_JOIN_DYNAMIC\",\n            \"dynamicForkTasksParam\": \"dynamicTasks\",\n            \"dynamicForkTasksInputParamName\": \"input\"\n          },\n          {\n            \"name\": \"dynamic_join\",\n            \"taskReferenceName\": \"join1\",\n            \"type\": \"JOIN\"\n          }\n        ],\n        \"1\": [\n          {\n            \"name\": \"fork_join\",\n            \"taskReferenceName\": \"forkx\",\n            \"type\": \"FORK_JOIN\",\n            \"forkTasks\": [\n              [\n                {\n                  \"name\": \"task_10\",\n                  \"taskReferenceName\": \"task_10\",\n                  \"type\": \"SIMPLE\"\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf3\",\n                  \"inputParameters\": {\n                    \"mod\": \"${task_1.output.mod}\",\n                    \"oddEven\": \"${task_1.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ],\n              [\n                {\n                  \"name\": \"task_11\",\n                  \"taskReferenceName\": \"task_11\",\n                  \"type\": \"SIMPLE\"\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf4\",\n                  \"inputParameters\": {\n                    \"mod\": \"${task_1.output.mod}\",\n                    \"oddEven\": \"${task_1.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ]\n            ]\n          },\n          {\n            \"name\": \"join\",\n            \"taskReferenceName\": \"join2\",\n            \"type\": \"JOIN\",\n            \"joinOn\": [\n              \"wf3\",\n              \"wf4\"\n            ]\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"search_elasticsearch\",\n      \"taskReferenceName\": \"get_es_1\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"http://localhost:9200/conductor/workflow/_search?q=status:COMPLETED&size=10\",\n          \"method\": \"GET\"\n        }\n      },\n      \"type\": \"HTTP\"\n    },\n    {\n      \"name\": \"task_30\",\n      \"taskReferenceName\": \"task_30\",\n      \"inputParameters\": {\n        \"statuses\": \"${get_es_1.output...status}\",\n        \"fistWorkflowId\": \"${get_es_1.output.workflowId[0]}\"\n      },\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"schemaVersion\": 2\n}"
  },
  {
    "path": "docs/mkdocs.yml",
    "content": "site_name: Conductor Documentation\nsite_description: Conductor is a platform created by Netflix to orchestrate workflows that span across microservices.\nrepo_url: https://github.com/Netflix/conductor\nedit_uri: ''\nstrict: true\nuse_directory_urls: false\n\nnav:\n  - Getting Started:\n    - Running Conductor:\n      - From Source: gettingstarted/source.md\n      - Using Docker: gettingstarted/docker.md\n      - Hosted Solutions: gettingstarted/hosted.md\n    - Basic Concepts: gettingstarted/basicconcepts.md\n    - High Level Steps: gettingstarted/steps.md\n    - Using the Client: gettingstarted/client.md\n    - Start a Workflow: gettingstarted/startworkflow.md\n    - Why Conductor?: gettingstarted/intro.md\n    - How-Tos:\n      - Workflows:\n        - Debugging Workflows: how-tos/Workflows/debugging-workflows.md\n        - Handling Errors: how-tos/Workflows/handling-errors.md\n        - Searching Workflows: how-tos/Workflows/searching-workflows.md\n        - Starting Workflows: how-tos/Workflows/starting-workflows.md\n        - Updating Workflows: how-tos/Workflows/updating-workflows.md\n        - View Workflow Execution: how-tos/Workflows/view-workflow-executions.md\n        - Versioning Workflows: how-tos/Workflows/versioning-workflows.md\n      - Tasks:\n        - Creating Task Definitions: how-tos/Tasks/creating-tasks.md\n        - Dynamic vs Switch Tasks: how-tos/Tasks/dynamic-vs-switch-tasks.md\n        - Monitoring Task Queues: how-tos/Tasks/monitoring-task-queues.md\n        - Reusing Tasks: how-tos/Tasks/reusing-tasks.md\n        - Task Configurations: how-tos/Tasks/task-configurations.md\n        - Task Inputs: how-tos/Tasks/task-inputs.md\n        - Task Timeouts: how-tos/Tasks/task-timeouts.md\n        - Updating Task Definitions: how-tos/Tasks/updating-tasks.md\n        - Extending System Tasks: how-tos/Tasks/extending-system-tasks.md\n      - Workers:\n        - Build a Go Task Worker: how-tos/Workers/build-a-golang-task-worker.md\n        - Build a Java Task Worker: how-tos/Workers/build-a-java-task-worker.md\n        - Build a Python Task Worker: how-tos/Workers/build-a-python-task-worker.md\n      - Monitoring:\n        - Conductor Log Level: how-tos/Monitoring/Conductor-LogLevel.md\n    - Developer Labs:\n      - Beginner: labs/beginner.md\n      - A First Workflow: labs/running-first-workflow.md\n      - Events and Event Handlers: labs/eventhandlers.md\n      - Kitchen Sink: labs/kitchensink.md\n  - Documentation: \n    - Architecture: \n      - Overview: architecture/overview.md\n      - Task Lifecycle: architecture/tasklifecycle.md\n    - API Specification: apispec.md\n    - Configuration:\n      - Task Definition: configuration/taskdef.md\n      - Worker Definition: configuration/workerdef.md\n      - Workflow Definition: configuration/workflowdef.md\n      - System Tasks: configuration/systask.md\n      - System Operators: configuration/sysoperator.md\n      - Event Handlers: configuration/eventhandlers.md\n      - Task Domains: configuration/taskdomains.md\n      - Isolation Groups: configuration/isolationgroups.md\n    - Operators:\n      -  Do-While: reference-docs/do-while-task.md\n      -  Dynamic: reference-docs/dynamic-task.md\n      -  Dynamic Fork: reference-docs/dynamic-fork-task.md\n      -  Fork: reference-docs/fork-task.md\n      -  Join:    reference-docs/join-task.md\n      -  Set Variable:  reference-docs/set-variable-task.md\n      -  Start Workflow: reference-docs/start-workflow-task.md\n      -  Sub Workflow: reference-docs/sub-workflow-task.md\n      -  Switch: reference-docs/switch-task.md\n      -  Terminate: reference-docs/terminate-task.md\n      -  Wait: reference-docs/wait-task.md\n    - System Tasks:\n      - Event Task: reference-docs/event-task.md\n      - HTTP Task: reference-docs/http-task.md\n      - Human Task: reference-docs/human-task.md\n      - Inline Task: reference-docs/inline-task.md\n      - JSON JQ Transform Task: reference-docs/json-jq-transform-task.md\n      - Kafka Publish Task: reference-docs/kafka-publish-task.md\n    - Conductor Metrics:\n      - Server Metrics: metrics/server.md\n      - Client Metrics: metrics/client.md\n    - Advanced Topics:\n      - Extending Conductor: extend.md\n      - Annotation Processor: reference-docs/annotation-processor.md\n      - Archival of Workflows: reference-docs/archival-of-workflows.md\n      - Azure Blob Storage: reference-docs/azureblob-storage.md\n      - External Payload Storage: externalpayloadstorage.md\n      - Redis: reference-docs/redis.md\n    - SDKs:\n      - CSharp SDK: how-tos/csharp-sdk.md\n      - Clojure SDK: how-tos/clojure-sdk.md\n      - Go SDK: how-tos/go-sdk.md\n      - Python SDK: how-tos/python-sdk.md\n      \n    - Best Practices: bestpractices.md\n    - FAQ: faq.md\n    - Directed Acyclic Graph: reference-docs/directed-acyclic-graph.md\n    - Technical Details: technicaldetails.md\n  - Resources:\n    - Contributing: resources/contributing.md\n    - Code of Conduct: resources/code-of-conduct.md\n    - Related Projects: resources/related.md\n    - License: resources/license.md\ntheme: \n  name: mkdocs\n  custom_dir: theme/\nextra_css:\n  - css/custom.css\nmarkdown_extensions:\n- admonition\n- codehilite\n"
  },
  {
    "path": "docs/theme/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block extrahead %}\n<link rel=\"icon\" href=\"/img/favicon.svg\">\n\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&family=Roboto:wght@400;500;700&display=swap\" rel=\"stylesheet\">\n{% endblock %}\n\n\n{% block content %}\n{% if page and page.is_homepage %}\n<div class=\"col-md-12 main\">{% include \"content.html\" %}</div>\n{% else %}\n<div class=\"col-md-3\">{% include \"toc.html\" %}</div>\n<div class=\"col-md-9 main\">{% include \"content.html\" %}</div>\n{% endif %}\n{% endblock %}\n\n{%- block next_prev %}\n{%- endblock %}\n\n{% block footer %}\n<div class=\"footer\">\n  <div class=\"container\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-6\">\n      <div class=\"subhead\">Netflix Engineering</div>\n      <div><a href=\"https://netflixtechblog.com\">Tech Blog</a></div>\n      <div><a href=\"https://twitter.com/netflixeng\">Twitter</a></div>\n      <div><a href=\"https://www.youtube.com/channel/UCGGRRqAjPm6sL3-WGBDnKJA\">Youtube</a></div>\n      <div><a href=\"https://netflix.github.io\">Github</a></div>\n      <div><a href=\"https://jobs.netflix.com\">Jobs</a></div>\n    </div>\n    <div class=\"col-6 fr\">\n      <img src=\"/img/netflix-oss.png\" />\n    </div>\n  </div>\n</div>\n</div>\n{% endblock %}"
  },
  {
    "path": "docs/theme/toc-sub.html",
    "content": "{%- if not nav_item.children %}\n<li class=\"tocitem\">\n    <a href=\"{{ nav_item.url|url }}\" class=\"toc-link {% if nav_item.active %} active{% endif %}\">{{ nav_item.title }}</a>\n</li>\n{%- else %}\n  <li class=\"tocitem\">\n    <span class=\"toc-link\">{{ nav_item.title }}</span>\n    <ul>\n      {%- for nav_item in nav_item.children %}\n        {% include \"toc-sub.html\" %}\n      {%- endfor %}\n    </ul>\n  </li>\n{%- endif %}\n"
  },
  {
    "path": "docs/theme/toc.html",
    "content": "<div class=\"toc bs-sidebar hidden-print\">\n  <ul>\n    {%- for nav_item in nav %}  \n      {% include \"toc-sub.html\" %}\n    {%- endfor %}\n  </ul>\n</div>"
  },
  {
    "path": "es6-persistence/build.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\ndependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n    compileOnly 'org.springframework.retry:spring-retry'\n\n    implementation \"commons-io:commons-io:${revCommonsIo}\"\n    implementation \"org.apache.commons:commons-lang3\"\n    // SBMTODO: remove guava dep\n    implementation \"com.google.guava:guava:${revGuava}\"\n\n    implementation \"org.elasticsearch.client:transport\"\n    implementation \"org.elasticsearch.client:elasticsearch-rest-client\"\n    implementation \"org.elasticsearch.client:elasticsearch-rest-high-level-client\"\n\n    testImplementation 'org.springframework.retry:spring-retry'\n    testImplementation \"org.awaitility:awaitility:${revAwaitility}\"\n    testImplementation \"org.testcontainers:elasticsearch:${revTestContainer}\"\n    testImplementation project(':conductor-common').sourceSets.test.output\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/config/ElasticSearchConditions.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.config;\n\nimport org.springframework.boot.autoconfigure.condition.AllNestedConditions;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\n\npublic class ElasticSearchConditions {\n\n    private ElasticSearchConditions() {}\n\n    public static class ElasticSearchV6Enabled extends AllNestedConditions {\n\n        ElasticSearchV6Enabled() {\n            super(ConfigurationPhase.PARSE_CONFIGURATION);\n        }\n\n        @SuppressWarnings(\"unused\")\n        @ConditionalOnProperty(\n                name = \"conductor.indexing.enabled\",\n                havingValue = \"true\",\n                matchIfMissing = true)\n        static class enabledIndexing {}\n\n        @SuppressWarnings(\"unused\")\n        @ConditionalOnProperty(\n                name = \"conductor.elasticsearch.version\",\n                havingValue = \"6\",\n                matchIfMissing = true)\n        static class enabledES6 {}\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/config/ElasticSearchProperties.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.config;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\n\n@ConfigurationProperties(\"conductor.elasticsearch\")\npublic class ElasticSearchProperties {\n\n    /**\n     * The comma separated list of urls for the elasticsearch cluster. Format --\n     * host1:port1,host2:port2\n     */\n    private String url = \"localhost:9300\";\n\n    /** The index prefix to be used when creating indices */\n    private String indexPrefix = \"conductor\";\n\n    /** The color of the elasticserach cluster to wait for to confirm healthy status */\n    private String clusterHealthColor = \"green\";\n\n    /** The size of the batch to be used for bulk indexing in async mode */\n    private int indexBatchSize = 1;\n\n    /** The size of the queue used for holding async indexing tasks */\n    private int asyncWorkerQueueSize = 100;\n\n    /** The maximum number of threads allowed in the async pool */\n    private int asyncMaxPoolSize = 12;\n\n    /**\n     * The time in seconds after which the async buffers will be flushed (if no activity) to prevent\n     * data loss\n     */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration asyncBufferFlushTimeout = Duration.ofSeconds(10);\n\n    /** The number of shards that the index will be created with */\n    private int indexShardCount = 5;\n\n    /** The number of replicas that the index will be configured to have */\n    private int indexReplicasCount = 1;\n\n    /** The number of task log results that will be returned in the response */\n    private int taskLogResultLimit = 10;\n\n    /** The timeout in milliseconds used when requesting a connection from the connection manager */\n    private int restClientConnectionRequestTimeout = -1;\n\n    /** Used to control if index management is to be enabled or will be controlled externally */\n    private boolean autoIndexManagementEnabled = true;\n\n    /**\n     * Document types are deprecated in ES6 and removed from ES7. This property can be used to\n     * disable the use of specific document types with an override. This property is currently used\n     * in ES6 module.\n     *\n     * <p><em>Note that this property will only take effect if {@link\n     * ElasticSearchProperties#isAutoIndexManagementEnabled} is set to false and index management is\n     * handled outside of this module.</em>\n     */\n    private String documentTypeOverride = \"\";\n\n    /** Elasticsearch basic auth username */\n    private String username;\n\n    /** Elasticsearch basic auth password */\n    private String password;\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getIndexPrefix() {\n        return indexPrefix;\n    }\n\n    public void setIndexPrefix(String indexPrefix) {\n        this.indexPrefix = indexPrefix;\n    }\n\n    public String getClusterHealthColor() {\n        return clusterHealthColor;\n    }\n\n    public void setClusterHealthColor(String clusterHealthColor) {\n        this.clusterHealthColor = clusterHealthColor;\n    }\n\n    public int getIndexBatchSize() {\n        return indexBatchSize;\n    }\n\n    public void setIndexBatchSize(int indexBatchSize) {\n        this.indexBatchSize = indexBatchSize;\n    }\n\n    public int getAsyncWorkerQueueSize() {\n        return asyncWorkerQueueSize;\n    }\n\n    public void setAsyncWorkerQueueSize(int asyncWorkerQueueSize) {\n        this.asyncWorkerQueueSize = asyncWorkerQueueSize;\n    }\n\n    public int getAsyncMaxPoolSize() {\n        return asyncMaxPoolSize;\n    }\n\n    public void setAsyncMaxPoolSize(int asyncMaxPoolSize) {\n        this.asyncMaxPoolSize = asyncMaxPoolSize;\n    }\n\n    public Duration getAsyncBufferFlushTimeout() {\n        return asyncBufferFlushTimeout;\n    }\n\n    public void setAsyncBufferFlushTimeout(Duration asyncBufferFlushTimeout) {\n        this.asyncBufferFlushTimeout = asyncBufferFlushTimeout;\n    }\n\n    public int getIndexShardCount() {\n        return indexShardCount;\n    }\n\n    public void setIndexShardCount(int indexShardCount) {\n        this.indexShardCount = indexShardCount;\n    }\n\n    public int getIndexReplicasCount() {\n        return indexReplicasCount;\n    }\n\n    public void setIndexReplicasCount(int indexReplicasCount) {\n        this.indexReplicasCount = indexReplicasCount;\n    }\n\n    public int getTaskLogResultLimit() {\n        return taskLogResultLimit;\n    }\n\n    public void setTaskLogResultLimit(int taskLogResultLimit) {\n        this.taskLogResultLimit = taskLogResultLimit;\n    }\n\n    public int getRestClientConnectionRequestTimeout() {\n        return restClientConnectionRequestTimeout;\n    }\n\n    public void setRestClientConnectionRequestTimeout(int restClientConnectionRequestTimeout) {\n        this.restClientConnectionRequestTimeout = restClientConnectionRequestTimeout;\n    }\n\n    public boolean isAutoIndexManagementEnabled() {\n        return autoIndexManagementEnabled;\n    }\n\n    public void setAutoIndexManagementEnabled(boolean autoIndexManagementEnabled) {\n        this.autoIndexManagementEnabled = autoIndexManagementEnabled;\n    }\n\n    public String getDocumentTypeOverride() {\n        return documentTypeOverride;\n    }\n\n    public void setDocumentTypeOverride(String documentTypeOverride) {\n        this.documentTypeOverride = documentTypeOverride;\n    }\n\n    public String getUsername() {\n        return username;\n    }\n\n    public void setUsername(String username) {\n        this.username = username;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public List<URL> toURLs() {\n        String clusterAddress = getUrl();\n        String[] hosts = clusterAddress.split(\",\");\n        return Arrays.stream(hosts)\n                .map(\n                        host ->\n                                (host.startsWith(\"http://\")\n                                                || host.startsWith(\"https://\")\n                                                || host.startsWith(\"tcp://\"))\n                                        ? toURL(host)\n                                        : toURL(\"tcp://\" + host))\n                .collect(Collectors.toList());\n    }\n\n    private URL toURL(String url) {\n        try {\n            return new URL(url);\n        } catch (MalformedURLException e) {\n            throw new IllegalArgumentException(url + \"can not be converted to java.net.URL\");\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/config/ElasticSearchV6Configuration.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.config;\n\nimport java.net.InetAddress;\nimport java.net.URI;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\nimport org.apache.http.HttpHost;\nimport org.apache.http.auth.AuthScope;\nimport org.apache.http.auth.UsernamePasswordCredentials;\nimport org.apache.http.client.CredentialsProvider;\nimport org.apache.http.impl.client.BasicCredentialsProvider;\nimport org.elasticsearch.client.Client;\nimport org.elasticsearch.client.RestClient;\nimport org.elasticsearch.client.RestClientBuilder;\nimport org.elasticsearch.client.transport.TransportClient;\nimport org.elasticsearch.common.settings.Settings;\nimport org.elasticsearch.common.transport.TransportAddress;\nimport org.elasticsearch.transport.client.PreBuiltTransportClient;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.retry.backoff.FixedBackOffPolicy;\nimport org.springframework.retry.support.RetryTemplate;\n\nimport com.netflix.conductor.dao.IndexDAO;\nimport com.netflix.conductor.es6.dao.index.ElasticSearchDAOV6;\nimport com.netflix.conductor.es6.dao.index.ElasticSearchRestDAOV6;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(ElasticSearchProperties.class)\n@Conditional(ElasticSearchConditions.ElasticSearchV6Enabled.class)\npublic class ElasticSearchV6Configuration {\n    private static final Logger log = LoggerFactory.getLogger(ElasticSearchV6Configuration.class);\n\n    @Bean\n    @Conditional(IsTcpProtocol.class)\n    public Client client(ElasticSearchProperties properties) {\n        Settings settings =\n                Settings.builder()\n                        .put(\"client.transport.ignore_cluster_name\", true)\n                        .put(\"client.transport.sniff\", true)\n                        .build();\n\n        TransportClient transportClient = new PreBuiltTransportClient(settings);\n\n        List<URI> clusterAddresses = getURIs(properties);\n\n        if (clusterAddresses.isEmpty()) {\n            log.warn(\"workflow.elasticsearch.url is not set.  Indexing will remain DISABLED.\");\n        }\n        for (URI hostAddress : clusterAddresses) {\n            int port = Optional.ofNullable(hostAddress.getPort()).orElse(9200);\n            try {\n                transportClient.addTransportAddress(\n                        new TransportAddress(InetAddress.getByName(hostAddress.getHost()), port));\n            } catch (Exception e) {\n                throw new RuntimeException(\"Invalid host\" + hostAddress.getHost(), e);\n            }\n        }\n        return transportClient;\n    }\n\n    @Bean\n    @Conditional(IsHttpProtocol.class)\n    public RestClient restClient(ElasticSearchProperties properties) {\n        RestClientBuilder restClientBuilder =\n                RestClient.builder(convertToHttpHosts(properties.toURLs()));\n        if (properties.getRestClientConnectionRequestTimeout() > 0) {\n            restClientBuilder.setRequestConfigCallback(\n                    requestConfigBuilder ->\n                            requestConfigBuilder.setConnectionRequestTimeout(\n                                    properties.getRestClientConnectionRequestTimeout()));\n        }\n\n        return restClientBuilder.build();\n    }\n\n    @Bean\n    @Conditional(IsHttpProtocol.class)\n    public RestClientBuilder restClientBuilder(ElasticSearchProperties properties) {\n        RestClientBuilder builder = RestClient.builder(convertToHttpHosts(properties.toURLs()));\n\n        if (properties.getUsername() != null && properties.getPassword() != null) {\n            log.info(\n                    \"Configure ElasticSearch with BASIC authentication. User:{}\",\n                    properties.getUsername());\n            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n            credentialsProvider.setCredentials(\n                    AuthScope.ANY,\n                    new UsernamePasswordCredentials(\n                            properties.getUsername(), properties.getPassword()));\n            builder.setHttpClientConfigCallback(\n                    httpClientBuilder ->\n                            httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));\n        } else {\n            log.info(\"Configure ElasticSearch with no authentication.\");\n        }\n        return builder;\n    }\n\n    @Bean\n    @Conditional(IsHttpProtocol.class)\n    public IndexDAO es6IndexRestDAO(\n            RestClientBuilder restClientBuilder,\n            ElasticSearchProperties properties,\n            @Qualifier(\"es6RetryTemplate\") RetryTemplate retryTemplate,\n            ObjectMapper objectMapper) {\n        return new ElasticSearchRestDAOV6(\n                restClientBuilder, retryTemplate, properties, objectMapper);\n    }\n\n    @Bean\n    @Conditional(IsTcpProtocol.class)\n    public IndexDAO es6IndexDAO(\n            Client client,\n            @Qualifier(\"es6RetryTemplate\") RetryTemplate retryTemplate,\n            ElasticSearchProperties properties,\n            ObjectMapper objectMapper) {\n        return new ElasticSearchDAOV6(client, retryTemplate, properties, objectMapper);\n    }\n\n    @Bean\n    public RetryTemplate es6RetryTemplate() {\n        RetryTemplate retryTemplate = new RetryTemplate();\n        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();\n        fixedBackOffPolicy.setBackOffPeriod(1000L);\n        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);\n        return retryTemplate;\n    }\n\n    private HttpHost[] convertToHttpHosts(List<URL> hosts) {\n        return hosts.stream()\n                .map(host -> new HttpHost(host.getHost(), host.getPort(), host.getProtocol()))\n                .toArray(HttpHost[]::new);\n    }\n\n    public List<URI> getURIs(ElasticSearchProperties properties) {\n        String clusterAddress = properties.getUrl();\n        String[] hosts = clusterAddress.split(\",\");\n\n        return Arrays.stream(hosts)\n                .map(\n                        host ->\n                                (host.startsWith(\"http://\")\n                                                || host.startsWith(\"https://\")\n                                                || host.startsWith(\"tcp://\"))\n                                        ? URI.create(host)\n                                        : URI.create(\"tcp://\" + host))\n                .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/config/IsHttpProtocol.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.config;\n\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Condition;\nimport org.springframework.context.annotation.ConditionContext;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.type.AnnotatedTypeMetadata;\n\n@EnableConfigurationProperties(ElasticSearchProperties.class)\n@Configuration\npublic class IsHttpProtocol implements Condition {\n    @Override\n    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {\n        String url = context.getEnvironment().getProperty(\"conductor.elasticsearch.url\");\n        if (url.startsWith(\"http\") || url.startsWith(\"https\")) {\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/config/IsTcpProtocol.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.config;\n\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Condition;\nimport org.springframework.context.annotation.ConditionContext;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.type.AnnotatedTypeMetadata;\n\n@EnableConfigurationProperties(ElasticSearchProperties.class)\n@Configuration\npublic class IsTcpProtocol implements Condition {\n    @Override\n    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {\n        String url = context.getEnvironment().getProperty(\"conductor.elasticsearch.url\");\n        if (url.startsWith(\"http\") || url.startsWith(\"https\")) {\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/index/BulkRequestBuilderWrapper.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.util.Objects;\n\nimport org.elasticsearch.action.ActionFuture;\nimport org.elasticsearch.action.bulk.BulkRequestBuilder;\nimport org.elasticsearch.action.bulk.BulkResponse;\nimport org.elasticsearch.action.index.IndexRequest;\nimport org.elasticsearch.action.update.UpdateRequest;\nimport org.springframework.lang.NonNull;\n\n/** Thread-safe wrapper for {@link BulkRequestBuilder}. */\npublic class BulkRequestBuilderWrapper {\n\n    private final BulkRequestBuilder bulkRequestBuilder;\n\n    public BulkRequestBuilderWrapper(@NonNull BulkRequestBuilder bulkRequestBuilder) {\n        this.bulkRequestBuilder = Objects.requireNonNull(bulkRequestBuilder);\n    }\n\n    public void add(@NonNull UpdateRequest req) {\n        synchronized (bulkRequestBuilder) {\n            bulkRequestBuilder.add(Objects.requireNonNull(req));\n        }\n    }\n\n    public void add(@NonNull IndexRequest req) {\n        synchronized (bulkRequestBuilder) {\n            bulkRequestBuilder.add(Objects.requireNonNull(req));\n        }\n    }\n\n    public int numberOfActions() {\n        synchronized (bulkRequestBuilder) {\n            return bulkRequestBuilder.numberOfActions();\n        }\n    }\n\n    public ActionFuture<BulkResponse> execute() {\n        synchronized (bulkRequestBuilder) {\n            return bulkRequestBuilder.execute();\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/index/BulkRequestWrapper.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.util.Objects;\n\nimport org.elasticsearch.action.bulk.BulkRequest;\nimport org.elasticsearch.action.index.IndexRequest;\nimport org.elasticsearch.action.update.UpdateRequest;\nimport org.springframework.lang.NonNull;\n\n/** Thread-safe wrapper for {@link BulkRequest}. */\nclass BulkRequestWrapper {\n\n    private final BulkRequest bulkRequest;\n\n    BulkRequestWrapper(@NonNull BulkRequest bulkRequest) {\n        this.bulkRequest = Objects.requireNonNull(bulkRequest);\n    }\n\n    public void add(@NonNull UpdateRequest req) {\n        synchronized (bulkRequest) {\n            bulkRequest.add(Objects.requireNonNull(req));\n        }\n    }\n\n    public void add(@NonNull IndexRequest req) {\n        synchronized (bulkRequest) {\n            bulkRequest.add(Objects.requireNonNull(req));\n        }\n    }\n\n    BulkRequest get() {\n        return bulkRequest;\n    }\n\n    int numberOfActions() {\n        synchronized (bulkRequest) {\n            return bulkRequest.numberOfActions();\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/index/ElasticSearchBaseDAO.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.io.IOException;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.elasticsearch.index.query.BoolQueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilders;\nimport org.elasticsearch.index.query.QueryStringQueryBuilder;\n\nimport com.netflix.conductor.dao.IndexDAO;\nimport com.netflix.conductor.es6.dao.query.parser.Expression;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ParserException;\n\nabstract class ElasticSearchBaseDAO implements IndexDAO {\n\n    String indexPrefix;\n\n    String loadTypeMappingSource(String path) throws IOException {\n        return applyIndexPrefixToTemplate(\n                IOUtils.toString(ElasticSearchBaseDAO.class.getResourceAsStream(path)));\n    }\n\n    private String applyIndexPrefixToTemplate(String text) {\n        String pattern = \"\\\"template\\\": \\\"\\\\*(.*)\\\\*\\\"\";\n        Pattern r = Pattern.compile(pattern);\n        Matcher m = r.matcher(text);\n        StringBuilder sb = new StringBuilder();\n        while (m.find()) {\n            m.appendReplacement(\n                    sb,\n                    m.group(0)\n                            .replaceFirst(\n                                    Pattern.quote(m.group(1)), indexPrefix + \"_\" + m.group(1)));\n        }\n        m.appendTail(sb);\n        return sb.toString();\n    }\n\n    BoolQueryBuilder boolQueryBuilder(String expression, String queryString)\n            throws ParserException {\n        QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();\n        if (StringUtils.isNotEmpty(expression)) {\n            Expression exp = Expression.fromString(expression);\n            queryBuilder = exp.getFilterBuilder();\n        }\n        BoolQueryBuilder filterQuery = QueryBuilders.boolQuery().must(queryBuilder);\n        QueryStringQueryBuilder stringQuery = QueryBuilders.queryStringQuery(queryString);\n        return QueryBuilders.boolQuery().must(stringQuery).must(filterQuery);\n    }\n\n    protected String getIndexName(String documentType) {\n        return indexPrefix + \"_\" + documentType;\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/index/ElasticSearchDAOV6.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.io.IOException;\nimport java.text.SimpleDateFormat;\nimport java.time.Instant;\nimport java.time.LocalDate;\nimport java.util.*;\nimport java.util.concurrent.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport javax.annotation.PostConstruct;\nimport javax.annotation.PreDestroy;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.elasticsearch.ResourceAlreadyExistsException;\nimport org.elasticsearch.action.DocWriteResponse;\nimport org.elasticsearch.action.admin.indices.create.CreateIndexRequest;\nimport org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;\nimport org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;\nimport org.elasticsearch.action.bulk.BulkRequestBuilder;\nimport org.elasticsearch.action.delete.DeleteRequest;\nimport org.elasticsearch.action.delete.DeleteResponse;\nimport org.elasticsearch.action.get.GetRequest;\nimport org.elasticsearch.action.get.GetResponse;\nimport org.elasticsearch.action.index.IndexRequest;\nimport org.elasticsearch.action.search.SearchRequestBuilder;\nimport org.elasticsearch.action.search.SearchResponse;\nimport org.elasticsearch.action.update.UpdateRequest;\nimport org.elasticsearch.client.Client;\nimport org.elasticsearch.common.Strings;\nimport org.elasticsearch.common.settings.Settings;\nimport org.elasticsearch.common.xcontent.XContentType;\nimport org.elasticsearch.index.IndexNotFoundException;\nimport org.elasticsearch.index.query.BoolQueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilders;\nimport org.elasticsearch.search.SearchHit;\nimport org.elasticsearch.search.SearchHits;\nimport org.elasticsearch.search.fetch.subphase.FetchSourceContext;\nimport org.elasticsearch.search.sort.SortBuilders;\nimport org.elasticsearch.search.sort.SortOrder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.retry.support.RetryTemplate;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.IndexDAO;\nimport com.netflix.conductor.es6.config.ElasticSearchProperties;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ParserException;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.type.MapType;\nimport com.fasterxml.jackson.databind.type.TypeFactory;\n\n@Trace\npublic class ElasticSearchDAOV6 extends ElasticSearchBaseDAO implements IndexDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ElasticSearchDAOV6.class);\n\n    private static final String WORKFLOW_DOC_TYPE = \"workflow\";\n    private static final String TASK_DOC_TYPE = \"task\";\n    private static final String LOG_DOC_TYPE = \"task_log\";\n    private static final String EVENT_DOC_TYPE = \"event\";\n    private static final String MSG_DOC_TYPE = \"message\";\n\n    private static final int CORE_POOL_SIZE = 6;\n    private static final long KEEP_ALIVE_TIME = 1L;\n    private static final int UPDATE_REQUEST_RETRY_COUNT = 5;\n\n    private static final String CLASS_NAME = ElasticSearchDAOV6.class.getSimpleName();\n\n    private final String workflowIndexName;\n    private final String taskIndexName;\n    private final String eventIndexPrefix;\n    private String eventIndexName;\n    private final String messageIndexPrefix;\n    private String messageIndexName;\n    private String logIndexName;\n    private final String logIndexPrefix;\n    private final String docTypeOverride;\n\n    private final ObjectMapper objectMapper;\n    private final Client elasticSearchClient;\n\n    private static final TimeZone GMT = TimeZone.getTimeZone(\"GMT\");\n    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(\"yyyyMMWW\");\n\n    private final ExecutorService executorService;\n    private final ExecutorService logExecutorService;\n\n    private final ConcurrentHashMap<String, BulkRequests> bulkRequests;\n    private final int indexBatchSize;\n    private final long asyncBufferFlushTimeout;\n    private final ElasticSearchProperties properties;\n\n    private final RetryTemplate retryTemplate;\n\n    static {\n        SIMPLE_DATE_FORMAT.setTimeZone(GMT);\n    }\n\n    public ElasticSearchDAOV6(\n            Client elasticSearchClient,\n            RetryTemplate retryTemplate,\n            ElasticSearchProperties properties,\n            ObjectMapper objectMapper) {\n        this.objectMapper = objectMapper;\n        this.elasticSearchClient = elasticSearchClient;\n        this.indexPrefix = properties.getIndexPrefix();\n        this.workflowIndexName = getIndexName(WORKFLOW_DOC_TYPE);\n        this.taskIndexName = getIndexName(TASK_DOC_TYPE);\n        this.logIndexPrefix = this.indexPrefix + \"_\" + LOG_DOC_TYPE;\n        this.messageIndexPrefix = this.indexPrefix + \"_\" + MSG_DOC_TYPE;\n        this.eventIndexPrefix = this.indexPrefix + \"_\" + EVENT_DOC_TYPE;\n        int workerQueueSize = properties.getAsyncWorkerQueueSize();\n        int maximumPoolSize = properties.getAsyncMaxPoolSize();\n        this.bulkRequests = new ConcurrentHashMap<>();\n        this.indexBatchSize = properties.getIndexBatchSize();\n        this.asyncBufferFlushTimeout = properties.getAsyncBufferFlushTimeout().toMillis();\n        this.properties = properties;\n\n        if (!properties.isAutoIndexManagementEnabled()\n                && StringUtils.isNotBlank(properties.getDocumentTypeOverride())) {\n            docTypeOverride = properties.getDocumentTypeOverride();\n        } else {\n            docTypeOverride = \"\";\n        }\n\n        this.executorService =\n                new ThreadPoolExecutor(\n                        CORE_POOL_SIZE,\n                        maximumPoolSize,\n                        KEEP_ALIVE_TIME,\n                        TimeUnit.MINUTES,\n                        new LinkedBlockingQueue<>(workerQueueSize),\n                        (runnable, executor) -> {\n                            LOGGER.warn(\n                                    \"Request  {} to async dao discarded in executor {}\",\n                                    runnable,\n                                    executor);\n                            Monitors.recordDiscardedIndexingCount(\"indexQueue\");\n                        });\n\n        int corePoolSize = 1;\n        maximumPoolSize = 2;\n        long keepAliveTime = 30L;\n        this.logExecutorService =\n                new ThreadPoolExecutor(\n                        corePoolSize,\n                        maximumPoolSize,\n                        keepAliveTime,\n                        TimeUnit.SECONDS,\n                        new LinkedBlockingQueue<>(workerQueueSize),\n                        (runnable, executor) -> {\n                            LOGGER.warn(\n                                    \"Request {} to async log dao discarded in executor {}\",\n                                    runnable,\n                                    executor);\n                            Monitors.recordDiscardedIndexingCount(\"logQueue\");\n                        });\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(this::flushBulkRequests, 60, 30, TimeUnit.SECONDS);\n        this.retryTemplate = retryTemplate;\n    }\n\n    @PreDestroy\n    private void shutdown() {\n        LOGGER.info(\"Starting graceful shutdown of executor service\");\n        shutdownExecutorService(logExecutorService);\n        shutdownExecutorService(executorService);\n    }\n\n    private void shutdownExecutorService(ExecutorService execService) {\n        try {\n            execService.shutdown();\n            if (execService.awaitTermination(30, TimeUnit.SECONDS)) {\n                LOGGER.debug(\"tasks completed, shutting down\");\n            } else {\n                LOGGER.warn(\"Forcing shutdown after waiting for 30 seconds\");\n                execService.shutdownNow();\n            }\n        } catch (InterruptedException ie) {\n            LOGGER.warn(\n                    \"Shutdown interrupted, invoking shutdownNow on scheduledThreadPoolExecutor for delay queue\");\n            execService.shutdownNow();\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    @Override\n    @PostConstruct\n    public void setup() throws Exception {\n        waitForHealthyCluster();\n\n        if (properties.isAutoIndexManagementEnabled()) {\n            createIndexesTemplates();\n            createWorkflowIndex();\n            createTaskIndex();\n        }\n    }\n\n    private void waitForHealthyCluster() throws Exception {\n        elasticSearchClient\n                .admin()\n                .cluster()\n                .prepareHealth()\n                .setWaitForGreenStatus()\n                .execute()\n                .get();\n    }\n\n    /** Initializes the indexes templates task_log, message and event, and mappings. */\n    private void createIndexesTemplates() {\n        try {\n            initIndexesTemplates();\n            updateIndexesNames();\n            Executors.newScheduledThreadPool(1)\n                    .scheduleAtFixedRate(this::updateIndexesNames, 0, 1, TimeUnit.HOURS);\n        } catch (Exception e) {\n            LOGGER.error(\"Error creating index templates\", e);\n        }\n    }\n\n    private void initIndexesTemplates() {\n        initIndexTemplate(LOG_DOC_TYPE);\n        initIndexTemplate(EVENT_DOC_TYPE);\n        initIndexTemplate(MSG_DOC_TYPE);\n    }\n\n    private void initIndexTemplate(String type) {\n        String template = \"template_\" + type;\n        GetIndexTemplatesResponse result =\n                elasticSearchClient\n                        .admin()\n                        .indices()\n                        .prepareGetTemplates(template)\n                        .execute()\n                        .actionGet();\n        if (result.getIndexTemplates().isEmpty()) {\n            LOGGER.info(\"Creating the index template '{}'\", template);\n            try {\n                String templateSource = loadTypeMappingSource(\"/\" + template + \".json\");\n                elasticSearchClient\n                        .admin()\n                        .indices()\n                        .preparePutTemplate(template)\n                        .setSource(templateSource.getBytes(), XContentType.JSON)\n                        .execute()\n                        .actionGet();\n            } catch (Exception e) {\n                LOGGER.error(\"Failed to init \" + template, e);\n            }\n        }\n    }\n\n    private void updateIndexesNames() {\n        logIndexName = updateIndexName(LOG_DOC_TYPE);\n        eventIndexName = updateIndexName(EVENT_DOC_TYPE);\n        messageIndexName = updateIndexName(MSG_DOC_TYPE);\n    }\n\n    private String updateIndexName(String type) {\n        String indexName =\n                this.indexPrefix + \"_\" + type + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n        createIndex(indexName);\n        return indexName;\n    }\n\n    private void createWorkflowIndex() {\n        createIndex(workflowIndexName);\n        addTypeMapping(workflowIndexName, WORKFLOW_DOC_TYPE, \"/mappings_docType_workflow.json\");\n    }\n\n    private void createTaskIndex() {\n        createIndex(taskIndexName);\n        addTypeMapping(taskIndexName, TASK_DOC_TYPE, \"/mappings_docType_task.json\");\n    }\n\n    private void createIndex(String indexName) {\n        try {\n            elasticSearchClient\n                    .admin()\n                    .indices()\n                    .prepareGetIndex()\n                    .addIndices(indexName)\n                    .execute()\n                    .actionGet();\n        } catch (IndexNotFoundException infe) {\n            try {\n                CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);\n                createIndexRequest.settings(\n                        Settings.builder()\n                                .put(\"index.number_of_shards\", properties.getIndexShardCount())\n                                .put(\n                                        \"index.number_of_replicas\",\n                                        properties.getIndexReplicasCount()));\n\n                elasticSearchClient.admin().indices().create(createIndexRequest).actionGet();\n            } catch (ResourceAlreadyExistsException done) {\n                LOGGER.error(\"Failed to update log index name: {}\", indexName, done);\n            }\n        }\n    }\n\n    private void addTypeMapping(String indexName, String type, String sourcePath) {\n        GetMappingsResponse getMappingsResponse =\n                elasticSearchClient\n                        .admin()\n                        .indices()\n                        .prepareGetMappings(indexName)\n                        .addTypes(type)\n                        .execute()\n                        .actionGet();\n        if (getMappingsResponse.mappings().isEmpty()) {\n            LOGGER.info(\"Adding the {} type mappings\", indexName);\n            try {\n                String source = loadTypeMappingSource(sourcePath);\n                elasticSearchClient\n                        .admin()\n                        .indices()\n                        .preparePutMapping(indexName)\n                        .setType(type)\n                        .setSource(source, XContentType.JSON)\n                        .execute()\n                        .actionGet();\n            } catch (Exception e) {\n                LOGGER.error(\"Failed to init index \" + indexName + \" mappings\", e);\n            }\n        }\n    }\n\n    @Override\n    public void indexWorkflow(WorkflowSummary workflow) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            String id = workflow.getWorkflowId();\n            byte[] doc = objectMapper.writeValueAsBytes(workflow);\n            String docType =\n                    StringUtils.isBlank(docTypeOverride) ? WORKFLOW_DOC_TYPE : docTypeOverride;\n\n            UpdateRequest req = buildUpdateRequest(id, doc, workflowIndexName, docType);\n            elasticSearchClient.update(req).actionGet();\n\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for indexing workflow: {}\",\n                    endTime - startTime,\n                    workflow.getWorkflowId());\n            Monitors.recordESIndexTime(\"index_workflow\", WORKFLOW_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"indexWorkflow\");\n            LOGGER.error(\"Failed to index workflow: {}\", workflow.getWorkflowId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncIndexWorkflow(WorkflowSummary workflow) {\n        return CompletableFuture.runAsync(() -> indexWorkflow(workflow), executorService);\n    }\n\n    @Override\n    public void indexTask(TaskSummary task) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            String id = task.getTaskId();\n            byte[] doc = objectMapper.writeValueAsBytes(task);\n            String docType = StringUtils.isBlank(docTypeOverride) ? TASK_DOC_TYPE : docTypeOverride;\n\n            UpdateRequest req = new UpdateRequest(taskIndexName, docType, id);\n            req.doc(doc, XContentType.JSON);\n            req.upsert(doc, XContentType.JSON);\n            indexObject(req, TASK_DOC_TYPE);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for  indexing task:{} in workflow: {}\",\n                    endTime - startTime,\n                    task.getTaskId(),\n                    task.getWorkflowId());\n            Monitors.recordESIndexTime(\"index_task\", TASK_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to index task: {}\", task.getTaskId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncIndexTask(TaskSummary task) {\n        return CompletableFuture.runAsync(() -> indexTask(task), executorService);\n    }\n\n    private void indexObject(UpdateRequest req, String docType) {\n        if (bulkRequests.get(docType) == null) {\n            bulkRequests.put(\n                    docType,\n                    new BulkRequests(\n                            System.currentTimeMillis(), elasticSearchClient.prepareBulk()));\n        }\n        bulkRequests.get(docType).getBulkRequestBuilder().add(req);\n        if (bulkRequests.get(docType).getBulkRequestBuilder().numberOfActions()\n                >= this.indexBatchSize) {\n            indexBulkRequest(docType);\n        }\n    }\n\n    private synchronized void indexBulkRequest(String docType) {\n        if (bulkRequests.get(docType).getBulkRequestBuilder() != null\n                && bulkRequests.get(docType).getBulkRequestBuilder().numberOfActions() > 0) {\n            updateWithRetry(bulkRequests.get(docType).getBulkRequestBuilder(), docType);\n            bulkRequests.put(\n                    docType,\n                    new BulkRequests(\n                            System.currentTimeMillis(), elasticSearchClient.prepareBulk()));\n        }\n    }\n\n    @Override\n    public void addTaskExecutionLogs(List<TaskExecLog> taskExecLogs) {\n        if (taskExecLogs.isEmpty()) {\n            return;\n        }\n\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            BulkRequestBuilderWrapper bulkRequestBuilder =\n                    new BulkRequestBuilderWrapper(elasticSearchClient.prepareBulk());\n            for (TaskExecLog log : taskExecLogs) {\n                String docType =\n                        StringUtils.isBlank(docTypeOverride) ? LOG_DOC_TYPE : docTypeOverride;\n                IndexRequest request = new IndexRequest(logIndexName, docType);\n                request.source(objectMapper.writeValueAsBytes(log), XContentType.JSON);\n                bulkRequestBuilder.add(request);\n            }\n            bulkRequestBuilder.execute().actionGet(5, TimeUnit.SECONDS);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\"Time taken {} for indexing taskExecutionLogs\", endTime - startTime);\n            Monitors.recordESIndexTime(\n                    \"index_task_execution_logs\", LOG_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"logQueue\", ((ThreadPoolExecutor) logExecutorService).getQueue().size());\n        } catch (Exception e) {\n            List<String> taskIds =\n                    taskExecLogs.stream().map(TaskExecLog::getTaskId).collect(Collectors.toList());\n            LOGGER.error(\"Failed to index task execution logs for tasks: {}\", taskIds, e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncAddTaskExecutionLogs(List<TaskExecLog> logs) {\n        return CompletableFuture.runAsync(() -> addTaskExecutionLogs(logs), logExecutorService);\n    }\n\n    @Override\n    public List<TaskExecLog> getTaskExecutionLogs(String taskId) {\n        try {\n            BoolQueryBuilder query = boolQueryBuilder(\"taskId='\" + taskId + \"'\", \"*\");\n\n            String docType = StringUtils.isBlank(docTypeOverride) ? LOG_DOC_TYPE : docTypeOverride;\n            final SearchRequestBuilder srb =\n                    elasticSearchClient\n                            .prepareSearch(logIndexPrefix + \"*\")\n                            .setQuery(query)\n                            .setTypes(docType)\n                            .setSize(properties.getTaskLogResultLimit())\n                            .addSort(SortBuilders.fieldSort(\"createdTime\").order(SortOrder.ASC));\n\n            return mapTaskExecLogsResponse(srb.execute().actionGet());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to get task execution logs for task: {}\", taskId, e);\n        }\n        return null;\n    }\n\n    private List<TaskExecLog> mapTaskExecLogsResponse(SearchResponse response) throws IOException {\n        SearchHit[] hits = response.getHits().getHits();\n        List<TaskExecLog> logs = new ArrayList<>(hits.length);\n        for (SearchHit hit : hits) {\n            String source = hit.getSourceAsString();\n            TaskExecLog tel = objectMapper.readValue(source, TaskExecLog.class);\n            logs.add(tel);\n        }\n        return logs;\n    }\n\n    @Override\n    public void addMessage(String queue, Message message) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            Map<String, Object> doc = new HashMap<>();\n            doc.put(\"messageId\", message.getId());\n            doc.put(\"payload\", message.getPayload());\n            doc.put(\"queue\", queue);\n            doc.put(\"created\", System.currentTimeMillis());\n\n            String docType = StringUtils.isBlank(docTypeOverride) ? MSG_DOC_TYPE : docTypeOverride;\n            UpdateRequest req = new UpdateRequest(messageIndexName, docType, message.getId());\n            req.doc(doc, XContentType.JSON);\n            req.upsert(doc, XContentType.JSON);\n            indexObject(req, MSG_DOC_TYPE);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for  indexing message: {}\",\n                    endTime - startTime,\n                    message.getId());\n            Monitors.recordESIndexTime(\"add_message\", MSG_DOC_TYPE, endTime - startTime);\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to index message: {}\", message.getId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncAddMessage(String queue, Message message) {\n        return CompletableFuture.runAsync(() -> addMessage(queue, message), executorService);\n    }\n\n    @Override\n    public List<Message> getMessages(String queue) {\n        try {\n            BoolQueryBuilder fq = boolQueryBuilder(\"queue='\" + queue + \"'\", \"*\");\n\n            String docType = StringUtils.isBlank(docTypeOverride) ? MSG_DOC_TYPE : docTypeOverride;\n            final SearchRequestBuilder srb =\n                    elasticSearchClient\n                            .prepareSearch(messageIndexPrefix + \"*\")\n                            .setQuery(fq)\n                            .setTypes(docType)\n                            .addSort(SortBuilders.fieldSort(\"created\").order(SortOrder.ASC));\n\n            return mapGetMessagesResponse(srb.execute().actionGet());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to get messages for queue: {}\", queue, e);\n        }\n        return null;\n    }\n\n    private List<Message> mapGetMessagesResponse(SearchResponse response) throws IOException {\n        SearchHit[] hits = response.getHits().getHits();\n        TypeFactory factory = TypeFactory.defaultInstance();\n        MapType type = factory.constructMapType(HashMap.class, String.class, String.class);\n        List<Message> messages = new ArrayList<>(hits.length);\n        for (SearchHit hit : hits) {\n            String source = hit.getSourceAsString();\n            Map<String, String> mapSource = objectMapper.readValue(source, type);\n            Message msg = new Message(mapSource.get(\"messageId\"), mapSource.get(\"payload\"), null);\n            messages.add(msg);\n        }\n        return messages;\n    }\n\n    @Override\n    public void addEventExecution(EventExecution eventExecution) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            byte[] doc = objectMapper.writeValueAsBytes(eventExecution);\n            String id =\n                    eventExecution.getName()\n                            + \".\"\n                            + eventExecution.getEvent()\n                            + \".\"\n                            + eventExecution.getMessageId()\n                            + \".\"\n                            + eventExecution.getId();\n            String docType =\n                    StringUtils.isBlank(docTypeOverride) ? EVENT_DOC_TYPE : docTypeOverride;\n            UpdateRequest req = buildUpdateRequest(id, doc, eventIndexName, docType);\n            indexObject(req, EVENT_DOC_TYPE);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for indexing event execution: {}\",\n                    endTime - startTime,\n                    eventExecution.getId());\n            Monitors.recordESIndexTime(\"add_event_execution\", EVENT_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"logQueue\", ((ThreadPoolExecutor) logExecutorService).getQueue().size());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to index event execution: {}\", eventExecution.getId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncAddEventExecution(EventExecution eventExecution) {\n        return CompletableFuture.runAsync(\n                () -> addEventExecution(eventExecution), logExecutorService);\n    }\n\n    @Override\n    public List<EventExecution> getEventExecutions(String event) {\n        try {\n            BoolQueryBuilder fq = boolQueryBuilder(\"event='\" + event + \"'\", \"*\");\n\n            String docType =\n                    StringUtils.isBlank(docTypeOverride) ? EVENT_DOC_TYPE : docTypeOverride;\n            final SearchRequestBuilder srb =\n                    elasticSearchClient\n                            .prepareSearch(eventIndexPrefix + \"*\")\n                            .setQuery(fq)\n                            .setTypes(docType)\n                            .addSort(SortBuilders.fieldSort(\"created\").order(SortOrder.ASC));\n\n            return mapEventExecutionsResponse(srb.execute().actionGet());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to get executions for event: {}\", event, e);\n        }\n        return null;\n    }\n\n    private List<EventExecution> mapEventExecutionsResponse(SearchResponse response)\n            throws IOException {\n        SearchHit[] hits = response.getHits().getHits();\n        List<EventExecution> executions = new ArrayList<>(hits.length);\n        for (SearchHit hit : hits) {\n            String source = hit.getSourceAsString();\n            EventExecution tel = objectMapper.readValue(source, EventExecution.class);\n            executions.add(tel);\n        }\n        return executions;\n    }\n\n    private void updateWithRetry(BulkRequestBuilderWrapper request, String docType) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            retryTemplate.execute(context -> request.execute().actionGet(5, TimeUnit.SECONDS));\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for indexing object of type: {}\", endTime - startTime, docType);\n            Monitors.recordESIndexTime(\"index_object\", docType, endTime - startTime);\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"index\");\n            LOGGER.error(\"Failed to index {} for requests\", request.numberOfActions(), e);\n        }\n    }\n\n    @Override\n    public SearchResult<String> searchWorkflows(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return search(query, start, count, sort, freeText, WORKFLOW_DOC_TYPE, true, String.class);\n    }\n\n    @Override\n    public SearchResult<WorkflowSummary> searchWorkflowSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return search(\n                query,\n                start,\n                count,\n                sort,\n                freeText,\n                WORKFLOW_DOC_TYPE,\n                false,\n                WorkflowSummary.class);\n    }\n\n    @Override\n    public long getWorkflowCount(String query, String freeText) {\n        return count(query, freeText, WORKFLOW_DOC_TYPE);\n    }\n\n    @Override\n    public SearchResult<String> searchTasks(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return search(query, start, count, sort, freeText, TASK_DOC_TYPE, true, String.class);\n    }\n\n    @Override\n    public SearchResult<TaskSummary> searchTaskSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        return search(query, start, count, sort, freeText, TASK_DOC_TYPE, false, TaskSummary.class);\n    }\n\n    @Override\n    public void removeWorkflow(String workflowId) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            DeleteRequest request =\n                    new DeleteRequest(workflowIndexName, WORKFLOW_DOC_TYPE, workflowId);\n            DeleteResponse response = elasticSearchClient.delete(request).actionGet();\n            if (response.getResult() == DocWriteResponse.Result.DELETED) {\n                LOGGER.error(\"Index removal failed - document not found by id: {}\", workflowId);\n            }\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for removing workflow: {}\", endTime - startTime, workflowId);\n            Monitors.recordESIndexTime(\"remove_workflow\", WORKFLOW_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Throwable e) {\n            LOGGER.error(\"Failed to remove workflow {} from index\", workflowId, e);\n            Monitors.error(CLASS_NAME, \"remove\");\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncRemoveWorkflow(String workflowId) {\n        return CompletableFuture.runAsync(() -> removeWorkflow(workflowId), executorService);\n    }\n\n    @Override\n    public void updateWorkflow(String workflowInstanceId, String[] keys, Object[] values) {\n        if (keys.length != values.length) {\n            throw new IllegalArgumentException(\"Number of keys and values do not match\");\n        }\n\n        long startTime = Instant.now().toEpochMilli();\n        UpdateRequest request =\n                new UpdateRequest(workflowIndexName, WORKFLOW_DOC_TYPE, workflowInstanceId);\n        Map<String, Object> source =\n                IntStream.range(0, keys.length)\n                        .boxed()\n                        .collect(Collectors.toMap(i -> keys[i], i -> values[i]));\n        request.doc(source);\n        LOGGER.debug(\n                \"Updating workflow {} in elasticsearch index: {}\",\n                workflowInstanceId,\n                workflowIndexName);\n        elasticSearchClient.update(request).actionGet();\n        long endTime = Instant.now().toEpochMilli();\n        LOGGER.debug(\n                \"Time taken {} for updating workflow: {}\", endTime - startTime, workflowInstanceId);\n        Monitors.recordESIndexTime(\"update_workflow\", WORKFLOW_DOC_TYPE, endTime - startTime);\n        Monitors.recordWorkerQueueSize(\n                \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncUpdateWorkflow(\n            String workflowInstanceId, String[] keys, Object[] values) {\n        return CompletableFuture.runAsync(\n                () -> updateWorkflow(workflowInstanceId, keys, values), executorService);\n    }\n\n    @Override\n    public void removeTask(String workflowId, String taskId) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            String docType = StringUtils.isBlank(docTypeOverride) ? TASK_DOC_TYPE : docTypeOverride;\n\n            SearchResult<String> taskSearchResult =\n                    searchTasks(\n                            String.format(\n                                    \"(taskId='%s') AND (workflowId='%s')\", taskId, workflowId),\n                            \"*\",\n                            0,\n                            1,\n                            null);\n\n            if (taskSearchResult.getTotalHits() == 0) {\n                LOGGER.error(\"Task: {} does not belong to workflow: {}\", taskId, workflowId);\n                Monitors.error(CLASS_NAME, \"removeTask\");\n                return;\n            }\n\n            DeleteRequest request = new DeleteRequest(taskIndexName, docType, taskId);\n            DeleteResponse response = elasticSearchClient.delete(request).actionGet();\n            long endTime = Instant.now().toEpochMilli();\n\n            if (response.getResult() != DocWriteResponse.Result.DELETED) {\n                LOGGER.error(\n                        \"Index removal failed - task not found by id: {} of workflow: {}\",\n                        taskId,\n                        workflowId);\n                Monitors.error(CLASS_NAME, \"removeTask\");\n                return;\n            }\n            LOGGER.debug(\n                    \"Time taken {} for removing task:{} of workflow: {}\",\n                    endTime - startTime,\n                    taskId,\n                    workflowId);\n            Monitors.recordESIndexTime(\"remove_task\", docType, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Failed to remove task: {} of workflow: {} from index\", taskId, workflowId, e);\n            Monitors.error(CLASS_NAME, \"removeTask\");\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncRemoveTask(String workflowId, String taskId) {\n        return CompletableFuture.runAsync(() -> removeTask(workflowId, taskId), executorService);\n    }\n\n    @Override\n    public void updateTask(String workflowId, String taskId, String[] keys, Object[] values) {\n        if (keys.length != values.length) {\n            throw new IllegalArgumentException(\"Number of keys and values do not match\");\n        }\n\n        long startTime = Instant.now().toEpochMilli();\n        String docType = StringUtils.isBlank(docTypeOverride) ? TASK_DOC_TYPE : docTypeOverride;\n\n        UpdateRequest request = new UpdateRequest(taskIndexName, docType, taskId);\n        Map<String, Object> source =\n                IntStream.range(0, keys.length)\n                        .boxed()\n                        .collect(Collectors.toMap(i -> keys[i], i -> values[i]));\n        request.doc(source);\n        LOGGER.debug(\n                \"Updating task: {} of workflow: {} in elasticsearch index: {}\",\n                taskId,\n                workflowId,\n                taskIndexName);\n        elasticSearchClient.update(request).actionGet();\n        long endTime = Instant.now().toEpochMilli();\n        LOGGER.debug(\n                \"Time taken {} for updating task: {} of workflow: {}\",\n                endTime - startTime,\n                taskId,\n                workflowId);\n        Monitors.recordESIndexTime(\"update_task\", docType, endTime - startTime);\n        Monitors.recordWorkerQueueSize(\n                \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncUpdateTask(\n            String workflowId, String taskId, String[] keys, Object[] values) {\n        return CompletableFuture.runAsync(\n                () -> updateTask(workflowId, taskId, keys, values), executorService);\n    }\n\n    @Override\n    public String get(String workflowInstanceId, String fieldToGet) {\n        String docType = StringUtils.isBlank(docTypeOverride) ? WORKFLOW_DOC_TYPE : docTypeOverride;\n        GetRequest request =\n                new GetRequest(workflowIndexName, docType, workflowInstanceId)\n                        .fetchSourceContext(\n                                new FetchSourceContext(\n                                        true, new String[] {fieldToGet}, Strings.EMPTY_ARRAY));\n        GetResponse response = elasticSearchClient.get(request).actionGet();\n\n        if (response.isExists()) {\n            Map<String, Object> sourceAsMap = response.getSourceAsMap();\n            if (sourceAsMap.get(fieldToGet) != null) {\n                return sourceAsMap.get(fieldToGet).toString();\n            }\n        }\n\n        LOGGER.debug(\n                \"Unable to find Workflow: {} in ElasticSearch index: {}.\",\n                workflowInstanceId,\n                workflowIndexName);\n        return null;\n    }\n\n    private long count(String structuredQuery, String freeTextQuery, String docType) {\n        try {\n            docType = StringUtils.isBlank(docTypeOverride) ? docType : docTypeOverride;\n            BoolQueryBuilder fq = boolQueryBuilder(structuredQuery, freeTextQuery);\n            // The count api has been removed from the Java api, use the search api instead and set\n            // size to 0.\n            final SearchRequestBuilder srb =\n                    elasticSearchClient\n                            .prepareSearch(getIndexName(docType))\n                            .setQuery(fq)\n                            .setTypes(docType)\n                            .storedFields(\"_id\")\n                            .setSize(0);\n            SearchResponse response = srb.get();\n            return response.getHits().getTotalHits();\n        } catch (ParserException e) {\n            throw new TransientException(e.getMessage(), e);\n        }\n    }\n\n    private <T> SearchResult<T> search(\n            String structuredQuery,\n            int start,\n            int size,\n            List<String> sortOptions,\n            String freeTextQuery,\n            String docType,\n            boolean idOnly,\n            Class<T> clazz) {\n        try {\n            docType = StringUtils.isBlank(docTypeOverride) ? docType : docTypeOverride;\n            BoolQueryBuilder fq = boolQueryBuilder(structuredQuery, freeTextQuery);\n            final SearchRequestBuilder srb =\n                    elasticSearchClient\n                            .prepareSearch(getIndexName(docType))\n                            .setQuery(fq)\n                            .setTypes(docType)\n                            .setFrom(start)\n                            .setSize(size);\n            if (idOnly) {\n                srb.storedFields(\"_id\");\n            }\n            addSortOptions(srb, sortOptions);\n            return mapSearchResult(srb.get(), idOnly, clazz);\n        } catch (ParserException e) {\n            throw new TransientException(e.getMessage(), e);\n        }\n    }\n\n    private void addSortOptions(SearchRequestBuilder srb, List<String> sortOptions) {\n        if (sortOptions != null) {\n            sortOptions.forEach(\n                    sortOption -> {\n                        SortOrder order = SortOrder.ASC;\n                        String field = sortOption;\n                        int indx = sortOption.indexOf(':');\n                        // Can't be 0, need the field name at-least\n                        if (indx > 0) {\n                            field = sortOption.substring(0, indx);\n                            order = SortOrder.valueOf(sortOption.substring(indx + 1));\n                        }\n                        srb.addSort(field, order);\n                    });\n        }\n    }\n\n    private <T> SearchResult<T> mapSearchResult(\n            SearchResponse response, boolean idOnly, Class<T> clazz) {\n        SearchHits searchHits = response.getHits();\n        long count = searchHits.getTotalHits();\n        List<T> result;\n        if (idOnly) {\n            result =\n                    Arrays.stream(searchHits.getHits())\n                            .map(hit -> clazz.cast(hit.getId()))\n                            .collect(Collectors.toList());\n        } else {\n            result =\n                    Arrays.stream(searchHits.getHits())\n                            .map(\n                                    hit -> {\n                                        try {\n                                            return objectMapper.readValue(\n                                                    hit.getSourceAsString(), clazz);\n                                        } catch (JsonProcessingException e) {\n                                            LOGGER.error(\n                                                    \"Failed to de-serialize elasticsearch from source: {}\",\n                                                    hit.getSourceAsString(),\n                                                    e);\n                                        }\n                                        return null;\n                                    })\n                            .collect(Collectors.toList());\n        }\n        return new SearchResult<>(count, result);\n    }\n\n    @Override\n    public List<String> searchArchivableWorkflows(String indexName, long archiveTtlDays) {\n        QueryBuilder q =\n                QueryBuilders.boolQuery()\n                        .must(\n                                QueryBuilders.rangeQuery(\"endTime\")\n                                        .lt(LocalDate.now().minusDays(archiveTtlDays).toString())\n                                        .gte(\n                                                LocalDate.now()\n                                                        .minusDays(archiveTtlDays)\n                                                        .minusDays(1)\n                                                        .toString()))\n                        .should(QueryBuilders.termQuery(\"status\", \"COMPLETED\"))\n                        .should(QueryBuilders.termQuery(\"status\", \"FAILED\"))\n                        .should(QueryBuilders.termQuery(\"status\", \"TIMED_OUT\"))\n                        .should(QueryBuilders.termQuery(\"status\", \"TERMINATED\"))\n                        .mustNot(QueryBuilders.existsQuery(\"archived\"))\n                        .minimumShouldMatch(1);\n        String docType = StringUtils.isBlank(docTypeOverride) ? WORKFLOW_DOC_TYPE : docTypeOverride;\n        SearchRequestBuilder s =\n                elasticSearchClient\n                        .prepareSearch(indexName)\n                        .setTypes(docType)\n                        .setQuery(q)\n                        .setSize(1000);\n        return extractSearchIds(s);\n    }\n\n    private UpdateRequest buildUpdateRequest(\n            String id, byte[] doc, String indexName, String docType) {\n        UpdateRequest req = new UpdateRequest(indexName, docType, id);\n        req.doc(doc, XContentType.JSON);\n        req.upsert(doc, XContentType.JSON);\n        req.retryOnConflict(UPDATE_REQUEST_RETRY_COUNT);\n        return req;\n    }\n\n    private List<String> extractSearchIds(SearchRequestBuilder s) {\n        SearchResponse response = s.execute().actionGet();\n        SearchHits hits = response.getHits();\n        List<String> ids = new LinkedList<>();\n        for (SearchHit hit : hits.getHits()) {\n            ids.add(hit.getId());\n        }\n        return ids;\n    }\n\n    /**\n     * Flush the buffers if bulk requests have not been indexed for the past {@link\n     * ElasticSearchProperties#getAsyncBufferFlushTimeout()} seconds. This is to prevent data loss\n     * in case the instance is terminated, while the buffer still holds documents to be indexed.\n     */\n    private void flushBulkRequests() {\n        bulkRequests.entrySet().stream()\n                .filter(\n                        entry ->\n                                (System.currentTimeMillis() - entry.getValue().getLastFlushTime())\n                                        >= asyncBufferFlushTimeout)\n                .filter(\n                        entry ->\n                                entry.getValue().getBulkRequestBuilder() != null\n                                        && entry.getValue()\n                                                        .getBulkRequestBuilder()\n                                                        .numberOfActions()\n                                                > 0)\n                .forEach(\n                        entry -> {\n                            LOGGER.debug(\n                                    \"Flushing bulk request buffer for type {}, size: {}\",\n                                    entry.getKey(),\n                                    entry.getValue().getBulkRequestBuilder().numberOfActions());\n                            indexBulkRequest(entry.getKey());\n                        });\n    }\n\n    private static class BulkRequests {\n\n        private long lastFlushTime;\n        private BulkRequestBuilderWrapper bulkRequestBuilder;\n\n        public long getLastFlushTime() {\n            return lastFlushTime;\n        }\n\n        public void setLastFlushTime(long lastFlushTime) {\n            this.lastFlushTime = lastFlushTime;\n        }\n\n        public BulkRequestBuilderWrapper getBulkRequestBuilder() {\n            return bulkRequestBuilder;\n        }\n\n        public void setBulkRequestBuilder(BulkRequestBuilder bulkRequestBuilder) {\n            this.bulkRequestBuilder = new BulkRequestBuilderWrapper(bulkRequestBuilder);\n        }\n\n        BulkRequests(long lastFlushTime, BulkRequestBuilder bulkRequestBuilder) {\n            this.lastFlushTime = lastFlushTime;\n            this.bulkRequestBuilder = new BulkRequestBuilderWrapper(bulkRequestBuilder);\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/index/ElasticSearchRestDAOV6.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.text.SimpleDateFormat;\nimport java.time.Instant;\nimport java.time.LocalDate;\nimport java.util.*;\nimport java.util.concurrent.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport javax.annotation.PostConstruct;\nimport javax.annotation.PreDestroy;\n\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.entity.ContentType;\nimport org.apache.http.nio.entity.NByteArrayEntity;\nimport org.apache.http.nio.entity.NStringEntity;\nimport org.apache.http.util.EntityUtils;\nimport org.elasticsearch.action.DocWriteResponse;\nimport org.elasticsearch.action.bulk.BulkRequest;\nimport org.elasticsearch.action.delete.DeleteRequest;\nimport org.elasticsearch.action.delete.DeleteResponse;\nimport org.elasticsearch.action.get.GetRequest;\nimport org.elasticsearch.action.get.GetResponse;\nimport org.elasticsearch.action.index.IndexRequest;\nimport org.elasticsearch.action.search.SearchRequest;\nimport org.elasticsearch.action.search.SearchResponse;\nimport org.elasticsearch.action.update.UpdateRequest;\nimport org.elasticsearch.client.*;\nimport org.elasticsearch.client.core.CountRequest;\nimport org.elasticsearch.client.core.CountResponse;\nimport org.elasticsearch.common.xcontent.XContentType;\nimport org.elasticsearch.index.query.BoolQueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilders;\nimport org.elasticsearch.search.SearchHit;\nimport org.elasticsearch.search.SearchHits;\nimport org.elasticsearch.search.builder.SearchSourceBuilder;\nimport org.elasticsearch.search.sort.FieldSortBuilder;\nimport org.elasticsearch.search.sort.SortOrder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.retry.support.RetryTemplate;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.core.exception.NonTransientException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.IndexDAO;\nimport com.netflix.conductor.es6.config.ElasticSearchProperties;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ParserException;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.fasterxml.jackson.databind.type.MapType;\nimport com.fasterxml.jackson.databind.type.TypeFactory;\n\n@Trace\npublic class ElasticSearchRestDAOV6 extends ElasticSearchBaseDAO implements IndexDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ElasticSearchRestDAOV6.class);\n\n    private static final int CORE_POOL_SIZE = 6;\n    private static final long KEEP_ALIVE_TIME = 1L;\n\n    private static final String WORKFLOW_DOC_TYPE = \"workflow\";\n    private static final String TASK_DOC_TYPE = \"task\";\n    private static final String LOG_DOC_TYPE = \"task_log\";\n    private static final String EVENT_DOC_TYPE = \"event\";\n    private static final String MSG_DOC_TYPE = \"message\";\n\n    private static final TimeZone GMT = TimeZone.getTimeZone(\"GMT\");\n    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(\"yyyyMMWW\");\n\n    private @interface HttpMethod {\n\n        String GET = \"GET\";\n        String POST = \"POST\";\n        String PUT = \"PUT\";\n        String HEAD = \"HEAD\";\n    }\n\n    private static final String className = ElasticSearchRestDAOV6.class.getSimpleName();\n\n    private final String workflowIndexName;\n    private final String taskIndexName;\n    private final String eventIndexPrefix;\n    private String eventIndexName;\n    private final String messageIndexPrefix;\n    private String messageIndexName;\n    private String logIndexName;\n    private final String logIndexPrefix;\n    private final String docTypeOverride;\n\n    private final String clusterHealthColor;\n    private final ObjectMapper objectMapper;\n    private final RestHighLevelClient elasticSearchClient;\n    private final RestClient elasticSearchAdminClient;\n    private final ExecutorService executorService;\n    private final ExecutorService logExecutorService;\n    private final ConcurrentHashMap<String, BulkRequests> bulkRequests;\n    private final int indexBatchSize;\n    private final long asyncBufferFlushTimeout;\n    private final ElasticSearchProperties properties;\n\n    private final RetryTemplate retryTemplate;\n\n    static {\n        SIMPLE_DATE_FORMAT.setTimeZone(GMT);\n    }\n\n    public ElasticSearchRestDAOV6(\n            RestClientBuilder restClientBuilder,\n            RetryTemplate retryTemplate,\n            ElasticSearchProperties properties,\n            ObjectMapper objectMapper) {\n\n        this.objectMapper = objectMapper;\n        this.elasticSearchAdminClient = restClientBuilder.build();\n        this.elasticSearchClient = new RestHighLevelClient(restClientBuilder);\n        this.clusterHealthColor = properties.getClusterHealthColor();\n        this.bulkRequests = new ConcurrentHashMap<>();\n        this.indexBatchSize = properties.getIndexBatchSize();\n        this.asyncBufferFlushTimeout = properties.getAsyncBufferFlushTimeout().toMillis();\n        this.properties = properties;\n\n        this.indexPrefix = properties.getIndexPrefix();\n        if (!properties.isAutoIndexManagementEnabled()\n                && StringUtils.isNotBlank(properties.getDocumentTypeOverride())) {\n            docTypeOverride = properties.getDocumentTypeOverride();\n        } else {\n            docTypeOverride = \"\";\n        }\n\n        this.workflowIndexName = getIndexName(WORKFLOW_DOC_TYPE);\n        this.taskIndexName = getIndexName(TASK_DOC_TYPE);\n        this.logIndexPrefix = this.indexPrefix + \"_\" + LOG_DOC_TYPE;\n        this.messageIndexPrefix = this.indexPrefix + \"_\" + MSG_DOC_TYPE;\n        this.eventIndexPrefix = this.indexPrefix + \"_\" + EVENT_DOC_TYPE;\n        int workerQueueSize = properties.getAsyncWorkerQueueSize();\n        int maximumPoolSize = properties.getAsyncMaxPoolSize();\n\n        // Set up a workerpool for performing async operations.\n        this.executorService =\n                new ThreadPoolExecutor(\n                        CORE_POOL_SIZE,\n                        maximumPoolSize,\n                        KEEP_ALIVE_TIME,\n                        TimeUnit.MINUTES,\n                        new LinkedBlockingQueue<>(workerQueueSize),\n                        (runnable, executor) -> {\n                            LOGGER.warn(\n                                    \"Request  {} to async dao discarded in executor {}\",\n                                    runnable,\n                                    executor);\n                            Monitors.recordDiscardedIndexingCount(\"indexQueue\");\n                        });\n\n        // Set up a workerpool for performing async operations for task_logs, event_executions,\n        // message\n        int corePoolSize = 1;\n        maximumPoolSize = 2;\n        long keepAliveTime = 30L;\n        this.logExecutorService =\n                new ThreadPoolExecutor(\n                        corePoolSize,\n                        maximumPoolSize,\n                        keepAliveTime,\n                        TimeUnit.SECONDS,\n                        new LinkedBlockingQueue<>(workerQueueSize),\n                        (runnable, executor) -> {\n                            LOGGER.warn(\n                                    \"Request {} to async log dao discarded in executor {}\",\n                                    runnable,\n                                    executor);\n                            Monitors.recordDiscardedIndexingCount(\"logQueue\");\n                        });\n\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleAtFixedRate(this::flushBulkRequests, 60, 30, TimeUnit.SECONDS);\n        this.retryTemplate = retryTemplate;\n    }\n\n    @PreDestroy\n    private void shutdown() {\n        LOGGER.info(\"Gracefully shutdown executor service\");\n        shutdownExecutorService(logExecutorService);\n        shutdownExecutorService(executorService);\n    }\n\n    private void shutdownExecutorService(ExecutorService execService) {\n        try {\n            execService.shutdown();\n            if (execService.awaitTermination(30, TimeUnit.SECONDS)) {\n                LOGGER.debug(\"tasks completed, shutting down\");\n            } else {\n                LOGGER.warn(\"Forcing shutdown after waiting for 30 seconds\");\n                execService.shutdownNow();\n            }\n        } catch (InterruptedException ie) {\n            LOGGER.warn(\n                    \"Shutdown interrupted, invoking shutdownNow on scheduledThreadPoolExecutor for delay queue\");\n            execService.shutdownNow();\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    @Override\n    @PostConstruct\n    public void setup() throws Exception {\n        waitForHealthyCluster();\n\n        if (properties.isAutoIndexManagementEnabled()) {\n            createIndexesTemplates();\n            createWorkflowIndex();\n            createTaskIndex();\n        }\n    }\n\n    private void createIndexesTemplates() {\n        try {\n            initIndexesTemplates();\n            updateIndexesNames();\n            Executors.newScheduledThreadPool(1)\n                    .scheduleAtFixedRate(this::updateIndexesNames, 0, 1, TimeUnit.HOURS);\n        } catch (Exception e) {\n            LOGGER.error(\"Error creating index templates!\", e);\n        }\n    }\n\n    private void initIndexesTemplates() {\n        initIndexTemplate(LOG_DOC_TYPE);\n        initIndexTemplate(EVENT_DOC_TYPE);\n        initIndexTemplate(MSG_DOC_TYPE);\n    }\n\n    /** Initializes the index with the required templates and mappings. */\n    private void initIndexTemplate(String type) {\n        String template = \"template_\" + type;\n        try {\n            if (doesResourceNotExist(\"/_template/\" + template)) {\n                LOGGER.info(\"Creating the index template '\" + template + \"'\");\n                InputStream stream =\n                        ElasticSearchDAOV6.class.getResourceAsStream(\"/\" + template + \".json\");\n                byte[] templateSource = IOUtils.toByteArray(stream);\n\n                HttpEntity entity =\n                        new NByteArrayEntity(templateSource, ContentType.APPLICATION_JSON);\n                elasticSearchAdminClient.performRequest(\n                        HttpMethod.PUT, \"/_template/\" + template, Collections.emptyMap(), entity);\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to init \" + template, e);\n        }\n    }\n\n    private void updateIndexesNames() {\n        logIndexName = updateIndexName(LOG_DOC_TYPE);\n        eventIndexName = updateIndexName(EVENT_DOC_TYPE);\n        messageIndexName = updateIndexName(MSG_DOC_TYPE);\n    }\n\n    private String updateIndexName(String type) {\n        String indexName =\n                this.indexPrefix + \"_\" + type + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n        try {\n            addIndex(indexName);\n            return indexName;\n        } catch (IOException e) {\n            LOGGER.error(\"Failed to update log index name: {}\", indexName, e);\n            throw new NonTransientException(\"Failed to update log index name: \" + indexName, e);\n        }\n    }\n\n    private void createWorkflowIndex() {\n        String indexName = getIndexName(WORKFLOW_DOC_TYPE);\n        try {\n            addIndex(indexName);\n        } catch (IOException e) {\n            LOGGER.error(\"Failed to initialize index '{}'\", indexName, e);\n        }\n        try {\n            addMappingToIndex(indexName, WORKFLOW_DOC_TYPE, \"/mappings_docType_workflow.json\");\n        } catch (IOException e) {\n            LOGGER.error(\"Failed to add {} mapping\", WORKFLOW_DOC_TYPE);\n        }\n    }\n\n    private void createTaskIndex() {\n        String indexName = getIndexName(TASK_DOC_TYPE);\n        try {\n            addIndex(indexName);\n        } catch (IOException e) {\n            LOGGER.error(\"Failed to initialize index '{}'\", indexName, e);\n        }\n        try {\n            addMappingToIndex(indexName, TASK_DOC_TYPE, \"/mappings_docType_task.json\");\n        } catch (IOException e) {\n            LOGGER.error(\"Failed to add {} mapping\", TASK_DOC_TYPE);\n        }\n    }\n\n    /**\n     * Waits for the ES cluster to become green.\n     *\n     * @throws Exception If there is an issue connecting with the ES cluster.\n     */\n    private void waitForHealthyCluster() throws Exception {\n        Map<String, String> params = new HashMap<>();\n        params.put(\"wait_for_status\", this.clusterHealthColor);\n        params.put(\"timeout\", \"30s\");\n\n        elasticSearchAdminClient.performRequest(\"GET\", \"/_cluster/health\", params);\n    }\n\n    /**\n     * Adds an index to elasticsearch if it does not exist.\n     *\n     * @param index The name of the index to create.\n     * @throws IOException If an error occurred during requests to ES.\n     */\n    private void addIndex(final String index) throws IOException {\n\n        LOGGER.info(\"Adding index '{}'...\", index);\n\n        String resourcePath = \"/\" + index;\n\n        if (doesResourceNotExist(resourcePath)) {\n\n            try {\n                ObjectNode setting = objectMapper.createObjectNode();\n                ObjectNode indexSetting = objectMapper.createObjectNode();\n\n                indexSetting.put(\"number_of_shards\", properties.getIndexShardCount());\n                indexSetting.put(\"number_of_replicas\", properties.getIndexReplicasCount());\n\n                setting.set(\"index\", indexSetting);\n\n                elasticSearchAdminClient.performRequest(\n                        HttpMethod.PUT,\n                        resourcePath,\n                        Collections.emptyMap(),\n                        new NStringEntity(setting.toString(), ContentType.APPLICATION_JSON));\n                LOGGER.info(\"Added '{}' index\", index);\n            } catch (ResponseException e) {\n\n                boolean errorCreatingIndex = true;\n\n                Response errorResponse = e.getResponse();\n                if (errorResponse.getStatusLine().getStatusCode() == HttpStatus.SC_BAD_REQUEST) {\n                    JsonNode root =\n                            objectMapper.readTree(EntityUtils.toString(errorResponse.getEntity()));\n                    String errorCode = root.get(\"error\").get(\"type\").asText();\n                    if (\"index_already_exists_exception\".equals(errorCode)) {\n                        errorCreatingIndex = false;\n                    }\n                }\n\n                if (errorCreatingIndex) {\n                    throw e;\n                }\n            }\n        } else {\n            LOGGER.info(\"Index '{}' already exists\", index);\n        }\n    }\n\n    /**\n     * Adds a mapping type to an index if it does not exist.\n     *\n     * @param index The name of the index.\n     * @param mappingType The name of the mapping type.\n     * @param mappingFilename The name of the mapping file to use to add the mapping if it does not\n     *     exist.\n     * @throws IOException If an error occurred during requests to ES.\n     */\n    private void addMappingToIndex(\n            final String index, final String mappingType, final String mappingFilename)\n            throws IOException {\n\n        LOGGER.info(\"Adding '{}' mapping to index '{}'...\", mappingType, index);\n\n        String resourcePath = \"/\" + index + \"/_mapping/\" + mappingType;\n\n        if (doesResourceNotExist(resourcePath)) {\n            HttpEntity entity =\n                    new NByteArrayEntity(\n                            loadTypeMappingSource(mappingFilename).getBytes(),\n                            ContentType.APPLICATION_JSON);\n            elasticSearchAdminClient.performRequest(\n                    HttpMethod.PUT, resourcePath, Collections.emptyMap(), entity);\n            LOGGER.info(\"Added '{}' mapping\", mappingType);\n        } else {\n            LOGGER.info(\"Mapping '{}' already exists\", mappingType);\n        }\n    }\n\n    /**\n     * Determines whether a resource exists in ES. This will call a GET method to a particular path\n     * and return true if status 200; false otherwise.\n     *\n     * @param resourcePath The path of the resource to get.\n     * @return True if it exists; false otherwise.\n     * @throws IOException If an error occurred during requests to ES.\n     */\n    public boolean doesResourceExist(final String resourcePath) throws IOException {\n        Response response = elasticSearchAdminClient.performRequest(HttpMethod.HEAD, resourcePath);\n        return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;\n    }\n\n    /**\n     * The inverse of doesResourceExist.\n     *\n     * @param resourcePath The path of the resource to check.\n     * @return True if it does not exist; false otherwise.\n     * @throws IOException If an error occurred during requests to ES.\n     */\n    public boolean doesResourceNotExist(final String resourcePath) throws IOException {\n        return !doesResourceExist(resourcePath);\n    }\n\n    @Override\n    public void indexWorkflow(WorkflowSummary workflow) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            String workflowId = workflow.getWorkflowId();\n            byte[] docBytes = objectMapper.writeValueAsBytes(workflow);\n            String docType =\n                    StringUtils.isBlank(docTypeOverride) ? WORKFLOW_DOC_TYPE : docTypeOverride;\n\n            IndexRequest request = new IndexRequest(workflowIndexName, docType, workflowId);\n            request.source(docBytes, XContentType.JSON);\n            elasticSearchClient.index(request, RequestOptions.DEFAULT);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for indexing workflow: {}\", endTime - startTime, workflowId);\n            Monitors.recordESIndexTime(\"index_workflow\", WORKFLOW_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Exception e) {\n            Monitors.error(className, \"indexWorkflow\");\n            LOGGER.error(\"Failed to index workflow: {}\", workflow.getWorkflowId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncIndexWorkflow(WorkflowSummary workflow) {\n        return CompletableFuture.runAsync(() -> indexWorkflow(workflow), executorService);\n    }\n\n    @Override\n    public void indexTask(TaskSummary task) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            String taskId = task.getTaskId();\n            String docType = StringUtils.isBlank(docTypeOverride) ? TASK_DOC_TYPE : docTypeOverride;\n\n            indexObject(taskIndexName, docType, taskId, task);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for  indexing task:{} in workflow: {}\",\n                    endTime - startTime,\n                    taskId,\n                    task.getWorkflowId());\n            Monitors.recordESIndexTime(\"index_task\", TASK_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to index task: {}\", task.getTaskId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncIndexTask(TaskSummary task) {\n        return CompletableFuture.runAsync(() -> indexTask(task), executorService);\n    }\n\n    @Override\n    public void addTaskExecutionLogs(List<TaskExecLog> taskExecLogs) {\n        if (taskExecLogs.isEmpty()) {\n            return;\n        }\n\n        long startTime = Instant.now().toEpochMilli();\n        BulkRequest bulkRequest = new BulkRequest();\n        for (TaskExecLog log : taskExecLogs) {\n\n            byte[] docBytes;\n            try {\n                docBytes = objectMapper.writeValueAsBytes(log);\n            } catch (JsonProcessingException e) {\n                LOGGER.error(\"Failed to convert task log to JSON for task {}\", log.getTaskId());\n                continue;\n            }\n\n            String docType = StringUtils.isBlank(docTypeOverride) ? LOG_DOC_TYPE : docTypeOverride;\n            IndexRequest request = new IndexRequest(logIndexName, docType);\n            request.source(docBytes, XContentType.JSON);\n            bulkRequest.add(request);\n        }\n\n        try {\n            elasticSearchClient.bulk(bulkRequest, RequestOptions.DEFAULT);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\"Time taken {} for indexing taskExecutionLogs\", endTime - startTime);\n            Monitors.recordESIndexTime(\n                    \"index_task_execution_logs\", LOG_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"logQueue\", ((ThreadPoolExecutor) logExecutorService).getQueue().size());\n        } catch (Exception e) {\n            List<String> taskIds =\n                    taskExecLogs.stream().map(TaskExecLog::getTaskId).collect(Collectors.toList());\n            LOGGER.error(\"Failed to index task execution logs for tasks: {}\", taskIds, e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncAddTaskExecutionLogs(List<TaskExecLog> logs) {\n        return CompletableFuture.runAsync(() -> addTaskExecutionLogs(logs), logExecutorService);\n    }\n\n    @Override\n    public List<TaskExecLog> getTaskExecutionLogs(String taskId) {\n        try {\n            BoolQueryBuilder query = boolQueryBuilder(\"taskId='\" + taskId + \"'\", \"*\");\n\n            // Create the searchObjectIdsViaExpression source\n            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();\n            searchSourceBuilder.query(query);\n            searchSourceBuilder.sort(new FieldSortBuilder(\"createdTime\").order(SortOrder.ASC));\n            searchSourceBuilder.size(properties.getTaskLogResultLimit());\n\n            // Generate the actual request to send to ES.\n            String docType = StringUtils.isBlank(docTypeOverride) ? LOG_DOC_TYPE : docTypeOverride;\n            SearchRequest searchRequest = new SearchRequest(logIndexPrefix + \"*\");\n            searchRequest.types(docType);\n            searchRequest.source(searchSourceBuilder);\n\n            SearchResponse response = elasticSearchClient.search(searchRequest);\n\n            return mapTaskExecLogsResponse(response);\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to get task execution logs for task: {}\", taskId, e);\n        }\n        return null;\n    }\n\n    private List<TaskExecLog> mapTaskExecLogsResponse(SearchResponse response) throws IOException {\n        SearchHit[] hits = response.getHits().getHits();\n        List<TaskExecLog> logs = new ArrayList<>(hits.length);\n        for (SearchHit hit : hits) {\n            String source = hit.getSourceAsString();\n            TaskExecLog tel = objectMapper.readValue(source, TaskExecLog.class);\n            logs.add(tel);\n        }\n        return logs;\n    }\n\n    @Override\n    public List<Message> getMessages(String queue) {\n        try {\n            BoolQueryBuilder query = boolQueryBuilder(\"queue='\" + queue + \"'\", \"*\");\n\n            // Create the searchObjectIdsViaExpression source\n            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();\n            searchSourceBuilder.query(query);\n            searchSourceBuilder.sort(new FieldSortBuilder(\"created\").order(SortOrder.ASC));\n\n            // Generate the actual request to send to ES.\n            String docType = StringUtils.isBlank(docTypeOverride) ? MSG_DOC_TYPE : docTypeOverride;\n            SearchRequest searchRequest = new SearchRequest(messageIndexPrefix + \"*\");\n            searchRequest.types(docType);\n            searchRequest.source(searchSourceBuilder);\n\n            SearchResponse response = elasticSearchClient.search(searchRequest);\n            return mapGetMessagesResponse(response);\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to get messages for queue: {}\", queue, e);\n        }\n        return null;\n    }\n\n    private List<Message> mapGetMessagesResponse(SearchResponse response) throws IOException {\n        SearchHit[] hits = response.getHits().getHits();\n        TypeFactory factory = TypeFactory.defaultInstance();\n        MapType type = factory.constructMapType(HashMap.class, String.class, String.class);\n        List<Message> messages = new ArrayList<>(hits.length);\n        for (SearchHit hit : hits) {\n            String source = hit.getSourceAsString();\n            Map<String, String> mapSource = objectMapper.readValue(source, type);\n            Message msg = new Message(mapSource.get(\"messageId\"), mapSource.get(\"payload\"), null);\n            messages.add(msg);\n        }\n        return messages;\n    }\n\n    @Override\n    public List<EventExecution> getEventExecutions(String event) {\n        try {\n            BoolQueryBuilder query = boolQueryBuilder(\"event='\" + event + \"'\", \"*\");\n\n            // Create the searchObjectIdsViaExpression source\n            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();\n            searchSourceBuilder.query(query);\n            searchSourceBuilder.sort(new FieldSortBuilder(\"created\").order(SortOrder.ASC));\n\n            // Generate the actual request to send to ES.\n            String docType =\n                    StringUtils.isBlank(docTypeOverride) ? EVENT_DOC_TYPE : docTypeOverride;\n            SearchRequest searchRequest = new SearchRequest(eventIndexPrefix + \"*\");\n            searchRequest.types(docType);\n            searchRequest.source(searchSourceBuilder);\n\n            SearchResponse response = elasticSearchClient.search(searchRequest);\n\n            return mapEventExecutionsResponse(response);\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to get executions for event: {}\", event, e);\n        }\n        return null;\n    }\n\n    private List<EventExecution> mapEventExecutionsResponse(SearchResponse response)\n            throws IOException {\n        SearchHit[] hits = response.getHits().getHits();\n        List<EventExecution> executions = new ArrayList<>(hits.length);\n        for (SearchHit hit : hits) {\n            String source = hit.getSourceAsString();\n            EventExecution tel = objectMapper.readValue(source, EventExecution.class);\n            executions.add(tel);\n        }\n        return executions;\n    }\n\n    @Override\n    public void addMessage(String queue, Message message) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            Map<String, Object> doc = new HashMap<>();\n            doc.put(\"messageId\", message.getId());\n            doc.put(\"payload\", message.getPayload());\n            doc.put(\"queue\", queue);\n            doc.put(\"created\", System.currentTimeMillis());\n\n            String docType = StringUtils.isBlank(docTypeOverride) ? MSG_DOC_TYPE : docTypeOverride;\n            indexObject(messageIndexName, docType, doc);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for  indexing message: {}\",\n                    endTime - startTime,\n                    message.getId());\n            Monitors.recordESIndexTime(\"add_message\", MSG_DOC_TYPE, endTime - startTime);\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to index message: {}\", message.getId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncAddMessage(String queue, Message message) {\n        return CompletableFuture.runAsync(() -> addMessage(queue, message), executorService);\n    }\n\n    @Override\n    public void addEventExecution(EventExecution eventExecution) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            String id =\n                    eventExecution.getName()\n                            + \".\"\n                            + eventExecution.getEvent()\n                            + \".\"\n                            + eventExecution.getMessageId()\n                            + \".\"\n                            + eventExecution.getId();\n\n            String docType =\n                    StringUtils.isBlank(docTypeOverride) ? EVENT_DOC_TYPE : docTypeOverride;\n            indexObject(eventIndexName, docType, id, eventExecution);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for indexing event execution: {}\",\n                    endTime - startTime,\n                    eventExecution.getId());\n            Monitors.recordESIndexTime(\"add_event_execution\", EVENT_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"logQueue\", ((ThreadPoolExecutor) logExecutorService).getQueue().size());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to index event execution: {}\", eventExecution.getId(), e);\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncAddEventExecution(EventExecution eventExecution) {\n        return CompletableFuture.runAsync(\n                () -> addEventExecution(eventExecution), logExecutorService);\n    }\n\n    @Override\n    public SearchResult<String> searchWorkflows(\n            String query, String freeText, int start, int count, List<String> sort) {\n        try {\n            return searchObjectsViaExpression(\n                    query, start, count, sort, freeText, WORKFLOW_DOC_TYPE, true, String.class);\n        } catch (Exception e) {\n            throw new TransientException(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public SearchResult<WorkflowSummary> searchWorkflowSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        try {\n            return searchObjectsViaExpression(\n                    query,\n                    start,\n                    count,\n                    sort,\n                    freeText,\n                    WORKFLOW_DOC_TYPE,\n                    false,\n                    WorkflowSummary.class);\n        } catch (Exception e) {\n            throw new TransientException(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public SearchResult<String> searchTasks(\n            String query, String freeText, int start, int count, List<String> sort) {\n        try {\n            return searchObjectsViaExpression(\n                    query, start, count, sort, freeText, TASK_DOC_TYPE, true, String.class);\n        } catch (Exception e) {\n            throw new TransientException(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public SearchResult<TaskSummary> searchTaskSummary(\n            String query, String freeText, int start, int count, List<String> sort) {\n        try {\n            return searchObjectsViaExpression(\n                    query, start, count, sort, freeText, TASK_DOC_TYPE, false, TaskSummary.class);\n        } catch (Exception e) {\n            throw new TransientException(e.getMessage(), e);\n        }\n    }\n\n    @Override\n    public void removeWorkflow(String workflowId) {\n        long startTime = Instant.now().toEpochMilli();\n        String docType = StringUtils.isBlank(docTypeOverride) ? WORKFLOW_DOC_TYPE : docTypeOverride;\n        DeleteRequest request = new DeleteRequest(workflowIndexName, docType, workflowId);\n\n        try {\n            DeleteResponse response = elasticSearchClient.delete(request);\n\n            if (response.getResult() == DocWriteResponse.Result.NOT_FOUND) {\n                LOGGER.error(\"Index removal failed - document not found by id: {}\", workflowId);\n            }\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for removing workflow: {}\", endTime - startTime, workflowId);\n            Monitors.recordESIndexTime(\"remove_workflow\", WORKFLOW_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (IOException e) {\n            LOGGER.error(\"Failed to remove workflow {} from index\", workflowId, e);\n            Monitors.error(className, \"remove\");\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncRemoveWorkflow(String workflowId) {\n        return CompletableFuture.runAsync(() -> removeWorkflow(workflowId), executorService);\n    }\n\n    @Override\n    public void updateWorkflow(String workflowInstanceId, String[] keys, Object[] values) {\n        try {\n            if (keys.length != values.length) {\n                throw new IllegalArgumentException(\"Number of keys and values do not match\");\n            }\n\n            long startTime = Instant.now().toEpochMilli();\n            String docType =\n                    StringUtils.isBlank(docTypeOverride) ? WORKFLOW_DOC_TYPE : docTypeOverride;\n            UpdateRequest request =\n                    new UpdateRequest(workflowIndexName, docType, workflowInstanceId);\n            Map<String, Object> source =\n                    IntStream.range(0, keys.length)\n                            .boxed()\n                            .collect(Collectors.toMap(i -> keys[i], i -> values[i]));\n            request.doc(source);\n\n            LOGGER.debug(\"Updating workflow {} with {}\", workflowInstanceId, source);\n            elasticSearchClient.update(request, RequestOptions.DEFAULT);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for updating workflow: {}\",\n                    endTime - startTime,\n                    workflowInstanceId);\n            Monitors.recordESIndexTime(\"update_workflow\", WORKFLOW_DOC_TYPE, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to update workflow {}\", workflowInstanceId, e);\n            Monitors.error(className, \"update\");\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncUpdateWorkflow(\n            String workflowInstanceId, String[] keys, Object[] values) {\n        return CompletableFuture.runAsync(\n                () -> updateWorkflow(workflowInstanceId, keys, values), executorService);\n    }\n\n    @Override\n    public void removeTask(String workflowId, String taskId) {\n        long startTime = Instant.now().toEpochMilli();\n        String docType = StringUtils.isBlank(docTypeOverride) ? TASK_DOC_TYPE : docTypeOverride;\n\n        SearchResult<String> taskSearchResult =\n                searchTasks(\n                        String.format(\"(taskId='%s') AND (workflowId='%s')\", taskId, workflowId),\n                        \"*\",\n                        0,\n                        1,\n                        null);\n\n        if (taskSearchResult.getTotalHits() == 0) {\n            LOGGER.error(\"Task: {} does not belong to workflow: {}\", taskId, workflowId);\n            Monitors.error(className, \"removeTask\");\n            return;\n        }\n\n        DeleteRequest request = new DeleteRequest(taskIndexName, docType, taskId);\n\n        try {\n            DeleteResponse response = elasticSearchClient.delete(request);\n\n            if (response.getResult() != DocWriteResponse.Result.DELETED) {\n                LOGGER.error(\"Index removal failed - task not found by id: {}\", workflowId);\n                Monitors.error(className, \"removeTask\");\n                return;\n            }\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for removing task:{} of workflow: {}\",\n                    endTime - startTime,\n                    taskId,\n                    workflowId);\n            Monitors.recordESIndexTime(\"remove_task\", docType, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (IOException e) {\n            LOGGER.error(\n                    \"Failed to remove task {} of workflow: {} from index\", taskId, workflowId, e);\n            Monitors.error(className, \"removeTask\");\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncRemoveTask(String workflowId, String taskId) {\n        return CompletableFuture.runAsync(() -> removeTask(workflowId, taskId), executorService);\n    }\n\n    @Override\n    public void updateTask(String workflowId, String taskId, String[] keys, Object[] values) {\n        try {\n            if (keys.length != values.length) {\n                throw new IllegalArgumentException(\"Number of keys and values do not match\");\n            }\n\n            long startTime = Instant.now().toEpochMilli();\n            String docType = StringUtils.isBlank(docTypeOverride) ? TASK_DOC_TYPE : docTypeOverride;\n            UpdateRequest request = new UpdateRequest(taskIndexName, docType, taskId);\n            Map<String, Object> source =\n                    IntStream.range(0, keys.length)\n                            .boxed()\n                            .collect(Collectors.toMap(i -> keys[i], i -> values[i]));\n            request.doc(source);\n\n            LOGGER.debug(\"Updating task: {} of workflow: {} with {}\", taskId, workflowId, source);\n            elasticSearchClient.update(request, RequestOptions.DEFAULT);\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for updating task: {} of workflow: {}\",\n                    endTime - startTime,\n                    taskId,\n                    workflowId);\n            Monitors.recordESIndexTime(\"update_task\", docType, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n        } catch (Exception e) {\n            LOGGER.error(\"Failed to update task: {} of workflow: {}\", taskId, workflowId, e);\n            Monitors.error(className, \"update\");\n        }\n    }\n\n    @Override\n    public CompletableFuture<Void> asyncUpdateTask(\n            String workflowId, String taskId, String[] keys, Object[] values) {\n        return CompletableFuture.runAsync(\n                () -> updateTask(workflowId, taskId, keys, values), executorService);\n    }\n\n    @Override\n    public String get(String workflowInstanceId, String fieldToGet) {\n\n        String docType = StringUtils.isBlank(docTypeOverride) ? WORKFLOW_DOC_TYPE : docTypeOverride;\n        GetRequest request = new GetRequest(workflowIndexName, docType, workflowInstanceId);\n\n        GetResponse response;\n        try {\n            response = elasticSearchClient.get(request);\n        } catch (IOException e) {\n            LOGGER.error(\n                    \"Unable to get Workflow: {} from ElasticSearch index: {}\",\n                    workflowInstanceId,\n                    workflowIndexName,\n                    e);\n            return null;\n        }\n\n        if (response.isExists()) {\n            Map<String, Object> sourceAsMap = response.getSourceAsMap();\n            if (sourceAsMap.get(fieldToGet) != null) {\n                return sourceAsMap.get(fieldToGet).toString();\n            }\n        }\n\n        LOGGER.debug(\n                \"Unable to find Workflow: {} in ElasticSearch index: {}.\",\n                workflowInstanceId,\n                workflowIndexName);\n        return null;\n    }\n\n    private <T> SearchResult<T> searchObjectsViaExpression(\n            String structuredQuery,\n            int start,\n            int size,\n            List<String> sortOptions,\n            String freeTextQuery,\n            String docType,\n            boolean idOnly,\n            Class<T> clazz)\n            throws ParserException, IOException {\n        QueryBuilder queryBuilder = boolQueryBuilder(structuredQuery, freeTextQuery);\n        return searchObjects(\n                getIndexName(docType),\n                queryBuilder,\n                start,\n                size,\n                sortOptions,\n                docType,\n                idOnly,\n                clazz);\n    }\n\n    private SearchResult<String> searchObjectIds(\n            String indexName, QueryBuilder queryBuilder, int start, int size, String docType)\n            throws IOException {\n        return searchObjects(\n                indexName, queryBuilder, start, size, null, docType, true, String.class);\n    }\n\n    /**\n     * Tries to find objects for a given query in an index.\n     *\n     * @param indexName The name of the index.\n     * @param queryBuilder The query to use for searching.\n     * @param start The start to use.\n     * @param size The total return size.\n     * @param sortOptions A list of string options to sort in the form VALUE:ORDER; where ORDER is\n     *     optional and can be either ASC OR DESC.\n     * @param docType The document type to searchObjectIdsViaExpression for.\n     * @return The SearchResults which includes the count and objects that were found.\n     * @throws IOException If we cannot communicate with ES.\n     */\n    private <T> SearchResult<T> searchObjects(\n            String indexName,\n            QueryBuilder queryBuilder,\n            int start,\n            int size,\n            List<String> sortOptions,\n            String docType,\n            boolean idOnly,\n            Class<T> clazz)\n            throws IOException {\n        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();\n        searchSourceBuilder.query(queryBuilder);\n        searchSourceBuilder.from(start);\n        searchSourceBuilder.size(size);\n        if (idOnly) {\n            searchSourceBuilder.fetchSource(false);\n        }\n\n        if (sortOptions != null && !sortOptions.isEmpty()) {\n\n            for (String sortOption : sortOptions) {\n                SortOrder order = SortOrder.ASC;\n                String field = sortOption;\n                int index = sortOption.indexOf(\":\");\n                if (index > 0) {\n                    field = sortOption.substring(0, index);\n                    order = SortOrder.valueOf(sortOption.substring(index + 1));\n                }\n                searchSourceBuilder.sort(new FieldSortBuilder(field).order(order));\n            }\n        }\n\n        // Generate the actual request to send to ES.\n        docType = StringUtils.isBlank(docTypeOverride) ? docType : docTypeOverride;\n        SearchRequest searchRequest = new SearchRequest(indexName);\n        searchRequest.types(docType);\n        searchRequest.source(searchSourceBuilder);\n\n        SearchResponse response = elasticSearchClient.search(searchRequest);\n        return mapSearchResult(response, idOnly, clazz);\n    }\n\n    private <T> SearchResult<T> mapSearchResult(\n            SearchResponse response, boolean idOnly, Class<T> clazz) {\n        SearchHits searchHits = response.getHits();\n        long count = searchHits.getTotalHits();\n        List<T> result;\n        if (idOnly) {\n            result =\n                    Arrays.stream(searchHits.getHits())\n                            .map(hit -> clazz.cast(hit.getId()))\n                            .collect(Collectors.toList());\n        } else {\n            result =\n                    Arrays.stream(searchHits.getHits())\n                            .map(\n                                    hit -> {\n                                        try {\n                                            return objectMapper.readValue(\n                                                    hit.getSourceAsString(), clazz);\n                                        } catch (JsonProcessingException e) {\n                                            LOGGER.error(\n                                                    \"Failed to de-serialize elasticsearch from source: {}\",\n                                                    hit.getSourceAsString(),\n                                                    e);\n                                        }\n                                        return null;\n                                    })\n                            .collect(Collectors.toList());\n        }\n        return new SearchResult<>(count, result);\n    }\n\n    @Override\n    public List<String> searchArchivableWorkflows(String indexName, long archiveTtlDays) {\n        QueryBuilder q =\n                QueryBuilders.boolQuery()\n                        .must(\n                                QueryBuilders.rangeQuery(\"endTime\")\n                                        .lt(LocalDate.now().minusDays(archiveTtlDays).toString())\n                                        .gte(\n                                                LocalDate.now()\n                                                        .minusDays(archiveTtlDays)\n                                                        .minusDays(1)\n                                                        .toString()))\n                        .should(QueryBuilders.termQuery(\"status\", \"COMPLETED\"))\n                        .should(QueryBuilders.termQuery(\"status\", \"FAILED\"))\n                        .should(QueryBuilders.termQuery(\"status\", \"TIMED_OUT\"))\n                        .should(QueryBuilders.termQuery(\"status\", \"TERMINATED\"))\n                        .mustNot(QueryBuilders.existsQuery(\"archived\"))\n                        .minimumShouldMatch(1);\n\n        SearchResult<String> workflowIds;\n        try {\n            workflowIds = searchObjectIds(indexName, q, 0, 1000, WORKFLOW_DOC_TYPE);\n        } catch (IOException e) {\n            LOGGER.error(\"Unable to communicate with ES to find archivable workflows\", e);\n            return Collections.emptyList();\n        }\n\n        return workflowIds.getResults();\n    }\n\n    @Override\n    public long getWorkflowCount(String query, String freeText) {\n        try {\n            return getObjectCounts(query, freeText, WORKFLOW_DOC_TYPE);\n        } catch (Exception e) {\n            throw new TransientException(e.getMessage(), e);\n        }\n    }\n\n    private long getObjectCounts(String structuredQuery, String freeTextQuery, String docType)\n            throws ParserException, IOException {\n        QueryBuilder queryBuilder = boolQueryBuilder(structuredQuery, freeTextQuery);\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.query(queryBuilder);\n\n        String indexName = getIndexName(docType);\n        CountRequest countRequest = new CountRequest(new String[] {indexName}, sourceBuilder);\n        CountResponse countResponse =\n                elasticSearchClient.count(countRequest, RequestOptions.DEFAULT);\n        return countResponse.getCount();\n    }\n\n    private void indexObject(final String index, final String docType, final Object doc) {\n        indexObject(index, docType, null, doc);\n    }\n\n    private void indexObject(\n            final String index, final String docType, final String docId, final Object doc) {\n\n        byte[] docBytes;\n        try {\n            docBytes = objectMapper.writeValueAsBytes(doc);\n        } catch (JsonProcessingException e) {\n            LOGGER.error(\"Failed to convert {} '{}' to byte string\", docType, docId);\n            return;\n        }\n\n        IndexRequest request = new IndexRequest(index, docType, docId);\n        request.source(docBytes, XContentType.JSON);\n\n        if (bulkRequests.get(docType) == null) {\n            bulkRequests.put(\n                    docType, new BulkRequests(System.currentTimeMillis(), new BulkRequest()));\n        }\n\n        bulkRequests.get(docType).getBulkRequest().add(request);\n        if (bulkRequests.get(docType).getBulkRequest().numberOfActions() >= this.indexBatchSize) {\n            indexBulkRequest(docType);\n        }\n    }\n\n    private synchronized void indexBulkRequest(String docType) {\n        if (bulkRequests.get(docType).getBulkRequest() != null\n                && bulkRequests.get(docType).getBulkRequest().numberOfActions() > 0) {\n            synchronized (bulkRequests.get(docType).getBulkRequest()) {\n                indexWithRetry(\n                        bulkRequests.get(docType).getBulkRequest().get(),\n                        \"Bulk Indexing \" + docType,\n                        docType);\n                bulkRequests.put(\n                        docType, new BulkRequests(System.currentTimeMillis(), new BulkRequest()));\n            }\n        }\n    }\n\n    /**\n     * Performs an index operation with a retry.\n     *\n     * @param request The index request that we want to perform.\n     * @param operationDescription The type of operation that we are performing.\n     */\n    private void indexWithRetry(\n            final BulkRequest request, final String operationDescription, String docType) {\n        try {\n            long startTime = Instant.now().toEpochMilli();\n            retryTemplate.execute(\n                    context -> elasticSearchClient.bulk(request, RequestOptions.DEFAULT));\n            long endTime = Instant.now().toEpochMilli();\n            LOGGER.debug(\n                    \"Time taken {} for indexing object of type: {}\", endTime - startTime, docType);\n            Monitors.recordESIndexTime(\"index_object\", docType, endTime - startTime);\n            Monitors.recordWorkerQueueSize(\n                    \"indexQueue\", ((ThreadPoolExecutor) executorService).getQueue().size());\n            Monitors.recordWorkerQueueSize(\n                    \"logQueue\", ((ThreadPoolExecutor) logExecutorService).getQueue().size());\n        } catch (Exception e) {\n            Monitors.error(className, \"index\");\n            LOGGER.error(\"Failed to index {} for request type: {}\", request, docType, e);\n        }\n    }\n\n    /**\n     * Flush the buffers if bulk requests have not been indexed for the past {@link\n     * ElasticSearchProperties#getAsyncBufferFlushTimeout()} seconds. This is to prevent data loss\n     * in case the instance is terminated, while the buffer still holds documents to be indexed.\n     */\n    private void flushBulkRequests() {\n        bulkRequests.entrySet().stream()\n                .filter(\n                        entry ->\n                                (System.currentTimeMillis() - entry.getValue().getLastFlushTime())\n                                        >= asyncBufferFlushTimeout)\n                .filter(\n                        entry ->\n                                entry.getValue().getBulkRequest() != null\n                                        && entry.getValue().getBulkRequest().numberOfActions() > 0)\n                .forEach(\n                        entry -> {\n                            LOGGER.debug(\n                                    \"Flushing bulk request buffer for type {}, size: {}\",\n                                    entry.getKey(),\n                                    entry.getValue().getBulkRequest().numberOfActions());\n                            indexBulkRequest(entry.getKey());\n                        });\n    }\n\n    private static class BulkRequests {\n\n        private final long lastFlushTime;\n        private final BulkRequestWrapper bulkRequest;\n\n        long getLastFlushTime() {\n            return lastFlushTime;\n        }\n\n        BulkRequestWrapper getBulkRequest() {\n            return bulkRequest;\n        }\n\n        BulkRequests(long lastFlushTime, BulkRequest bulkRequest) {\n            this.lastFlushTime = lastFlushTime;\n            this.bulkRequest = new BulkRequestWrapper(bulkRequest);\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/Expression.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\n\nimport org.elasticsearch.index.query.QueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilders;\n\nimport com.netflix.conductor.es6.dao.query.parser.internal.AbstractNode;\nimport com.netflix.conductor.es6.dao.query.parser.internal.BooleanOp;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ParserException;\n\npublic class Expression extends AbstractNode implements FilterProvider {\n\n    private NameValue nameVal;\n    private GroupedExpression ge;\n    private BooleanOp op;\n    private Expression rhs;\n\n    public Expression(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        byte[] peeked = peek(1);\n\n        if (peeked[0] == '(') {\n            this.ge = new GroupedExpression(is);\n        } else {\n            this.nameVal = new NameValue(is);\n        }\n\n        peeked = peek(3);\n        if (isBoolOpr(peeked)) {\n            // we have an expression next\n            this.op = new BooleanOp(is);\n            this.rhs = new Expression(is);\n        }\n    }\n\n    public boolean isBinaryExpr() {\n        return this.op != null;\n    }\n\n    public BooleanOp getOperator() {\n        return this.op;\n    }\n\n    public Expression getRightHandSide() {\n        return this.rhs;\n    }\n\n    public boolean isNameValue() {\n        return this.nameVal != null;\n    }\n\n    public NameValue getNameValue() {\n        return this.nameVal;\n    }\n\n    public GroupedExpression getGroupedExpression() {\n        return this.ge;\n    }\n\n    @Override\n    public QueryBuilder getFilterBuilder() {\n        QueryBuilder lhs = null;\n        if (nameVal != null) {\n            lhs = nameVal.getFilterBuilder();\n        } else {\n            lhs = ge.getFilterBuilder();\n        }\n\n        if (this.isBinaryExpr()) {\n            QueryBuilder rhsFilter = rhs.getFilterBuilder();\n            if (this.op.isAnd()) {\n                return QueryBuilders.boolQuery().must(lhs).must(rhsFilter);\n            } else {\n                return QueryBuilders.boolQuery().should(lhs).should(rhsFilter);\n            }\n        } else {\n            return lhs;\n        }\n    }\n\n    @Override\n    public String toString() {\n        if (isBinaryExpr()) {\n            return \"\" + (nameVal == null ? ge : nameVal) + op + rhs;\n        } else {\n            return \"\" + (nameVal == null ? ge : nameVal);\n        }\n    }\n\n    public static Expression fromString(String value) throws ParserException {\n        return new Expression(new BufferedInputStream(new ByteArrayInputStream(value.getBytes())));\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/FilterProvider.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser;\n\nimport org.elasticsearch.index.query.QueryBuilder;\n\npublic interface FilterProvider {\n\n    /**\n     * @return FilterBuilder for elasticsearch\n     */\n    public QueryBuilder getFilterBuilder();\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/GroupedExpression.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser;\n\nimport java.io.InputStream;\n\nimport org.elasticsearch.index.query.QueryBuilder;\n\nimport com.netflix.conductor.es6.dao.query.parser.internal.AbstractNode;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ParserException;\n\npublic class GroupedExpression extends AbstractNode implements FilterProvider {\n\n    private Expression expression;\n\n    public GroupedExpression(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        byte[] peeked = read(1);\n        assertExpected(peeked, \"(\");\n\n        this.expression = new Expression(is);\n\n        peeked = read(1);\n        assertExpected(peeked, \")\");\n    }\n\n    @Override\n    public String toString() {\n        return \"(\" + expression + \")\";\n    }\n\n    /**\n     * @return the expression\n     */\n    public Expression getExpression() {\n        return expression;\n    }\n\n    @Override\n    public QueryBuilder getFilterBuilder() {\n        return expression.getFilterBuilder();\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/NameValue.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser;\n\nimport java.io.InputStream;\n\nimport org.elasticsearch.index.query.QueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilders;\n\nimport com.netflix.conductor.es6.dao.query.parser.internal.AbstractNode;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ComparisonOp;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ComparisonOp.Operators;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ConstValue;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ListConst;\nimport com.netflix.conductor.es6.dao.query.parser.internal.Name;\nimport com.netflix.conductor.es6.dao.query.parser.internal.ParserException;\nimport com.netflix.conductor.es6.dao.query.parser.internal.Range;\n\n/**\n *\n *\n * <pre>\n * Represents an expression of the form as below:\n * key OPR value\n * OPR is the comparison operator which could be one of the following:\n * \t&gt;, &lt;, = , !=, IN, BETWEEN\n * </pre>\n */\npublic class NameValue extends AbstractNode implements FilterProvider {\n\n    private Name name;\n\n    private ComparisonOp op;\n\n    private ConstValue value;\n\n    private Range range;\n\n    private ListConst valueList;\n\n    public NameValue(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        this.name = new Name(is);\n        this.op = new ComparisonOp(is);\n\n        if (this.op.getOperator().equals(Operators.BETWEEN.value())) {\n            this.range = new Range(is);\n        }\n        if (this.op.getOperator().equals(Operators.IN.value())) {\n            this.valueList = new ListConst(is);\n        } else {\n            this.value = new ConstValue(is);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"\" + name + op + value;\n    }\n\n    /**\n     * @return the name\n     */\n    public Name getName() {\n        return name;\n    }\n\n    /**\n     * @return the op\n     */\n    public ComparisonOp getOp() {\n        return op;\n    }\n\n    /**\n     * @return the value\n     */\n    public ConstValue getValue() {\n        return value;\n    }\n\n    @Override\n    public QueryBuilder getFilterBuilder() {\n        if (op.getOperator().equals(Operators.EQUALS.value())) {\n            return QueryBuilders.queryStringQuery(\n                    name.getName() + \":\" + value.getValue().toString());\n        } else if (op.getOperator().equals(Operators.BETWEEN.value())) {\n            return QueryBuilders.rangeQuery(name.getName())\n                    .from(range.getLow())\n                    .to(range.getHigh());\n        } else if (op.getOperator().equals(Operators.IN.value())) {\n            return QueryBuilders.termsQuery(name.getName(), valueList.getList());\n        } else if (op.getOperator().equals(Operators.NOT_EQUALS.value())) {\n            return QueryBuilders.queryStringQuery(\n                    \"NOT \" + name.getName() + \":\" + value.getValue().toString());\n        } else if (op.getOperator().equals(Operators.GREATER_THAN.value())) {\n            return QueryBuilders.rangeQuery(name.getName())\n                    .from(value.getValue())\n                    .includeLower(false)\n                    .includeUpper(false);\n        } else if (op.getOperator().equals(Operators.IS.value())) {\n            if (value.getSysConstant().equals(ConstValue.SystemConsts.NULL)) {\n                return QueryBuilders.boolQuery()\n                        .mustNot(\n                                QueryBuilders.boolQuery()\n                                        .must(QueryBuilders.matchAllQuery())\n                                        .mustNot(QueryBuilders.existsQuery(name.getName())));\n            } else if (value.getSysConstant().equals(ConstValue.SystemConsts.NOT_NULL)) {\n                return QueryBuilders.boolQuery()\n                        .mustNot(\n                                QueryBuilders.boolQuery()\n                                        .must(QueryBuilders.matchAllQuery())\n                                        .must(QueryBuilders.existsQuery(name.getName())));\n            }\n        } else if (op.getOperator().equals(Operators.LESS_THAN.value())) {\n            return QueryBuilders.rangeQuery(name.getName())\n                    .to(value.getValue())\n                    .includeLower(false)\n                    .includeUpper(false);\n        } else if (op.getOperator().equals(Operators.STARTS_WITH.value())) {\n            return QueryBuilders.prefixQuery(name.getName(), value.getUnquotedValue());\n        }\n\n        throw new IllegalStateException(\"Incorrect/unsupported operators\");\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/AbstractNode.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.InputStream;\nimport java.math.BigDecimal;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\npublic abstract class AbstractNode {\n\n    public static final Pattern WHITESPACE = Pattern.compile(\"\\\\s\");\n\n    protected static Set<Character> comparisonOprs = new HashSet<>();\n\n    static {\n        comparisonOprs.add('>');\n        comparisonOprs.add('<');\n        comparisonOprs.add('=');\n    }\n\n    protected InputStream is;\n\n    protected AbstractNode(InputStream is) throws ParserException {\n        this.is = is;\n        this.parse();\n    }\n\n    protected boolean isNumber(String test) {\n        try {\n            // If you can convert to a big decimal value, then it is a number.\n            new BigDecimal(test);\n            return true;\n\n        } catch (NumberFormatException e) {\n            // Ignore\n        }\n        return false;\n    }\n\n    protected boolean isBoolOpr(byte[] buffer) {\n        if (buffer.length > 1 && buffer[0] == 'O' && buffer[1] == 'R') {\n            return true;\n        } else {\n            return buffer.length > 2 && buffer[0] == 'A' && buffer[1] == 'N' && buffer[2] == 'D';\n        }\n    }\n\n    protected boolean isComparisonOpr(byte[] buffer) {\n        if (buffer[0] == 'I' && buffer[1] == 'N') {\n            return true;\n        } else if (buffer[0] == '!' && buffer[1] == '=') {\n            return true;\n        } else {\n            return comparisonOprs.contains((char) buffer[0]);\n        }\n    }\n\n    protected byte[] peek(int length) throws Exception {\n        return read(length, true);\n    }\n\n    protected byte[] read(int length) throws Exception {\n        return read(length, false);\n    }\n\n    protected String readToken() throws Exception {\n        skipWhitespace();\n        StringBuilder sb = new StringBuilder();\n        while (is.available() > 0) {\n            char c = (char) peek(1)[0];\n            if (c == ' ' || c == '\\t' || c == '\\n' || c == '\\r') {\n                is.skip(1);\n                break;\n            } else if (c == '=' || c == '>' || c == '<' || c == '!') {\n                // do not skip\n                break;\n            }\n            sb.append(c);\n            is.skip(1);\n        }\n        return sb.toString().trim();\n    }\n\n    protected boolean isNumeric(char c) {\n        return c == '-' || c == 'e' || (c >= '0' && c <= '9') || c == '.';\n    }\n\n    protected void assertExpected(byte[] found, String expected) throws ParserException {\n        assertExpected(new String(found), expected);\n    }\n\n    protected void assertExpected(String found, String expected) throws ParserException {\n        if (!found.equals(expected)) {\n            throw new ParserException(\"Expected \" + expected + \", found \" + found);\n        }\n    }\n\n    protected void assertExpected(char found, char expected) throws ParserException {\n        if (found != expected) {\n            throw new ParserException(\"Expected \" + expected + \", found \" + found);\n        }\n    }\n\n    protected static void efor(int length, FunctionThrowingException<Integer> consumer)\n            throws Exception {\n        for (int i = 0; i < length; i++) {\n            consumer.accept(i);\n        }\n    }\n\n    protected abstract void _parse() throws Exception;\n\n    // Public stuff here\n    private void parse() throws ParserException {\n        // skip white spaces\n        skipWhitespace();\n        try {\n            _parse();\n        } catch (Exception e) {\n            if (!(e instanceof ParserException)) {\n                throw new ParserException(\"Error parsing\", e);\n            } else {\n                throw (ParserException) e;\n            }\n        }\n        skipWhitespace();\n    }\n\n    // Private methods\n\n    private byte[] read(int length, boolean peekOnly) throws Exception {\n        byte[] buf = new byte[length];\n        if (peekOnly) {\n            is.mark(length);\n        }\n        efor(length, (Integer c) -> buf[c] = (byte) is.read());\n        if (peekOnly) {\n            is.reset();\n        }\n        return buf;\n    }\n\n    protected void skipWhitespace() throws ParserException {\n        try {\n            while (is.available() > 0) {\n                byte c = peek(1)[0];\n                if (c == ' ' || c == '\\t' || c == '\\n' || c == '\\r') {\n                    // skip\n                    read(1);\n                } else {\n                    break;\n                }\n            }\n        } catch (Exception e) {\n            throw new ParserException(e.getMessage(), e);\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/BooleanOp.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.InputStream;\n\npublic class BooleanOp extends AbstractNode {\n\n    private String value;\n\n    public BooleanOp(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        byte[] buffer = peek(3);\n        if (buffer.length > 1 && buffer[0] == 'O' && buffer[1] == 'R') {\n            this.value = \"OR\";\n        } else if (buffer.length > 2 && buffer[0] == 'A' && buffer[1] == 'N' && buffer[2] == 'D') {\n            this.value = \"AND\";\n        } else {\n            throw new ParserException(\"No valid boolean operator found...\");\n        }\n        read(this.value.length());\n    }\n\n    @Override\n    public String toString() {\n        return \" \" + value + \" \";\n    }\n\n    public String getOperator() {\n        return value;\n    }\n\n    public boolean isAnd() {\n        return \"AND\".equals(value);\n    }\n\n    public boolean isOr() {\n        return \"OR\".equals(value);\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/ComparisonOp.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.InputStream;\n\npublic class ComparisonOp extends AbstractNode {\n\n    public enum Operators {\n        BETWEEN(\"BETWEEN\"),\n        EQUALS(\"=\"),\n        LESS_THAN(\"<\"),\n        GREATER_THAN(\">\"),\n        IN(\"IN\"),\n        NOT_EQUALS(\"!=\"),\n        IS(\"IS\"),\n        STARTS_WITH(\"STARTS_WITH\");\n\n        private final String value;\n\n        Operators(String value) {\n            this.value = value;\n        }\n\n        public String value() {\n            return value;\n        }\n    }\n\n    static {\n        int max = 0;\n        for (Operators op : Operators.values()) {\n            max = Math.max(max, op.value().length());\n        }\n        maxOperatorLength = max;\n    }\n\n    private static final int maxOperatorLength;\n\n    private static final int betweenLen = Operators.BETWEEN.value().length();\n    private static final int startsWithLen = Operators.STARTS_WITH.value().length();\n\n    private String value;\n\n    public ComparisonOp(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        byte[] peeked = peek(maxOperatorLength);\n        if (peeked[0] == '=' || peeked[0] == '>' || peeked[0] == '<') {\n            this.value = new String(peeked, 0, 1);\n        } else if (peeked[0] == 'I' && peeked[1] == 'N') {\n            this.value = \"IN\";\n        } else if (peeked[0] == 'I' && peeked[1] == 'S') {\n            this.value = \"IS\";\n        } else if (peeked[0] == '!' && peeked[1] == '=') {\n            this.value = \"!=\";\n        } else if (peeked.length >= betweenLen\n                && peeked[0] == 'B'\n                && peeked[1] == 'E'\n                && peeked[2] == 'T'\n                && peeked[3] == 'W'\n                && peeked[4] == 'E'\n                && peeked[5] == 'E'\n                && peeked[6] == 'N') {\n            this.value = Operators.BETWEEN.value();\n        } else if (peeked.length == startsWithLen\n                && new String(peeked).equals(Operators.STARTS_WITH.value())) {\n            this.value = Operators.STARTS_WITH.value();\n        } else {\n            throw new ParserException(\n                    \"Expecting an operator (=, >, <, !=, BETWEEN, IN, STARTS_WITH), but found none.  Peeked=>\"\n                            + new String(peeked));\n        }\n\n        read(this.value.length());\n    }\n\n    @Override\n    public String toString() {\n        return \" \" + value + \" \";\n    }\n\n    public String getOperator() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/ConstValue.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.InputStream;\n\n/**\n * Constant value can be:\n *\n * <ol>\n *   <li>List of values (a,b,c)\n *   <li>Range of values (m AND n)\n *   <li>A value (x)\n *   <li>A value is either a string or a number\n * </ol>\n */\npublic class ConstValue extends AbstractNode {\n\n    public enum SystemConsts {\n        NULL(\"null\"),\n        NOT_NULL(\"not null\");\n        private final String value;\n\n        SystemConsts(String value) {\n            this.value = value;\n        }\n\n        public String value() {\n            return value;\n        }\n    }\n\n    private static final String QUOTE = \"\\\"\";\n\n    private Object value;\n\n    private SystemConsts sysConsts;\n\n    public ConstValue(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        byte[] peeked = peek(4);\n        String sp = new String(peeked).trim();\n        // Read a constant value (number or a string)\n        if (peeked[0] == '\"' || peeked[0] == '\\'') {\n            this.value = readString(is);\n        } else if (sp.toLowerCase().startsWith(\"not\")) {\n            this.value = SystemConsts.NOT_NULL.value();\n            sysConsts = SystemConsts.NOT_NULL;\n            read(SystemConsts.NOT_NULL.value().length());\n        } else if (sp.equalsIgnoreCase(SystemConsts.NULL.value())) {\n            this.value = SystemConsts.NULL.value();\n            sysConsts = SystemConsts.NULL;\n            read(SystemConsts.NULL.value().length());\n        } else {\n            this.value = readNumber(is);\n        }\n    }\n\n    private String readNumber(InputStream is) throws Exception {\n        StringBuilder sb = new StringBuilder();\n        while (is.available() > 0) {\n            is.mark(1);\n            char c = (char) is.read();\n            if (!isNumeric(c)) {\n                is.reset();\n                break;\n            } else {\n                sb.append(c);\n            }\n        }\n        return sb.toString().trim();\n    }\n\n    /**\n     * Reads an escaped string\n     *\n     * @throws Exception\n     */\n    private String readString(InputStream is) throws Exception {\n        char delim = (char) read(1)[0];\n        StringBuilder sb = new StringBuilder();\n        boolean valid = false;\n        while (is.available() > 0) {\n            char c = (char) is.read();\n            if (c == delim) {\n                valid = true;\n                break;\n            } else if (c == '\\\\') {\n                // read the next character as part of the value\n                c = (char) is.read();\n                sb.append(c);\n            } else {\n                sb.append(c);\n            }\n        }\n        if (!valid) {\n            throw new ParserException(\n                    \"String constant is not quoted with <\" + delim + \"> : \" + sb.toString());\n        }\n        return QUOTE + sb.toString() + QUOTE;\n    }\n\n    public Object getValue() {\n        return value;\n    }\n\n    @Override\n    public String toString() {\n        return \"\" + value;\n    }\n\n    public String getUnquotedValue() {\n        String result = toString();\n        if (result.length() >= 2 && result.startsWith(QUOTE) && result.endsWith(QUOTE)) {\n            result = result.substring(1, result.length() - 1);\n        }\n        return result;\n    }\n\n    public boolean isSysConstant() {\n        return this.sysConsts != null;\n    }\n\n    public SystemConsts getSysConstant() {\n        return this.sysConsts;\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/FunctionThrowingException.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\n@FunctionalInterface\npublic interface FunctionThrowingException<T> {\n\n    void accept(T t) throws Exception;\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/ListConst.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.InputStream;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/** List of constants */\npublic class ListConst extends AbstractNode {\n\n    private List<Object> values;\n\n    public ListConst(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        byte[] peeked = read(1);\n        assertExpected(peeked, \"(\");\n        this.values = readList();\n    }\n\n    private List<Object> readList() throws Exception {\n        List<Object> list = new LinkedList<>();\n        boolean valid = false;\n        char c;\n\n        StringBuilder sb = new StringBuilder();\n        while (is.available() > 0) {\n            c = (char) is.read();\n            if (c == ')') {\n                valid = true;\n                break;\n            } else if (c == ',') {\n                list.add(sb.toString().trim());\n                sb = new StringBuilder();\n            } else {\n                sb.append(c);\n            }\n        }\n        list.add(sb.toString().trim());\n        if (!valid) {\n            throw new ParserException(\"Expected ')' but never encountered in the stream\");\n        }\n        return list;\n    }\n\n    public List<Object> getList() {\n        return values;\n    }\n\n    @Override\n    public String toString() {\n        return values.toString();\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/Name.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.InputStream;\n\n/** Represents the name of the field to be searched against. */\npublic class Name extends AbstractNode {\n\n    private String value;\n\n    public Name(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        this.value = readToken();\n    }\n\n    @Override\n    public String toString() {\n        return value;\n    }\n\n    public String getName() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/ParserException.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\n@SuppressWarnings(\"serial\")\npublic class ParserException extends Exception {\n\n    public ParserException(String message) {\n        super(message);\n    }\n\n    public ParserException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/java/com/netflix/conductor/es6/dao/query/parser/internal/Range.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.InputStream;\n\npublic class Range extends AbstractNode {\n\n    private String low;\n\n    private String high;\n\n    public Range(InputStream is) throws ParserException {\n        super(is);\n    }\n\n    @Override\n    protected void _parse() throws Exception {\n        this.low = readNumber(is);\n\n        skipWhitespace();\n        byte[] peeked = read(3);\n        assertExpected(peeked, \"AND\");\n        skipWhitespace();\n\n        String num = readNumber(is);\n        if (\"\".equals(num)) {\n            throw new ParserException(\"Missing the upper range value...\");\n        }\n        this.high = num;\n    }\n\n    private String readNumber(InputStream is) throws Exception {\n        StringBuilder sb = new StringBuilder();\n        while (is.available() > 0) {\n            is.mark(1);\n            char c = (char) is.read();\n            if (!isNumeric(c)) {\n                is.reset();\n                break;\n            } else {\n                sb.append(c);\n            }\n        }\n        return sb.toString().trim();\n    }\n\n    /**\n     * @return the low\n     */\n    public String getLow() {\n        return low;\n    }\n\n    /**\n     * @return the high\n     */\n    public String getHigh() {\n        return high;\n    }\n\n    @Override\n    public String toString() {\n        return low + \" AND \" + high;\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/main/resources/mappings_docType_task.json",
    "content": "{\n  \"task\": {\n    \"properties\": {\n      \"correlationId\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"endTime\": {\n        \"type\": \"date\",\n        \"format\": \"strict_date_optional_time||epoch_millis\"\n      },\n      \"executionTime\": {\n        \"type\": \"long\"\n      },\n      \"input\": {\n        \"type\": \"text\",\n        \"index\": true\n      },\n      \"output\": {\n        \"type\": \"text\",\n        \"index\": true\n      },\n      \"queueWaitTime\": {\n        \"type\": \"long\"\n      },\n      \"reasonForIncompletion\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"scheduledTime\": {\n        \"type\": \"date\",\n        \"format\": \"strict_date_optional_time||epoch_millis\"\n      },\n      \"startTime\": {\n        \"type\": \"date\",\n        \"format\": \"strict_date_optional_time||epoch_millis\"\n      },\n      \"status\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"taskDefName\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"taskId\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"taskType\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"updateTime\": {\n        \"type\": \"date\",\n        \"format\": \"strict_date_optional_time||epoch_millis\"\n      },\n      \"workflowId\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"workflowType\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      },\n      \"domain\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      }\n    }\n  }\n}"
  },
  {
    "path": "es6-persistence/src/main/resources/mappings_docType_workflow.json",
    "content": "{\n  \"workflow\": {\n    \"properties\": {\n      \"correlationId\": {\n        \"type\": \"keyword\",\n        \"index\": true,\n        \"doc_values\": true\n      },\n      \"endTime\": {\n        \"type\": \"date\",\n        \"format\": \"strict_date_optional_time||epoch_millis\",\n        \"doc_values\": true\n      },\n      \"executionTime\": {\n        \"type\": \"long\",\n        \"doc_values\": true\n      },\n      \"failedReferenceTaskNames\": {\n        \"type\": \"text\",\n        \"index\": false\n      },\n      \"failedTaskNames\": {\n        \"type\": \"text\",\n        \"index\": true\n      },\n      \"input\": {\n        \"type\": \"text\",\n        \"index\": true\n      },\n      \"output\": {\n        \"type\": \"text\",\n        \"index\": true\n      },\n      \"reasonForIncompletion\": {\n        \"type\": \"keyword\",\n        \"index\": true,\n        \"doc_values\": true\n      },\n      \"startTime\": {\n        \"type\": \"date\",\n        \"format\": \"strict_date_optional_time||epoch_millis\",\n        \"doc_values\": true\n      },\n      \"status\": {\n        \"type\": \"keyword\",\n        \"index\": true,\n        \"doc_values\": true\n      },\n      \"updateTime\": {\n        \"type\": \"date\",\n        \"format\": \"strict_date_optional_time||epoch_millis\",\n        \"doc_values\": true\n      },\n      \"version\": {\n        \"type\": \"long\",\n        \"doc_values\": true\n      },\n      \"workflowId\": {\n        \"type\": \"keyword\",\n        \"index\": true,\n        \"doc_values\": true\n      },\n      \"workflowType\": {\n        \"type\": \"keyword\",\n        \"index\": true,\n        \"doc_values\": true\n      },\n      \"rawJSON\": {\n        \"type\": \"text\",\n        \"index\": false\n      },\n      \"event\": {\n        \"type\": \"keyword\",\n        \"index\": true\n      }\n    }\n  }\n}"
  },
  {
    "path": "es6-persistence/src/main/resources/template_event.json",
    "content": "{\n  \"order\": 0,\n  \"template\": \"*event*\",\n  \"settings\": {\n    \"index\": {\n      \"refresh_interval\": \"1s\"\n    }\n  },\n  \"mappings\": {\n    \"event\": {\n      \"properties\": {\n        \"action\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"created\": {\n          \"type\": \"long\"\n        },\n        \"event\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"id\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"messageId\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"name\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"output\": {\n          \"properties\": {\n            \"workflowId\": {\n              \"type\": \"keyword\",\n              \"index\": true\n            }\n          }\n        },\n        \"status\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        }\n      }\n    }\n  },\n  \"aliases\": {}\n}"
  },
  {
    "path": "es6-persistence/src/main/resources/template_message.json",
    "content": "{\n  \"order\": 0,\n  \"template\": \"*message*\",\n  \"settings\": {\n    \"index\": {\n      \"refresh_interval\": \"1s\"\n    }\n  },\n  \"mappings\": {\n    \"message\": {\n      \"properties\": {\n        \"created\": {\n          \"type\": \"long\"\n        },\n        \"messageId\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"payload\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"queue\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        }\n      }\n    }\n  },\n  \"aliases\": {}\n}"
  },
  {
    "path": "es6-persistence/src/main/resources/template_task_log.json",
    "content": "{\n  \"order\": 0,\n  \"template\": \"*task*log*\",\n  \"settings\": {\n    \"index\": {\n      \"refresh_interval\": \"1s\"\n    }\n  },\n  \"mappings\": {\n    \"task_log\": {\n      \"properties\": {\n        \"createdTime\": {\n          \"type\": \"long\"\n        },\n        \"log\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"taskId\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        }\n      }\n    }\n  },\n  \"aliases\": {}\n}"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/index/ElasticSearchDaoBaseTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.net.InetAddress;\nimport java.util.concurrent.ExecutionException;\n\nimport org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;\nimport org.elasticsearch.client.transport.TransportClient;\nimport org.elasticsearch.cluster.metadata.IndexMetaData;\nimport org.elasticsearch.common.collect.ImmutableOpenMap;\nimport org.elasticsearch.common.settings.Settings;\nimport org.elasticsearch.common.transport.TransportAddress;\nimport org.elasticsearch.transport.client.PreBuiltTransportClient;\nimport org.junit.After;\nimport org.junit.AfterClass;\nimport org.junit.Before;\nimport org.springframework.retry.support.RetryTemplate;\n\nabstract class ElasticSearchDaoBaseTest extends ElasticSearchTest {\n\n    protected TransportClient elasticSearchClient;\n    protected ElasticSearchDAOV6 indexDAO;\n\n    @Before\n    public void setup() throws Exception {\n        int mappedPort = container.getMappedPort(9300);\n        properties.setUrl(\"tcp://localhost:\" + mappedPort);\n\n        Settings settings =\n                Settings.builder().put(\"client.transport.ignore_cluster_name\", true).build();\n\n        elasticSearchClient =\n                new PreBuiltTransportClient(settings)\n                        .addTransportAddress(\n                                new TransportAddress(\n                                        InetAddress.getByName(\"localhost\"), mappedPort));\n\n        indexDAO =\n                new ElasticSearchDAOV6(\n                        elasticSearchClient, new RetryTemplate(), properties, objectMapper);\n        indexDAO.setup();\n    }\n\n    @AfterClass\n    public static void closeClient() {\n        container.stop();\n    }\n\n    @After\n    public void tearDown() {\n        deleteAllIndices();\n\n        if (elasticSearchClient != null) {\n            elasticSearchClient.close();\n        }\n    }\n\n    private void deleteAllIndices() {\n        ImmutableOpenMap<String, IndexMetaData> indices =\n                elasticSearchClient\n                        .admin()\n                        .cluster()\n                        .prepareState()\n                        .get()\n                        .getState()\n                        .getMetaData()\n                        .getIndices();\n        indices.forEach(\n                cursor -> {\n                    try {\n                        elasticSearchClient\n                                .admin()\n                                .indices()\n                                .delete(new DeleteIndexRequest(cursor.value.getIndex().getName()))\n                                .get();\n                    } catch (InterruptedException | ExecutionException e) {\n                        throw new RuntimeException(e);\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/index/ElasticSearchRestDaoBaseTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\n\nimport org.apache.http.HttpHost;\nimport org.elasticsearch.client.Response;\nimport org.elasticsearch.client.RestClient;\nimport org.elasticsearch.client.RestClientBuilder;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.springframework.retry.support.RetryTemplate;\n\nabstract class ElasticSearchRestDaoBaseTest extends ElasticSearchTest {\n\n    protected RestClient restClient;\n    protected ElasticSearchRestDAOV6 indexDAO;\n\n    @Before\n    public void setup() throws Exception {\n        String httpHostAddress = container.getHttpHostAddress();\n        String host = httpHostAddress.split(\":\")[0];\n        int port = Integer.parseInt(httpHostAddress.split(\":\")[1]);\n\n        properties.setUrl(\"http://\" + httpHostAddress);\n\n        RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost(host, port, \"http\"));\n        restClient = restClientBuilder.build();\n\n        indexDAO =\n                new ElasticSearchRestDAOV6(\n                        restClientBuilder, new RetryTemplate(), properties, objectMapper);\n        indexDAO.setup();\n    }\n\n    @After\n    public void tearDown() throws Exception {\n        deleteAllIndices();\n\n        if (restClient != null) {\n            restClient.close();\n        }\n    }\n\n    private void deleteAllIndices() throws IOException {\n        Response beforeResponse = restClient.performRequest(\"GET\", \"/_cat/indices\");\n\n        Reader streamReader = new InputStreamReader(beforeResponse.getEntity().getContent());\n        BufferedReader bufferedReader = new BufferedReader(streamReader);\n\n        String line;\n        while ((line = bufferedReader.readLine()) != null) {\n            String[] fields = line.split(\"\\\\s\");\n            String endpoint = String.format(\"/%s\", fields[2]);\n\n            restClient.performRequest(\"DELETE\", endpoint);\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/index/ElasticSearchTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.util.Map;\n\nimport org.junit.AfterClass;\nimport org.junit.BeforeClass;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.testcontainers.elasticsearch.ElasticsearchContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.es6.config.ElasticSearchProperties;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n@ContextConfiguration(\n        classes = {TestObjectMapperConfiguration.class, ElasticSearchTest.TestConfiguration.class})\n@RunWith(SpringRunner.class)\n@TestPropertySource(\n        properties = {\"conductor.indexing.enabled=true\", \"conductor.elasticsearch.version=6\"})\nabstract class ElasticSearchTest {\n\n    @Configuration\n    static class TestConfiguration {\n\n        @Bean\n        public ElasticSearchProperties elasticSearchProperties() {\n            return new ElasticSearchProperties();\n        }\n    }\n\n    protected static final ElasticsearchContainer container =\n            new ElasticsearchContainer(\n                            DockerImageName.parse(\n                                            \"docker.elastic.co/elasticsearch/elasticsearch-oss\")\n                                    .withTag(\"6.8.17\")) // this should match the client version\n                    // Resolve issue with es container not starting on m1/m2 macs\n                    .withEnv(Map.of(\"bootstrap.system_call_filter\", \"false\"));\n\n    @Autowired protected ObjectMapper objectMapper;\n\n    @Autowired protected ElasticSearchProperties properties;\n\n    @BeforeClass\n    public static void startServer() {\n        container.start();\n    }\n\n    @AfterClass\n    public static void stopServer() {\n        container.stop();\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/index/TestElasticSearchDAOV6.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.concurrent.ExecutionException;\nimport java.util.function.Supplier;\n\nimport org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;\nimport org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;\nimport org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.Workflow.WorkflowStatus;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.es6.utils.TestUtils;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.google.common.collect.ImmutableMap;\n\nimport static org.junit.Assert.*;\nimport static org.junit.Assert.assertFalse;\n\npublic class TestElasticSearchDAOV6 extends ElasticSearchDaoBaseTest {\n\n    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(\"yyyyMMWW\");\n\n    private static final String INDEX_PREFIX = \"conductor\";\n    private static final String WORKFLOW_DOC_TYPE = \"workflow\";\n    private static final String TASK_DOC_TYPE = \"task\";\n    private static final String MSG_DOC_TYPE = \"message\";\n    private static final String EVENT_DOC_TYPE = \"event\";\n    private static final String LOG_INDEX_PREFIX = \"task_log\";\n\n    @Test\n    public void assertInitialSetup() {\n        SIMPLE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(\"GMT\"));\n\n        String workflowIndex = INDEX_PREFIX + \"_\" + WORKFLOW_DOC_TYPE;\n        String taskIndex = INDEX_PREFIX + \"_\" + TASK_DOC_TYPE;\n\n        String taskLogIndex =\n                INDEX_PREFIX + \"_\" + LOG_INDEX_PREFIX + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n        String messageIndex =\n                INDEX_PREFIX + \"_\" + MSG_DOC_TYPE + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n        String eventIndex =\n                INDEX_PREFIX + \"_\" + EVENT_DOC_TYPE + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n\n        assertTrue(\"Index 'conductor_workflow' should exist\", indexExists(\"conductor_workflow\"));\n        assertTrue(\"Index 'conductor_task' should exist\", indexExists(\"conductor_task\"));\n\n        assertTrue(\"Index '\" + taskLogIndex + \"' should exist\", indexExists(taskLogIndex));\n        assertTrue(\"Index '\" + messageIndex + \"' should exist\", indexExists(messageIndex));\n        assertTrue(\"Index '\" + eventIndex + \"' should exist\", indexExists(eventIndex));\n\n        assertTrue(\n                \"Mapping 'workflow' for index 'conductor' should exist\",\n                doesMappingExist(workflowIndex, WORKFLOW_DOC_TYPE));\n        assertTrue(\n                \"Mapping 'task' for index 'conductor' should exist\",\n                doesMappingExist(taskIndex, TASK_DOC_TYPE));\n    }\n\n    private boolean indexExists(final String index) {\n        IndicesExistsRequest request = new IndicesExistsRequest(index);\n        try {\n            return elasticSearchClient.admin().indices().exists(request).get().isExists();\n        } catch (InterruptedException | ExecutionException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private boolean doesMappingExist(final String index, final String mappingName) {\n        GetMappingsRequest request = new GetMappingsRequest().indices(index);\n        try {\n            GetMappingsResponse response =\n                    elasticSearchClient.admin().indices().getMappings(request).get();\n\n            return response.getMappings().get(index).containsKey(mappingName);\n        } catch (InterruptedException | ExecutionException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Test\n    public void shouldIndexWorkflow() throws JsonProcessingException {\n        WorkflowSummary workflow = TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflow);\n\n        assertWorkflowSummary(workflow.getWorkflowId(), workflow);\n    }\n\n    @Test\n    public void shouldIndexWorkflowAsync() throws Exception {\n        WorkflowSummary workflow = TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.asyncIndexWorkflow(workflow).get();\n\n        assertWorkflowSummary(workflow.getWorkflowId(), workflow);\n    }\n\n    @Test\n    public void shouldRemoveWorkflow() {\n        WorkflowSummary workflow = TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflow);\n\n        // wait for workflow to be indexed\n        List<String> workflows = tryFindResults(() -> searchWorkflows(workflow.getWorkflowId()), 1);\n        assertEquals(1, workflows.size());\n\n        indexDAO.removeWorkflow(workflow.getWorkflowId());\n\n        workflows = tryFindResults(() -> searchWorkflows(workflow.getWorkflowId()), 0);\n\n        assertTrue(\"Workflow was not removed.\", workflows.isEmpty());\n    }\n\n    @Test\n    public void shouldAsyncRemoveWorkflow() throws Exception {\n        WorkflowSummary workflow = TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflow);\n\n        // wait for workflow to be indexed\n        List<String> workflows = tryFindResults(() -> searchWorkflows(workflow.getWorkflowId()), 1);\n        assertEquals(1, workflows.size());\n\n        indexDAO.asyncRemoveWorkflow(workflow.getWorkflowId()).get();\n\n        workflows = tryFindResults(() -> searchWorkflows(workflow.getWorkflowId()), 0);\n\n        assertTrue(\"Workflow was not removed.\", workflows.isEmpty());\n    }\n\n    @Test\n    public void shouldUpdateWorkflow() throws JsonProcessingException {\n        WorkflowSummary workflow = TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflow);\n\n        indexDAO.updateWorkflow(\n                workflow.getWorkflowId(),\n                new String[] {\"status\"},\n                new Object[] {WorkflowStatus.COMPLETED});\n\n        workflow.setStatus(WorkflowStatus.COMPLETED);\n        assertWorkflowSummary(workflow.getWorkflowId(), workflow);\n    }\n\n    @Test\n    public void shouldAsyncUpdateWorkflow() throws Exception {\n        WorkflowSummary workflow = TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflow);\n\n        indexDAO.asyncUpdateWorkflow(\n                        workflow.getWorkflowId(),\n                        new String[] {\"status\"},\n                        new Object[] {WorkflowStatus.FAILED})\n                .get();\n\n        workflow.setStatus(WorkflowStatus.FAILED);\n        assertWorkflowSummary(workflow.getWorkflowId(), workflow);\n    }\n\n    @Test\n    public void shouldIndexTask() {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary));\n\n        assertEquals(taskSummary.getTaskId(), tasks.get(0));\n    }\n\n    @Test\n    public void shouldIndexTaskAsync() throws Exception {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n\n        indexDAO.asyncIndexTask(taskSummary).get();\n\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary));\n\n        assertEquals(taskSummary.getTaskId(), tasks.get(0));\n    }\n\n    @Test\n    public void shouldRemoveTask() {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 1);\n\n        TaskSummary taskSummary =\n                TestUtils.loadTaskSnapshot(\n                        objectMapper, \"task_summary\", workflowSummary.getWorkflowId());\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.removeTask(workflowSummary.getWorkflowId(), taskSummary.getTaskId());\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertTrue(\"Task was not removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldAsyncRemoveTask() throws Exception {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 1);\n\n        TaskSummary taskSummary =\n                TestUtils.loadTaskSnapshot(\n                        objectMapper, \"task_summary\", workflowSummary.getWorkflowId());\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.asyncRemoveTask(workflowSummary.getWorkflowId(), taskSummary.getTaskId()).get();\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertTrue(\"Task was not removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldNotRemoveTaskWhenNotAssociatedWithWorkflow() {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.removeTask(\"InvalidWorkflow\", taskSummary.getTaskId());\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertFalse(\"Task was removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldNotAsyncRemoveTaskWhenNotAssociatedWithWorkflow() throws Exception {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.asyncRemoveTask(\"InvalidWorkflow\", taskSummary.getTaskId()).get();\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertFalse(\"Task was removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldAddTaskExecutionLogs() {\n        List<TaskExecLog> logs = new ArrayList<>();\n        String taskId = uuid();\n        logs.add(createLog(taskId, \"log1\"));\n        logs.add(createLog(taskId, \"log2\"));\n        logs.add(createLog(taskId, \"log3\"));\n\n        indexDAO.addTaskExecutionLogs(logs);\n\n        List<TaskExecLog> indexedLogs =\n                tryFindResults(() -> indexDAO.getTaskExecutionLogs(taskId), 3);\n\n        assertEquals(3, indexedLogs.size());\n\n        assertTrue(\"Not all logs was indexed\", indexedLogs.containsAll(logs));\n    }\n\n    @Test\n    public void shouldAddTaskExecutionLogsAsync() throws Exception {\n        List<TaskExecLog> logs = new ArrayList<>();\n        String taskId = uuid();\n        logs.add(createLog(taskId, \"log1\"));\n        logs.add(createLog(taskId, \"log2\"));\n        logs.add(createLog(taskId, \"log3\"));\n\n        indexDAO.asyncAddTaskExecutionLogs(logs).get();\n\n        List<TaskExecLog> indexedLogs =\n                tryFindResults(() -> indexDAO.getTaskExecutionLogs(taskId), 3);\n\n        assertEquals(3, indexedLogs.size());\n\n        assertTrue(\"Not all logs was indexed\", indexedLogs.containsAll(logs));\n    }\n\n    @Test\n    public void shouldAddMessage() {\n        String queue = \"queue\";\n        Message message1 = new Message(uuid(), \"payload1\", null);\n        Message message2 = new Message(uuid(), \"payload2\", null);\n\n        indexDAO.addMessage(queue, message1);\n        indexDAO.addMessage(queue, message2);\n\n        List<Message> indexedMessages = tryFindResults(() -> indexDAO.getMessages(queue), 2);\n\n        assertEquals(2, indexedMessages.size());\n\n        assertTrue(\n                \"Not all messages was indexed\",\n                indexedMessages.containsAll(Arrays.asList(message1, message2)));\n    }\n\n    @Test\n    public void shouldAddEventExecution() {\n        String event = \"event\";\n        EventExecution execution1 = createEventExecution(event);\n        EventExecution execution2 = createEventExecution(event);\n\n        indexDAO.addEventExecution(execution1);\n        indexDAO.addEventExecution(execution2);\n\n        List<EventExecution> indexedExecutions =\n                tryFindResults(() -> indexDAO.getEventExecutions(event), 2);\n\n        assertEquals(2, indexedExecutions.size());\n\n        assertTrue(\n                \"Not all event executions was indexed\",\n                indexedExecutions.containsAll(Arrays.asList(execution1, execution2)));\n    }\n\n    @Test\n    public void shouldAsyncAddEventExecution() throws Exception {\n        String event = \"event2\";\n        EventExecution execution1 = createEventExecution(event);\n        EventExecution execution2 = createEventExecution(event);\n\n        indexDAO.asyncAddEventExecution(execution1).get();\n        indexDAO.asyncAddEventExecution(execution2).get();\n\n        List<EventExecution> indexedExecutions =\n                tryFindResults(() -> indexDAO.getEventExecutions(event), 2);\n\n        assertEquals(2, indexedExecutions.size());\n\n        assertTrue(\n                \"Not all event executions was indexed\",\n                indexedExecutions.containsAll(Arrays.asList(execution1, execution2)));\n    }\n\n    @Test\n    public void shouldAddIndexPrefixToIndexTemplate() throws Exception {\n        String json = TestUtils.loadJsonResource(\"expected_template_task_log\");\n\n        String content = indexDAO.loadTypeMappingSource(\"/template_task_log.json\");\n\n        assertEquals(json, content);\n    }\n\n    @Test\n    public void shouldCountWorkflows() {\n        int counts = 1100;\n        for (int i = 0; i < counts; i++) {\n            WorkflowSummary workflow =\n                    TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n            indexDAO.indexWorkflow(workflow);\n        }\n\n        // wait for workflow to be indexed\n        long result = tryGetCount(() -> getWorkflowCount(\"template_workflow\", \"RUNNING\"), counts);\n        assertEquals(counts, result);\n    }\n\n    @Test\n    public void shouldFindWorkflow() {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        List<WorkflowSummary> workflows =\n                tryFindResults(() -> searchWorkflowSummary(workflowSummary.getWorkflowId()), 1);\n        assertEquals(1, workflows.size());\n        assertEquals(workflowSummary, workflows.get(0));\n    }\n\n    @Test\n    public void shouldFindTask() {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        List<TaskSummary> tasks = tryFindResults(() -> searchTaskSummary(taskSummary));\n        assertEquals(1, tasks.size());\n        assertEquals(taskSummary, tasks.get(0));\n    }\n\n    private long tryGetCount(Supplier<Long> countFunction, int resultsCount) {\n        long result = 0;\n        for (int i = 0; i < 20; i++) {\n            result = countFunction.get();\n            if (result == resultsCount) {\n                return result;\n            }\n            try {\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e.getMessage(), e);\n            }\n        }\n        return result;\n    }\n\n    // Get total workflow counts given the name and status\n    private long getWorkflowCount(String workflowName, String status) {\n        return indexDAO.getWorkflowCount(\n                \"status=\\\"\" + status + \"\\\" AND workflowType=\\\"\" + workflowName + \"\\\"\", \"*\");\n    }\n\n    private void assertWorkflowSummary(String workflowId, WorkflowSummary summary)\n            throws JsonProcessingException {\n        assertEquals(summary.getWorkflowType(), indexDAO.get(workflowId, \"workflowType\"));\n        assertEquals(String.valueOf(summary.getVersion()), indexDAO.get(workflowId, \"version\"));\n        assertEquals(summary.getWorkflowId(), indexDAO.get(workflowId, \"workflowId\"));\n        assertEquals(summary.getCorrelationId(), indexDAO.get(workflowId, \"correlationId\"));\n        assertEquals(summary.getStartTime(), indexDAO.get(workflowId, \"startTime\"));\n        assertEquals(summary.getUpdateTime(), indexDAO.get(workflowId, \"updateTime\"));\n        assertEquals(summary.getEndTime(), indexDAO.get(workflowId, \"endTime\"));\n        assertEquals(summary.getStatus().name(), indexDAO.get(workflowId, \"status\"));\n        assertEquals(summary.getInput(), indexDAO.get(workflowId, \"input\"));\n        assertEquals(summary.getOutput(), indexDAO.get(workflowId, \"output\"));\n        assertEquals(\n                summary.getReasonForIncompletion(),\n                indexDAO.get(workflowId, \"reasonForIncompletion\"));\n        assertEquals(\n                String.valueOf(summary.getExecutionTime()),\n                indexDAO.get(workflowId, \"executionTime\"));\n        assertEquals(summary.getEvent(), indexDAO.get(workflowId, \"event\"));\n        assertEquals(\n                summary.getFailedReferenceTaskNames(),\n                indexDAO.get(workflowId, \"failedReferenceTaskNames\"));\n        assertEquals(\n                summary.getFailedTaskNames(),\n                objectMapper.readValue(indexDAO.get(workflowId, \"failedTaskNames\"), Set.class));\n    }\n\n    private <T> List<T> tryFindResults(Supplier<List<T>> searchFunction) {\n        return tryFindResults(searchFunction, 1);\n    }\n\n    private <T> List<T> tryFindResults(Supplier<List<T>> searchFunction, int resultsCount) {\n        List<T> result = Collections.emptyList();\n        for (int i = 0; i < 20; i++) {\n            result = searchFunction.get();\n            if (result.size() == resultsCount) {\n                return result;\n            }\n            try {\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e.getMessage(), e);\n            }\n        }\n        return result;\n    }\n\n    private List<String> searchWorkflows(String workflowId) {\n        return indexDAO.searchWorkflows(\n                        \"\", \"workflowId:\\\"\" + workflowId + \"\\\"\", 0, 100, Collections.emptyList())\n                .getResults();\n    }\n\n    private List<WorkflowSummary> searchWorkflowSummary(String workflowId) {\n        return indexDAO.searchWorkflowSummary(\n                        \"\", \"workflowId:\\\"\" + workflowId + \"\\\"\", 0, 100, Collections.emptyList())\n                .getResults();\n    }\n\n    private List<String> searchTasks(TaskSummary taskSummary) {\n        return indexDAO.searchTasks(\n                        \"\",\n                        \"workflowId:\\\"\" + taskSummary.getWorkflowId() + \"\\\"\",\n                        0,\n                        100,\n                        Collections.emptyList())\n                .getResults();\n    }\n\n    private List<TaskSummary> searchTaskSummary(TaskSummary taskSummary) {\n        return indexDAO.searchTaskSummary(\n                        \"\",\n                        \"workflowId:\\\"\" + taskSummary.getWorkflowId() + \"\\\"\",\n                        0,\n                        100,\n                        Collections.emptyList())\n                .getResults();\n    }\n\n    private TaskExecLog createLog(String taskId, String log) {\n        TaskExecLog taskExecLog = new TaskExecLog(log);\n        taskExecLog.setTaskId(taskId);\n        return taskExecLog;\n    }\n\n    private EventExecution createEventExecution(String event) {\n        EventExecution execution = new EventExecution(uuid(), uuid());\n        execution.setName(\"name\");\n        execution.setEvent(event);\n        execution.setCreated(System.currentTimeMillis());\n        execution.setStatus(EventExecution.Status.COMPLETED);\n        execution.setAction(EventHandler.Action.Type.start_workflow);\n        execution.setOutput(ImmutableMap.of(\"a\", 1, \"b\", 2, \"c\", 3));\n        return execution;\n    }\n\n    private String uuid() {\n        return UUID.randomUUID().toString();\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/index/TestElasticSearchDAOV6Batch.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.util.HashMap;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\nimport org.springframework.test.context.TestPropertySource;\n\nimport com.netflix.conductor.common.metadata.tasks.Task.Status;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@TestPropertySource(properties = \"conductor.elasticsearch.indexBatchSize=2\")\npublic class TestElasticSearchDAOV6Batch extends ElasticSearchDaoBaseTest {\n\n    @Test\n    public void indexTaskWithBatchSizeTwo() {\n        String correlationId = \"some-correlation-id\";\n        TaskSummary taskSummary = new TaskSummary();\n        taskSummary.setTaskId(\"some-task-id\");\n        taskSummary.setWorkflowId(\"some-workflow-instance-id\");\n        taskSummary.setTaskType(\"some-task-type\");\n        taskSummary.setStatus(Status.FAILED);\n        try {\n            taskSummary.setInput(\n                    objectMapper.writeValueAsString(\n                            new HashMap<String, Object>() {\n                                {\n                                    put(\"input_key\", \"input_value\");\n                                }\n                            }));\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n        taskSummary.setCorrelationId(correlationId);\n        taskSummary.setTaskDefName(\"some-task-def-name\");\n        taskSummary.setReasonForIncompletion(\"some-failure-reason\");\n\n        indexDAO.indexTask(taskSummary);\n        indexDAO.indexTask(taskSummary);\n\n        await().atMost(5, TimeUnit.SECONDS)\n                .untilAsserted(\n                        () -> {\n                            SearchResult<String> result =\n                                    indexDAO.searchTasks(\n                                            \"correlationId='\" + correlationId + \"'\",\n                                            \"*\",\n                                            0,\n                                            10000,\n                                            null);\n\n                            assertTrue(\n                                    \"should return 1 or more search results\",\n                                    result.getResults().size() > 0);\n                            assertEquals(\n                                    \"taskId should match the indexed task\",\n                                    \"some-task-id\",\n                                    result.getResults().get(0));\n                        });\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/index/TestElasticSearchRestDAOV6.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.io.IOException;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport java.util.UUID;\nimport java.util.function.Supplier;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.Workflow.WorkflowStatus;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.events.queue.Message;\nimport com.netflix.conductor.es6.utils.TestUtils;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.google.common.collect.ImmutableMap;\n\nimport static org.junit.Assert.*;\n\npublic class TestElasticSearchRestDAOV6 extends ElasticSearchRestDaoBaseTest {\n\n    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(\"yyyyMMWW\");\n\n    private static final String INDEX_PREFIX = \"conductor\";\n    private static final String WORKFLOW_DOC_TYPE = \"workflow\";\n    private static final String TASK_DOC_TYPE = \"task\";\n    private static final String MSG_DOC_TYPE = \"message\";\n    private static final String EVENT_DOC_TYPE = \"event\";\n    private static final String LOG_INDEX_PREFIX = \"task_log\";\n\n    private boolean indexExists(final String index) throws IOException {\n        return indexDAO.doesResourceExist(\"/\" + index);\n    }\n\n    private boolean doesMappingExist(final String index, final String mappingName)\n            throws IOException {\n        return indexDAO.doesResourceExist(\"/\" + index + \"/_mapping/\" + mappingName);\n    }\n\n    @Test\n    public void assertInitialSetup() throws IOException {\n        SIMPLE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(\"GMT\"));\n\n        String workflowIndex = INDEX_PREFIX + \"_\" + WORKFLOW_DOC_TYPE;\n        String taskIndex = INDEX_PREFIX + \"_\" + TASK_DOC_TYPE;\n\n        String taskLogIndex =\n                INDEX_PREFIX + \"_\" + LOG_INDEX_PREFIX + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n        String messageIndex =\n                INDEX_PREFIX + \"_\" + MSG_DOC_TYPE + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n        String eventIndex =\n                INDEX_PREFIX + \"_\" + EVENT_DOC_TYPE + \"_\" + SIMPLE_DATE_FORMAT.format(new Date());\n\n        assertTrue(\"Index 'conductor_workflow' should exist\", indexExists(\"conductor_workflow\"));\n        assertTrue(\"Index 'conductor_task' should exist\", indexExists(\"conductor_task\"));\n\n        assertTrue(\"Index '\" + taskLogIndex + \"' should exist\", indexExists(taskLogIndex));\n        assertTrue(\"Index '\" + messageIndex + \"' should exist\", indexExists(messageIndex));\n        assertTrue(\"Index '\" + eventIndex + \"' should exist\", indexExists(eventIndex));\n\n        assertTrue(\n                \"Mapping 'workflow' for index 'conductor' should exist\",\n                doesMappingExist(workflowIndex, WORKFLOW_DOC_TYPE));\n        assertTrue(\n                \"Mapping 'task' for index 'conductor' should exist\",\n                doesMappingExist(taskIndex, TASK_DOC_TYPE));\n    }\n\n    @Test\n    public void shouldIndexWorkflow() throws JsonProcessingException {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        assertWorkflowSummary(workflowSummary.getWorkflowId(), workflowSummary);\n    }\n\n    @Test\n    public void shouldIndexWorkflowAsync() throws Exception {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.asyncIndexWorkflow(workflowSummary).get();\n\n        assertWorkflowSummary(workflowSummary.getWorkflowId(), workflowSummary);\n    }\n\n    @Test\n    public void shouldRemoveWorkflow() {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        List<String> workflows =\n                tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 1);\n        assertEquals(1, workflows.size());\n\n        indexDAO.removeWorkflow(workflowSummary.getWorkflowId());\n\n        workflows = tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 0);\n\n        assertTrue(\"Workflow was not removed.\", workflows.isEmpty());\n    }\n\n    @Test\n    public void shouldAsyncRemoveWorkflow() throws Exception {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        List<String> workflows =\n                tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 1);\n        assertEquals(1, workflows.size());\n\n        indexDAO.asyncRemoveWorkflow(workflowSummary.getWorkflowId()).get();\n\n        workflows = tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 0);\n\n        assertTrue(\"Workflow was not removed.\", workflows.isEmpty());\n    }\n\n    @Test\n    public void shouldUpdateWorkflow() throws JsonProcessingException {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        indexDAO.updateWorkflow(\n                workflowSummary.getWorkflowId(),\n                new String[] {\"status\"},\n                new Object[] {WorkflowStatus.COMPLETED});\n\n        workflowSummary.setStatus(WorkflowStatus.COMPLETED);\n        assertWorkflowSummary(workflowSummary.getWorkflowId(), workflowSummary);\n    }\n\n    @Test\n    public void shouldAsyncUpdateWorkflow() throws Exception {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        indexDAO.asyncUpdateWorkflow(\n                        workflowSummary.getWorkflowId(),\n                        new String[] {\"status\"},\n                        new Object[] {WorkflowStatus.FAILED})\n                .get();\n\n        workflowSummary.setStatus(WorkflowStatus.FAILED);\n        assertWorkflowSummary(workflowSummary.getWorkflowId(), workflowSummary);\n    }\n\n    @Test\n    public void shouldIndexTask() {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary));\n\n        assertEquals(taskSummary.getTaskId(), tasks.get(0));\n    }\n\n    @Test\n    public void shouldIndexTaskAsync() throws Exception {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.asyncIndexTask(taskSummary).get();\n\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary));\n\n        assertEquals(taskSummary.getTaskId(), tasks.get(0));\n    }\n\n    @Test\n    public void shouldRemoveTask() {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 1);\n\n        TaskSummary taskSummary =\n                TestUtils.loadTaskSnapshot(\n                        objectMapper, \"task_summary\", workflowSummary.getWorkflowId());\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.removeTask(workflowSummary.getWorkflowId(), taskSummary.getTaskId());\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertTrue(\"Task was not removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldAsyncRemoveTask() throws Exception {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        tryFindResults(() -> searchWorkflows(workflowSummary.getWorkflowId()), 1);\n\n        TaskSummary taskSummary =\n                TestUtils.loadTaskSnapshot(\n                        objectMapper, \"task_summary\", workflowSummary.getWorkflowId());\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.asyncRemoveTask(workflowSummary.getWorkflowId(), taskSummary.getTaskId()).get();\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertTrue(\"Task was not removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldNotRemoveTaskWhenNotAssociatedWithWorkflow() {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.removeTask(\"InvalidWorkflow\", taskSummary.getTaskId());\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertFalse(\"Task was removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldNotAsyncRemoveTaskWhenNotAssociatedWithWorkflow() throws Exception {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        // Wait for the task to be indexed\n        List<String> tasks = tryFindResults(() -> searchTasks(taskSummary), 1);\n\n        indexDAO.asyncRemoveTask(\"InvalidWorkflow\", taskSummary.getTaskId()).get();\n\n        tasks = tryFindResults(() -> searchTasks(taskSummary), 0);\n\n        assertFalse(\"Task was removed.\", tasks.isEmpty());\n    }\n\n    @Test\n    public void shouldAddTaskExecutionLogs() {\n        List<TaskExecLog> logs = new ArrayList<>();\n        String taskId = uuid();\n        logs.add(createLog(taskId, \"log1\"));\n        logs.add(createLog(taskId, \"log2\"));\n        logs.add(createLog(taskId, \"log3\"));\n\n        indexDAO.addTaskExecutionLogs(logs);\n\n        List<TaskExecLog> indexedLogs =\n                tryFindResults(() -> indexDAO.getTaskExecutionLogs(taskId), 3);\n\n        assertEquals(3, indexedLogs.size());\n\n        assertTrue(\"Not all logs was indexed\", indexedLogs.containsAll(logs));\n    }\n\n    @Test\n    public void shouldAddTaskExecutionLogsAsync() throws Exception {\n        List<TaskExecLog> logs = new ArrayList<>();\n        String taskId = uuid();\n        logs.add(createLog(taskId, \"log1\"));\n        logs.add(createLog(taskId, \"log2\"));\n        logs.add(createLog(taskId, \"log3\"));\n\n        indexDAO.asyncAddTaskExecutionLogs(logs).get();\n\n        List<TaskExecLog> indexedLogs =\n                tryFindResults(() -> indexDAO.getTaskExecutionLogs(taskId), 3);\n\n        assertEquals(3, indexedLogs.size());\n\n        assertTrue(\"Not all logs was indexed\", indexedLogs.containsAll(logs));\n    }\n\n    @Test\n    public void shouldAddMessage() {\n        String queue = \"queue\";\n        Message message1 = new Message(uuid(), \"payload1\", null);\n        Message message2 = new Message(uuid(), \"payload2\", null);\n\n        indexDAO.addMessage(queue, message1);\n        indexDAO.addMessage(queue, message2);\n\n        List<Message> indexedMessages = tryFindResults(() -> indexDAO.getMessages(queue), 2);\n\n        assertEquals(2, indexedMessages.size());\n\n        assertTrue(\n                \"Not all messages was indexed\",\n                indexedMessages.containsAll(Arrays.asList(message1, message2)));\n    }\n\n    @Test\n    public void shouldAddEventExecution() {\n        String event = \"event\";\n        EventExecution execution1 = createEventExecution(event);\n        EventExecution execution2 = createEventExecution(event);\n\n        indexDAO.addEventExecution(execution1);\n        indexDAO.addEventExecution(execution2);\n\n        List<EventExecution> indexedExecutions =\n                tryFindResults(() -> indexDAO.getEventExecutions(event), 2);\n\n        assertEquals(2, indexedExecutions.size());\n\n        assertTrue(\n                \"Not all event executions was indexed\",\n                indexedExecutions.containsAll(Arrays.asList(execution1, execution2)));\n    }\n\n    @Test\n    public void shouldAsyncAddEventExecution() throws Exception {\n        String event = \"event2\";\n        EventExecution execution1 = createEventExecution(event);\n        EventExecution execution2 = createEventExecution(event);\n\n        indexDAO.asyncAddEventExecution(execution1).get();\n        indexDAO.asyncAddEventExecution(execution2).get();\n\n        List<EventExecution> indexedExecutions =\n                tryFindResults(() -> indexDAO.getEventExecutions(event), 2);\n\n        assertEquals(2, indexedExecutions.size());\n\n        assertTrue(\n                \"Not all event executions was indexed\",\n                indexedExecutions.containsAll(Arrays.asList(execution1, execution2)));\n    }\n\n    @Test\n    public void shouldAddIndexPrefixToIndexTemplate() throws Exception {\n        String json = TestUtils.loadJsonResource(\"expected_template_task_log\");\n\n        String content = indexDAO.loadTypeMappingSource(\"/template_task_log.json\");\n\n        assertEquals(json, content);\n    }\n\n    @Test\n    public void shouldCountWorkflows() {\n        int counts = 1100;\n        for (int i = 0; i < counts; i++) {\n            WorkflowSummary workflowSummary =\n                    TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n            indexDAO.indexWorkflow(workflowSummary);\n        }\n\n        // wait for workflow to be indexed\n        long result = tryGetCount(() -> getWorkflowCount(\"template_workflow\", \"RUNNING\"), counts);\n        assertEquals(counts, result);\n    }\n\n    @Test\n    public void shouldFindWorkflow() {\n        WorkflowSummary workflowSummary =\n                TestUtils.loadWorkflowSnapshot(objectMapper, \"workflow_summary\");\n        indexDAO.indexWorkflow(workflowSummary);\n\n        // wait for workflow to be indexed\n        List<WorkflowSummary> workflows =\n                tryFindResults(() -> searchWorkflowSummary(workflowSummary.getWorkflowId()), 1);\n        assertEquals(1, workflows.size());\n        assertEquals(workflowSummary, workflows.get(0));\n    }\n\n    @Test\n    public void shouldFindTask() {\n        TaskSummary taskSummary = TestUtils.loadTaskSnapshot(objectMapper, \"task_summary\");\n        indexDAO.indexTask(taskSummary);\n\n        List<TaskSummary> tasks = tryFindResults(() -> searchTaskSummary(taskSummary));\n        assertEquals(1, tasks.size());\n        assertEquals(taskSummary, tasks.get(0));\n    }\n\n    private long tryGetCount(Supplier<Long> countFunction, int resultsCount) {\n        long result = 0;\n        for (int i = 0; i < 20; i++) {\n            result = countFunction.get();\n            if (result == resultsCount) {\n                return result;\n            }\n            try {\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e.getMessage(), e);\n            }\n        }\n        return result;\n    }\n\n    // Get total workflow counts given the name and status\n    private long getWorkflowCount(String workflowName, String status) {\n        return indexDAO.getWorkflowCount(\n                \"status=\\\"\" + status + \"\\\" AND workflowType=\\\"\" + workflowName + \"\\\"\", \"*\");\n    }\n\n    private void assertWorkflowSummary(String workflowId, WorkflowSummary summary)\n            throws JsonProcessingException {\n        assertEquals(summary.getWorkflowType(), indexDAO.get(workflowId, \"workflowType\"));\n        assertEquals(String.valueOf(summary.getVersion()), indexDAO.get(workflowId, \"version\"));\n        assertEquals(summary.getWorkflowId(), indexDAO.get(workflowId, \"workflowId\"));\n        assertEquals(summary.getCorrelationId(), indexDAO.get(workflowId, \"correlationId\"));\n        assertEquals(summary.getStartTime(), indexDAO.get(workflowId, \"startTime\"));\n        assertEquals(summary.getUpdateTime(), indexDAO.get(workflowId, \"updateTime\"));\n        assertEquals(summary.getEndTime(), indexDAO.get(workflowId, \"endTime\"));\n        assertEquals(summary.getStatus().name(), indexDAO.get(workflowId, \"status\"));\n        assertEquals(summary.getInput(), indexDAO.get(workflowId, \"input\"));\n        assertEquals(summary.getOutput(), indexDAO.get(workflowId, \"output\"));\n        assertEquals(\n                summary.getReasonForIncompletion(),\n                indexDAO.get(workflowId, \"reasonForIncompletion\"));\n        assertEquals(\n                String.valueOf(summary.getExecutionTime()),\n                indexDAO.get(workflowId, \"executionTime\"));\n        assertEquals(summary.getEvent(), indexDAO.get(workflowId, \"event\"));\n        assertEquals(\n                summary.getFailedReferenceTaskNames(),\n                indexDAO.get(workflowId, \"failedReferenceTaskNames\"));\n        assertEquals(\n                summary.getFailedTaskNames(),\n                objectMapper.readValue(indexDAO.get(workflowId, \"failedTaskNames\"), Set.class));\n    }\n\n    private <T> List<T> tryFindResults(Supplier<List<T>> searchFunction) {\n        return tryFindResults(searchFunction, 1);\n    }\n\n    private <T> List<T> tryFindResults(Supplier<List<T>> searchFunction, int resultsCount) {\n        List<T> result = Collections.emptyList();\n        for (int i = 0; i < 20; i++) {\n            result = searchFunction.get();\n            if (result.size() == resultsCount) {\n                return result;\n            }\n            try {\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e.getMessage(), e);\n            }\n        }\n        return result;\n    }\n\n    private List<String> searchWorkflows(String workflowId) {\n        return indexDAO.searchWorkflows(\n                        \"\", \"workflowId:\\\"\" + workflowId + \"\\\"\", 0, 100, Collections.emptyList())\n                .getResults();\n    }\n\n    private List<WorkflowSummary> searchWorkflowSummary(String workflowId) {\n        return indexDAO.searchWorkflowSummary(\n                        \"\", \"workflowId:\\\"\" + workflowId + \"\\\"\", 0, 100, Collections.emptyList())\n                .getResults();\n    }\n\n    private List<String> searchWorkflows(String workflowName, String status) {\n        List<String> sortOptions = new ArrayList<>();\n        sortOptions.add(\"startTime:DESC\");\n        return indexDAO.searchWorkflows(\n                        \"status=\\\"\" + status + \"\\\" AND workflowType=\\\"\" + workflowName + \"\\\"\",\n                        \"*\",\n                        0,\n                        1000,\n                        sortOptions)\n                .getResults();\n    }\n\n    private List<String> searchTasks(TaskSummary taskSummary) {\n        return indexDAO.searchTasks(\n                        \"\",\n                        \"workflowId:\\\"\" + taskSummary.getWorkflowId() + \"\\\"\",\n                        0,\n                        100,\n                        Collections.emptyList())\n                .getResults();\n    }\n\n    private List<TaskSummary> searchTaskSummary(TaskSummary taskSummary) {\n        return indexDAO.searchTaskSummary(\n                        \"\",\n                        \"workflowId:\\\"\" + taskSummary.getWorkflowId() + \"\\\"\",\n                        0,\n                        100,\n                        Collections.emptyList())\n                .getResults();\n    }\n\n    private TaskExecLog createLog(String taskId, String log) {\n        TaskExecLog taskExecLog = new TaskExecLog(log);\n        taskExecLog.setTaskId(taskId);\n        return taskExecLog;\n    }\n\n    private EventExecution createEventExecution(String event) {\n        EventExecution execution = new EventExecution(uuid(), uuid());\n        execution.setName(\"name\");\n        execution.setEvent(event);\n        execution.setCreated(System.currentTimeMillis());\n        execution.setStatus(EventExecution.Status.COMPLETED);\n        execution.setAction(EventHandler.Action.Type.start_workflow);\n        execution.setOutput(ImmutableMap.of(\"a\", 1, \"b\", 2, \"c\", 3));\n        return execution;\n    }\n\n    private String uuid() {\n        return UUID.randomUUID().toString();\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/index/TestElasticSearchRestDAOV6Batch.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.index;\n\nimport java.util.HashMap;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.Test;\nimport org.springframework.test.context.TestPropertySource;\n\nimport com.netflix.conductor.common.metadata.tasks.Task.Status;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n@TestPropertySource(properties = \"conductor.elasticsearch.indexBatchSize=2\")\npublic class TestElasticSearchRestDAOV6Batch extends ElasticSearchRestDaoBaseTest {\n\n    @Test\n    public void indexTaskWithBatchSizeTwo() {\n        String correlationId = \"some-correlation-id\";\n        TaskSummary taskSummary = new TaskSummary();\n        taskSummary.setTaskId(\"some-task-id\");\n        taskSummary.setWorkflowId(\"some-workflow-instance-id\");\n        taskSummary.setTaskType(\"some-task-type\");\n        taskSummary.setStatus(Status.FAILED);\n        try {\n            taskSummary.setInput(\n                    objectMapper.writeValueAsString(\n                            new HashMap<String, Object>() {\n                                {\n                                    put(\"input_key\", \"input_value\");\n                                }\n                            }));\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n        taskSummary.setCorrelationId(correlationId);\n        taskSummary.setTaskDefName(\"some-task-def-name\");\n        taskSummary.setReasonForIncompletion(\"some-failure-reason\");\n\n        indexDAO.indexTask(taskSummary);\n        indexDAO.indexTask(taskSummary);\n\n        await().atMost(5, TimeUnit.SECONDS)\n                .untilAsserted(\n                        () -> {\n                            SearchResult<String> result =\n                                    indexDAO.searchTasks(\n                                            \"correlationId='\" + correlationId + \"'\",\n                                            \"*\",\n                                            0,\n                                            10000,\n                                            null);\n\n                            assertTrue(\n                                    \"should return 1 or more search results\",\n                                    result.getResults().size() > 0);\n                            assertEquals(\n                                    \"taskId should match the indexed task\",\n                                    \"some-task-id\",\n                                    result.getResults().get(0));\n                        });\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/query/parser/TestExpression.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.es6.dao.query.parser.internal.ConstValue;\nimport com.netflix.conductor.es6.dao.query.parser.internal.TestAbstractParser;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class TestExpression extends TestAbstractParser {\n\n    @Test\n    public void test() throws Exception {\n        String test =\n                \"type='IMAGE' AND subType\t='sdp' AND (metadata.width > 50 OR metadata.height > 50)\";\n        InputStream inputStream =\n                new BufferedInputStream(new ByteArrayInputStream(test.getBytes()));\n        Expression expression = new Expression(inputStream);\n\n        assertTrue(expression.isBinaryExpr());\n        assertNull(expression.getGroupedExpression());\n        assertNotNull(expression.getNameValue());\n\n        NameValue nameValue = expression.getNameValue();\n        assertEquals(\"type\", nameValue.getName().getName());\n        assertEquals(\"=\", nameValue.getOp().getOperator());\n        assertEquals(\"\\\"IMAGE\\\"\", nameValue.getValue().getValue());\n\n        Expression rightHandSide = expression.getRightHandSide();\n        assertNotNull(rightHandSide);\n        assertTrue(rightHandSide.isBinaryExpr());\n\n        nameValue = rightHandSide.getNameValue();\n        assertNotNull(nameValue); // subType = sdp\n        assertNull(rightHandSide.getGroupedExpression());\n        assertEquals(\"subType\", nameValue.getName().getName());\n        assertEquals(\"=\", nameValue.getOp().getOperator());\n        assertEquals(\"\\\"sdp\\\"\", nameValue.getValue().getValue());\n\n        assertEquals(\"AND\", rightHandSide.getOperator().getOperator());\n        rightHandSide = rightHandSide.getRightHandSide();\n        assertNotNull(rightHandSide);\n        assertFalse(rightHandSide.isBinaryExpr());\n        GroupedExpression groupedExpression = rightHandSide.getGroupedExpression();\n        assertNotNull(groupedExpression);\n        expression = groupedExpression.getExpression();\n        assertNotNull(expression);\n\n        assertTrue(expression.isBinaryExpr());\n        nameValue = expression.getNameValue();\n        assertNotNull(nameValue);\n        assertEquals(\"metadata.width\", nameValue.getName().getName());\n        assertEquals(\">\", nameValue.getOp().getOperator());\n        assertEquals(\"50\", nameValue.getValue().getValue());\n\n        assertEquals(\"OR\", expression.getOperator().getOperator());\n        rightHandSide = expression.getRightHandSide();\n        assertNotNull(rightHandSide);\n        assertFalse(rightHandSide.isBinaryExpr());\n        nameValue = rightHandSide.getNameValue();\n        assertNotNull(nameValue);\n\n        assertEquals(\"metadata.height\", nameValue.getName().getName());\n        assertEquals(\">\", nameValue.getOp().getOperator());\n        assertEquals(\"50\", nameValue.getValue().getValue());\n    }\n\n    @Test\n    public void testWithSysConstants() throws Exception {\n        String test = \"type='IMAGE' AND subType\t='sdp' AND description IS null\";\n        InputStream inputStream =\n                new BufferedInputStream(new ByteArrayInputStream(test.getBytes()));\n        Expression expression = new Expression(inputStream);\n\n        assertTrue(expression.isBinaryExpr());\n        assertNull(expression.getGroupedExpression());\n        assertNotNull(expression.getNameValue());\n\n        NameValue nameValue = expression.getNameValue();\n        assertEquals(\"type\", nameValue.getName().getName());\n        assertEquals(\"=\", nameValue.getOp().getOperator());\n        assertEquals(\"\\\"IMAGE\\\"\", nameValue.getValue().getValue());\n\n        Expression rightHandSide = expression.getRightHandSide();\n        assertNotNull(rightHandSide);\n        assertTrue(rightHandSide.isBinaryExpr());\n\n        nameValue = rightHandSide.getNameValue();\n        assertNotNull(nameValue); // subType = sdp\n        assertNull(rightHandSide.getGroupedExpression());\n        assertEquals(\"subType\", nameValue.getName().getName());\n        assertEquals(\"=\", nameValue.getOp().getOperator());\n        assertEquals(\"\\\"sdp\\\"\", nameValue.getValue().getValue());\n\n        assertEquals(\"AND\", rightHandSide.getOperator().getOperator());\n        rightHandSide = rightHandSide.getRightHandSide();\n        assertNotNull(rightHandSide);\n        assertFalse(rightHandSide.isBinaryExpr());\n\n        GroupedExpression groupedExpression = rightHandSide.getGroupedExpression();\n        assertNull(groupedExpression);\n        nameValue = rightHandSide.getNameValue();\n        assertNotNull(nameValue);\n        assertEquals(\"description\", nameValue.getName().getName());\n        assertEquals(\"IS\", nameValue.getOp().getOperator());\n\n        ConstValue constValue = nameValue.getValue();\n        assertNotNull(constValue);\n        assertEquals(constValue.getSysConstant(), ConstValue.SystemConsts.NULL);\n\n        test = \"description IS not null\";\n        inputStream = new BufferedInputStream(new ByteArrayInputStream(test.getBytes()));\n        expression = new Expression(inputStream);\n\n        nameValue = expression.getNameValue();\n        assertNotNull(nameValue);\n        assertEquals(\"description\", nameValue.getName().getName());\n        assertEquals(\"IS\", nameValue.getOp().getOperator());\n\n        constValue = nameValue.getValue();\n        assertNotNull(constValue);\n        assertEquals(constValue.getSysConstant(), ConstValue.SystemConsts.NOT_NULL);\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/query/parser/internal/TestAbstractParser.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\n\npublic abstract class TestAbstractParser {\n\n    protected InputStream getInputStream(String expression) {\n        return new BufferedInputStream(new ByteArrayInputStream(expression.getBytes()));\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/query/parser/internal/TestBooleanOp.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class TestBooleanOp extends TestAbstractParser {\n\n    @Test\n    public void test() throws Exception {\n        String[] tests = new String[] {\"AND\", \"OR\"};\n        for (String test : tests) {\n            BooleanOp name = new BooleanOp(getInputStream(test));\n            String nameVal = name.getOperator();\n            assertNotNull(nameVal);\n            assertEquals(test, nameVal);\n        }\n    }\n\n    @Test(expected = ParserException.class)\n    public void testInvalid() throws Exception {\n        String test = \"<\";\n        BooleanOp name = new BooleanOp(getInputStream(test));\n        String nameVal = name.getOperator();\n        assertNotNull(nameVal);\n        assertEquals(test, nameVal);\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/query/parser/internal/TestComparisonOp.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class TestComparisonOp extends TestAbstractParser {\n\n    @Test\n    public void test() throws Exception {\n        String[] tests = new String[] {\"<\", \">\", \"=\", \"!=\", \"IN\", \"BETWEEN\", \"STARTS_WITH\"};\n        for (String test : tests) {\n            ComparisonOp name = new ComparisonOp(getInputStream(test));\n            String nameVal = name.getOperator();\n            assertNotNull(nameVal);\n            assertEquals(test, nameVal);\n        }\n    }\n\n    @Test(expected = ParserException.class)\n    public void testInvalidOp() throws Exception {\n        String test = \"AND\";\n        ComparisonOp name = new ComparisonOp(getInputStream(test));\n        String nameVal = name.getOperator();\n        assertNotNull(nameVal);\n        assertEquals(test, nameVal);\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/query/parser/internal/TestConstValue.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\npublic class TestConstValue extends TestAbstractParser {\n\n    @Test\n    public void testStringConst() throws Exception {\n        String test = \"'string value'\";\n        String expected =\n                test.replaceAll(\n                        \"'\", \"\\\"\"); // Quotes are removed but then the result is double quoted.\n        ConstValue constValue = new ConstValue(getInputStream(test));\n        assertNotNull(constValue.getValue());\n        assertEquals(expected, constValue.getValue());\n        assertTrue(constValue.getValue() instanceof String);\n\n        test = \"\\\"string value\\\"\";\n        constValue = new ConstValue(getInputStream(test));\n        assertNotNull(constValue.getValue());\n        assertEquals(expected, constValue.getValue());\n        assertTrue(constValue.getValue() instanceof String);\n    }\n\n    @Test\n    public void testSystemConst() throws Exception {\n        String test = \"null\";\n        ConstValue constValue = new ConstValue(getInputStream(test));\n        assertNotNull(constValue.getValue());\n        assertTrue(constValue.getValue() instanceof String);\n        assertEquals(constValue.getSysConstant(), ConstValue.SystemConsts.NULL);\n\n        test = \"not null\";\n        constValue = new ConstValue(getInputStream(test));\n        assertNotNull(constValue.getValue());\n        assertEquals(constValue.getSysConstant(), ConstValue.SystemConsts.NOT_NULL);\n    }\n\n    @Test(expected = ParserException.class)\n    public void testInvalid() throws Exception {\n        String test = \"'string value\";\n        new ConstValue(getInputStream(test));\n    }\n\n    @Test\n    public void testNumConst() throws Exception {\n        String test = \"12345.89\";\n        ConstValue cv = new ConstValue(getInputStream(test));\n        assertNotNull(cv.getValue());\n        assertTrue(\n                cv.getValue()\n                        instanceof\n                        String); // Numeric values are stored as string as we are just passing thru\n        // them to ES\n        assertEquals(test, cv.getValue());\n    }\n\n    @Test\n    public void testRange() throws Exception {\n        String test = \"50 AND 100\";\n        Range range = new Range(getInputStream(test));\n        assertEquals(\"50\", range.getLow());\n        assertEquals(\"100\", range.getHigh());\n    }\n\n    @Test(expected = ParserException.class)\n    public void testBadRange() throws Exception {\n        String test = \"50 AND\";\n        new Range(getInputStream(test));\n    }\n\n    @Test\n    public void testArray() throws Exception {\n        String test = \"(1, 3, 'name', 'value2')\";\n        ListConst listConst = new ListConst(getInputStream(test));\n        List<Object> list = listConst.getList();\n        assertEquals(4, list.size());\n        assertTrue(list.contains(\"1\"));\n        assertEquals(\"'value2'\", list.get(3)); // Values are preserved as it is...\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/dao/query/parser/internal/TestName.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.dao.query.parser.internal;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\npublic class TestName extends TestAbstractParser {\n\n    @Test\n    public void test() throws Exception {\n        String test = \"metadata.en_US.lang\t\t\";\n        Name name = new Name(getInputStream(test));\n        String nameVal = name.getName();\n        assertNotNull(nameVal);\n        assertEquals(test.trim(), nameVal);\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/java/com/netflix/conductor/es6/utils/TestUtils.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.es6.utils;\n\nimport java.nio.charset.StandardCharsets;\n\nimport org.apache.commons.io.FileUtils;\nimport org.springframework.util.ResourceUtils;\n\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.utils.IDGenerator;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic class TestUtils {\n\n    private static final String WORKFLOW_INSTANCE_ID_PLACEHOLDER = \"WORKFLOW_INSTANCE_ID\";\n\n    public static WorkflowSummary loadWorkflowSnapshot(\n            ObjectMapper objectMapper, String resourceFileName) {\n        try {\n            String content = loadJsonResource(resourceFileName);\n            String workflowId = new IDGenerator().generate();\n            content = content.replace(WORKFLOW_INSTANCE_ID_PLACEHOLDER, workflowId);\n\n            return objectMapper.readValue(content, WorkflowSummary.class);\n        } catch (Exception e) {\n            throw new RuntimeException(e.getMessage(), e);\n        }\n    }\n\n    public static TaskSummary loadTaskSnapshot(ObjectMapper objectMapper, String resourceFileName) {\n        try {\n            String content = loadJsonResource(resourceFileName);\n            String workflowId = new IDGenerator().generate();\n            content = content.replace(WORKFLOW_INSTANCE_ID_PLACEHOLDER, workflowId);\n\n            return objectMapper.readValue(content, TaskSummary.class);\n        } catch (Exception e) {\n            throw new RuntimeException(e.getMessage(), e);\n        }\n    }\n\n    public static TaskSummary loadTaskSnapshot(\n            ObjectMapper objectMapper, String resourceFileName, String workflowId) {\n        try {\n            String content = loadJsonResource(resourceFileName);\n            content = content.replace(WORKFLOW_INSTANCE_ID_PLACEHOLDER, workflowId);\n\n            return objectMapper.readValue(content, TaskSummary.class);\n        } catch (Exception e) {\n            throw new RuntimeException(e.getMessage(), e);\n        }\n    }\n\n    public static String loadJsonResource(String resourceFileName) {\n        try {\n            return FileUtils.readFileToString(\n                    ResourceUtils.getFile(\"classpath:\" + resourceFileName + \".json\"),\n                    StandardCharsets.UTF_8);\n        } catch (Exception e) {\n            throw new RuntimeException(e.getMessage(), e);\n        }\n    }\n}\n"
  },
  {
    "path": "es6-persistence/src/test/resources/expected_template_task_log.json",
    "content": "{\n  \"order\": 0,\n  \"template\": \"*conductor_task*log*\",\n  \"settings\": {\n    \"index\": {\n      \"refresh_interval\": \"1s\"\n    }\n  },\n  \"mappings\": {\n    \"task_log\": {\n      \"properties\": {\n        \"createdTime\": {\n          \"type\": \"long\"\n        },\n        \"log\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        },\n        \"taskId\": {\n          \"type\": \"keyword\",\n          \"index\": true\n        }\n      }\n    }\n  },\n  \"aliases\": {}\n}"
  },
  {
    "path": "es6-persistence/src/test/resources/task_summary.json",
    "content": "{\n  \"taskId\": \"9dea4567-0240-4eab-bde8-99f4535ea3fc\",\n  \"taskDefName\": \"templated_task\",\n  \"taskType\": \"templated_task\",\n  \"workflowId\": \"WORKFLOW_INSTANCE_ID\",\n  \"workflowType\": \"template_workflow\",\n  \"correlationId\": \"testTaskDefTemplate\",\n  \"scheduledTime\": \"2021-08-22T05:18:25.121Z\",\n  \"startTime\": \"0\",\n  \"endTime\": \"0\",\n  \"updateTime\": \"2021-08-23T00:18:25.121Z\",\n  \"status\": \"SCHEDULED\",\n  \"workflowPriority\": 1,\n  \"queueWaitTime\": 0,\n  \"executionTime\": 0,\n  \"input\": \"{http_request={method=GET, vipStack=test_stack, body={requestDetails={key1=value1, key2=42}, outputPath=s3://bucket/outputPath, inputPaths=[file://path1, file://path2]}, uri=/get/something}}\"\n}"
  },
  {
    "path": "es6-persistence/src/test/resources/workflow_summary.json",
    "content": "{\n  \"workflowType\": \"template_workflow\",\n  \"version\": 1,\n  \"workflowId\": \"WORKFLOW_INSTANCE_ID\",\n  \"priority\": 1,\n  \"correlationId\": \"testTaskDefTemplate\",\n  \"startTime\": 1534983505050,\n  \"updateTime\": 1534983505131,\n  \"endTime\": 0,\n  \"status\": \"RUNNING\",\n  \"input\": \"{path1=file://path1, path2=file://path2, requestDetails={key1=value1, key2=42}, outputPath=s3://bucket/outputPath}\"\n}\n"
  },
  {
    "path": "family.properties",
    "content": "generation=1\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionSha256Sum=a01b6587e15fe7ed120a0ee299c25982a1eee045abd6a9dd5e216b2f628ef9ac\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.6.2-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=${0##*/}\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "grpc/build.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\nbuildscript {\n    dependencies {\n        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.19'\n    }\n}\n\nplugins {\n    id 'java'\n    id 'idea'\n    id \"com.google.protobuf\" version \"0.8.19\"\n}\n\nrepositories{\n    maven { url \"https://mvnrepository.com/artifact\" }\n}\n\ndependencies {\n    implementation project(':conductor-common')\n\n    implementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n    implementation \"io.grpc:grpc-protobuf:${revGrpc}\"\n    implementation \"io.grpc:grpc-stub:${revGrpc}\"\n    implementation \"javax.annotation:javax.annotation-api:1.3.2\"\n}\n\ndef artifactName = 'com.google.protobuf:protoc:3.14.0:osx-x86_64'\nswitch (org.gradle.internal.os.OperatingSystem.current()) {\n    case org.gradle.internal.os.OperatingSystem.LINUX:\n        artifactName = \"com.google.protobuf:protoc:3.21.12\"\n        break;\n    case org.gradle.internal.os.OperatingSystem.MAC_OS:\n        artifactName = \"com.google.protobuf:protoc:3.14.0:osx-x86_64\"\n        break;\n    case org.gradle.internal.os.OperatingSystem.WINDOWS:\n        artifactName = \"com.google.protobuf:protoc:3.21.12\"\n        break;\n}\n\nprotobuf {\n    protoc {\n        artifact = artifactName\n    }\n    plugins {\n        grpc {\n            artifact = \"io.grpc:protoc-gen-grpc-java:${revGrpc}\"\n        }\n    }\n    generateProtoTasks {\n        processResources.dependsOn extractProto\n        all()*.plugins {\n            grpc {}\n        }\n    }\n}\n\nidea {\n    module {\n        sourceDirs += file(\"${projectDir}/build/generated/source/proto/main/java\");\n        sourceDirs += file(\"${projectDir}/build/generated/source/proto/main/grpc\");\n    }\n}\n\nsourceSets {\n  main {\n    java {\n        srcDir 'build/generated/source/proto/main/java'\n        srcDir 'build/generated/source/proto/main/grpc'\n    }\n  }\n}\n\ncompileJava.dependsOn(tasks.getByPath(':conductor-common:protogen'))\n"
  },
  {
    "path": "grpc/src/main/java/com/netflix/conductor/grpc/AbstractProtoMapper.java",
    "content": "package com.netflix.conductor.grpc;\n\nimport com.google.protobuf.Any;\nimport com.google.protobuf.Value;\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.workflow.DynamicForkJoinTask;\nimport com.netflix.conductor.common.metadata.workflow.DynamicForkJoinTaskList;\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.SkipTaskRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDefSummary;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.proto.DynamicForkJoinTaskListPb;\nimport com.netflix.conductor.proto.DynamicForkJoinTaskPb;\nimport com.netflix.conductor.proto.EventExecutionPb;\nimport com.netflix.conductor.proto.EventHandlerPb;\nimport com.netflix.conductor.proto.PollDataPb;\nimport com.netflix.conductor.proto.RerunWorkflowRequestPb;\nimport com.netflix.conductor.proto.SkipTaskRequestPb;\nimport com.netflix.conductor.proto.StartWorkflowRequestPb;\nimport com.netflix.conductor.proto.SubWorkflowParamsPb;\nimport com.netflix.conductor.proto.TaskDefPb;\nimport com.netflix.conductor.proto.TaskExecLogPb;\nimport com.netflix.conductor.proto.TaskPb;\nimport com.netflix.conductor.proto.TaskResultPb;\nimport com.netflix.conductor.proto.TaskSummaryPb;\nimport com.netflix.conductor.proto.WorkflowDefPb;\nimport com.netflix.conductor.proto.WorkflowDefSummaryPb;\nimport com.netflix.conductor.proto.WorkflowPb;\nimport com.netflix.conductor.proto.WorkflowSummaryPb;\nimport com.netflix.conductor.proto.WorkflowTaskPb;\nimport java.lang.IllegalArgumentException;\nimport java.lang.Object;\nimport java.lang.String;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport javax.annotation.Generated;\n\n@Generated(\"com.netflix.conductor.annotationsprocessor.protogen\")\npublic abstract class AbstractProtoMapper {\n    public DynamicForkJoinTaskPb.DynamicForkJoinTask toProto(DynamicForkJoinTask from) {\n        DynamicForkJoinTaskPb.DynamicForkJoinTask.Builder to = DynamicForkJoinTaskPb.DynamicForkJoinTask.newBuilder();\n        if (from.getTaskName() != null) {\n            to.setTaskName( from.getTaskName() );\n        }\n        if (from.getWorkflowName() != null) {\n            to.setWorkflowName( from.getWorkflowName() );\n        }\n        if (from.getReferenceName() != null) {\n            to.setReferenceName( from.getReferenceName() );\n        }\n        for (Map.Entry<String, Object> pair : from.getInput().entrySet()) {\n            to.putInput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getType() != null) {\n            to.setType( from.getType() );\n        }\n        return to.build();\n    }\n\n    public DynamicForkJoinTask fromProto(DynamicForkJoinTaskPb.DynamicForkJoinTask from) {\n        DynamicForkJoinTask to = new DynamicForkJoinTask();\n        to.setTaskName( from.getTaskName() );\n        to.setWorkflowName( from.getWorkflowName() );\n        to.setReferenceName( from.getReferenceName() );\n        Map<String, Object> inputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputMap().entrySet()) {\n            inputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInput(inputMap);\n        to.setType( from.getType() );\n        return to;\n    }\n\n    public DynamicForkJoinTaskListPb.DynamicForkJoinTaskList toProto(DynamicForkJoinTaskList from) {\n        DynamicForkJoinTaskListPb.DynamicForkJoinTaskList.Builder to = DynamicForkJoinTaskListPb.DynamicForkJoinTaskList.newBuilder();\n        for (DynamicForkJoinTask elem : from.getDynamicTasks()) {\n            to.addDynamicTasks( toProto(elem) );\n        }\n        return to.build();\n    }\n\n    public DynamicForkJoinTaskList fromProto(\n            DynamicForkJoinTaskListPb.DynamicForkJoinTaskList from) {\n        DynamicForkJoinTaskList to = new DynamicForkJoinTaskList();\n        to.setDynamicTasks( from.getDynamicTasksList().stream().map(this::fromProto).collect(Collectors.toCollection(ArrayList::new)) );\n        return to;\n    }\n\n    public EventExecutionPb.EventExecution toProto(EventExecution from) {\n        EventExecutionPb.EventExecution.Builder to = EventExecutionPb.EventExecution.newBuilder();\n        if (from.getId() != null) {\n            to.setId( from.getId() );\n        }\n        if (from.getMessageId() != null) {\n            to.setMessageId( from.getMessageId() );\n        }\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getEvent() != null) {\n            to.setEvent( from.getEvent() );\n        }\n        to.setCreated( from.getCreated() );\n        if (from.getStatus() != null) {\n            to.setStatus( toProto( from.getStatus() ) );\n        }\n        if (from.getAction() != null) {\n            to.setAction( toProto( from.getAction() ) );\n        }\n        for (Map.Entry<String, Object> pair : from.getOutput().entrySet()) {\n            to.putOutput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        return to.build();\n    }\n\n    public EventExecution fromProto(EventExecutionPb.EventExecution from) {\n        EventExecution to = new EventExecution();\n        to.setId( from.getId() );\n        to.setMessageId( from.getMessageId() );\n        to.setName( from.getName() );\n        to.setEvent( from.getEvent() );\n        to.setCreated( from.getCreated() );\n        to.setStatus( fromProto( from.getStatus() ) );\n        to.setAction( fromProto( from.getAction() ) );\n        Map<String, Object> outputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getOutputMap().entrySet()) {\n            outputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setOutput(outputMap);\n        return to;\n    }\n\n    public EventExecutionPb.EventExecution.Status toProto(EventExecution.Status from) {\n        EventExecutionPb.EventExecution.Status to;\n        switch (from) {\n            case IN_PROGRESS: to = EventExecutionPb.EventExecution.Status.IN_PROGRESS; break;\n            case COMPLETED: to = EventExecutionPb.EventExecution.Status.COMPLETED; break;\n            case FAILED: to = EventExecutionPb.EventExecution.Status.FAILED; break;\n            case SKIPPED: to = EventExecutionPb.EventExecution.Status.SKIPPED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public EventExecution.Status fromProto(EventExecutionPb.EventExecution.Status from) {\n        EventExecution.Status to;\n        switch (from) {\n            case IN_PROGRESS: to = EventExecution.Status.IN_PROGRESS; break;\n            case COMPLETED: to = EventExecution.Status.COMPLETED; break;\n            case FAILED: to = EventExecution.Status.FAILED; break;\n            case SKIPPED: to = EventExecution.Status.SKIPPED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public EventHandlerPb.EventHandler toProto(EventHandler from) {\n        EventHandlerPb.EventHandler.Builder to = EventHandlerPb.EventHandler.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getEvent() != null) {\n            to.setEvent( from.getEvent() );\n        }\n        if (from.getCondition() != null) {\n            to.setCondition( from.getCondition() );\n        }\n        for (EventHandler.Action elem : from.getActions()) {\n            to.addActions( toProto(elem) );\n        }\n        to.setActive( from.isActive() );\n        if (from.getEvaluatorType() != null) {\n            to.setEvaluatorType( from.getEvaluatorType() );\n        }\n        return to.build();\n    }\n\n    public EventHandler fromProto(EventHandlerPb.EventHandler from) {\n        EventHandler to = new EventHandler();\n        to.setName( from.getName() );\n        to.setEvent( from.getEvent() );\n        to.setCondition( from.getCondition() );\n        to.setActions( from.getActionsList().stream().map(this::fromProto).collect(Collectors.toCollection(ArrayList::new)) );\n        to.setActive( from.getActive() );\n        to.setEvaluatorType( from.getEvaluatorType() );\n        return to;\n    }\n\n    public EventHandlerPb.EventHandler.StartWorkflow toProto(EventHandler.StartWorkflow from) {\n        EventHandlerPb.EventHandler.StartWorkflow.Builder to = EventHandlerPb.EventHandler.StartWorkflow.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getVersion() != null) {\n            to.setVersion( from.getVersion() );\n        }\n        if (from.getCorrelationId() != null) {\n            to.setCorrelationId( from.getCorrelationId() );\n        }\n        for (Map.Entry<String, Object> pair : from.getInput().entrySet()) {\n            to.putInput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getInputMessage() != null) {\n            to.setInputMessage( toProto( from.getInputMessage() ) );\n        }\n        to.putAllTaskToDomain( from.getTaskToDomain() );\n        return to.build();\n    }\n\n    public EventHandler.StartWorkflow fromProto(EventHandlerPb.EventHandler.StartWorkflow from) {\n        EventHandler.StartWorkflow to = new EventHandler.StartWorkflow();\n        to.setName( from.getName() );\n        to.setVersion( from.getVersion() );\n        to.setCorrelationId( from.getCorrelationId() );\n        Map<String, Object> inputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputMap().entrySet()) {\n            inputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInput(inputMap);\n        if (from.hasInputMessage()) {\n            to.setInputMessage( fromProto( from.getInputMessage() ) );\n        }\n        to.setTaskToDomain( from.getTaskToDomainMap() );\n        return to;\n    }\n\n    public EventHandlerPb.EventHandler.TaskDetails toProto(EventHandler.TaskDetails from) {\n        EventHandlerPb.EventHandler.TaskDetails.Builder to = EventHandlerPb.EventHandler.TaskDetails.newBuilder();\n        if (from.getWorkflowId() != null) {\n            to.setWorkflowId( from.getWorkflowId() );\n        }\n        if (from.getTaskRefName() != null) {\n            to.setTaskRefName( from.getTaskRefName() );\n        }\n        for (Map.Entry<String, Object> pair : from.getOutput().entrySet()) {\n            to.putOutput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getOutputMessage() != null) {\n            to.setOutputMessage( toProto( from.getOutputMessage() ) );\n        }\n        if (from.getTaskId() != null) {\n            to.setTaskId( from.getTaskId() );\n        }\n        return to.build();\n    }\n\n    public EventHandler.TaskDetails fromProto(EventHandlerPb.EventHandler.TaskDetails from) {\n        EventHandler.TaskDetails to = new EventHandler.TaskDetails();\n        to.setWorkflowId( from.getWorkflowId() );\n        to.setTaskRefName( from.getTaskRefName() );\n        Map<String, Object> outputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getOutputMap().entrySet()) {\n            outputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setOutput(outputMap);\n        if (from.hasOutputMessage()) {\n            to.setOutputMessage( fromProto( from.getOutputMessage() ) );\n        }\n        to.setTaskId( from.getTaskId() );\n        return to;\n    }\n\n    public EventHandlerPb.EventHandler.Action toProto(EventHandler.Action from) {\n        EventHandlerPb.EventHandler.Action.Builder to = EventHandlerPb.EventHandler.Action.newBuilder();\n        if (from.getAction() != null) {\n            to.setAction( toProto( from.getAction() ) );\n        }\n        if (from.getStart_workflow() != null) {\n            to.setStartWorkflow( toProto( from.getStart_workflow() ) );\n        }\n        if (from.getComplete_task() != null) {\n            to.setCompleteTask( toProto( from.getComplete_task() ) );\n        }\n        if (from.getFail_task() != null) {\n            to.setFailTask( toProto( from.getFail_task() ) );\n        }\n        to.setExpandInlineJson( from.isExpandInlineJSON() );\n        return to.build();\n    }\n\n    public EventHandler.Action fromProto(EventHandlerPb.EventHandler.Action from) {\n        EventHandler.Action to = new EventHandler.Action();\n        to.setAction( fromProto( from.getAction() ) );\n        if (from.hasStartWorkflow()) {\n            to.setStart_workflow( fromProto( from.getStartWorkflow() ) );\n        }\n        if (from.hasCompleteTask()) {\n            to.setComplete_task( fromProto( from.getCompleteTask() ) );\n        }\n        if (from.hasFailTask()) {\n            to.setFail_task( fromProto( from.getFailTask() ) );\n        }\n        to.setExpandInlineJSON( from.getExpandInlineJson() );\n        return to;\n    }\n\n    public EventHandlerPb.EventHandler.Action.Type toProto(EventHandler.Action.Type from) {\n        EventHandlerPb.EventHandler.Action.Type to;\n        switch (from) {\n            case start_workflow: to = EventHandlerPb.EventHandler.Action.Type.START_WORKFLOW; break;\n            case complete_task: to = EventHandlerPb.EventHandler.Action.Type.COMPLETE_TASK; break;\n            case fail_task: to = EventHandlerPb.EventHandler.Action.Type.FAIL_TASK; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public EventHandler.Action.Type fromProto(EventHandlerPb.EventHandler.Action.Type from) {\n        EventHandler.Action.Type to;\n        switch (from) {\n            case START_WORKFLOW: to = EventHandler.Action.Type.start_workflow; break;\n            case COMPLETE_TASK: to = EventHandler.Action.Type.complete_task; break;\n            case FAIL_TASK: to = EventHandler.Action.Type.fail_task; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public PollDataPb.PollData toProto(PollData from) {\n        PollDataPb.PollData.Builder to = PollDataPb.PollData.newBuilder();\n        if (from.getQueueName() != null) {\n            to.setQueueName( from.getQueueName() );\n        }\n        if (from.getDomain() != null) {\n            to.setDomain( from.getDomain() );\n        }\n        if (from.getWorkerId() != null) {\n            to.setWorkerId( from.getWorkerId() );\n        }\n        to.setLastPollTime( from.getLastPollTime() );\n        return to.build();\n    }\n\n    public PollData fromProto(PollDataPb.PollData from) {\n        PollData to = new PollData();\n        to.setQueueName( from.getQueueName() );\n        to.setDomain( from.getDomain() );\n        to.setWorkerId( from.getWorkerId() );\n        to.setLastPollTime( from.getLastPollTime() );\n        return to;\n    }\n\n    public RerunWorkflowRequestPb.RerunWorkflowRequest toProto(RerunWorkflowRequest from) {\n        RerunWorkflowRequestPb.RerunWorkflowRequest.Builder to = RerunWorkflowRequestPb.RerunWorkflowRequest.newBuilder();\n        if (from.getReRunFromWorkflowId() != null) {\n            to.setReRunFromWorkflowId( from.getReRunFromWorkflowId() );\n        }\n        for (Map.Entry<String, Object> pair : from.getWorkflowInput().entrySet()) {\n            to.putWorkflowInput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getReRunFromTaskId() != null) {\n            to.setReRunFromTaskId( from.getReRunFromTaskId() );\n        }\n        for (Map.Entry<String, Object> pair : from.getTaskInput().entrySet()) {\n            to.putTaskInput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getCorrelationId() != null) {\n            to.setCorrelationId( from.getCorrelationId() );\n        }\n        return to.build();\n    }\n\n    public RerunWorkflowRequest fromProto(RerunWorkflowRequestPb.RerunWorkflowRequest from) {\n        RerunWorkflowRequest to = new RerunWorkflowRequest();\n        to.setReRunFromWorkflowId( from.getReRunFromWorkflowId() );\n        Map<String, Object> workflowInputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getWorkflowInputMap().entrySet()) {\n            workflowInputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setWorkflowInput(workflowInputMap);\n        to.setReRunFromTaskId( from.getReRunFromTaskId() );\n        Map<String, Object> taskInputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getTaskInputMap().entrySet()) {\n            taskInputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setTaskInput(taskInputMap);\n        to.setCorrelationId( from.getCorrelationId() );\n        return to;\n    }\n\n    public SkipTaskRequest fromProto(SkipTaskRequestPb.SkipTaskRequest from) {\n        SkipTaskRequest to = new SkipTaskRequest();\n        Map<String, Object> taskInputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getTaskInputMap().entrySet()) {\n            taskInputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setTaskInput(taskInputMap);\n        Map<String, Object> taskOutputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getTaskOutputMap().entrySet()) {\n            taskOutputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setTaskOutput(taskOutputMap);\n        if (from.hasTaskInputMessage()) {\n            to.setTaskInputMessage( fromProto( from.getTaskInputMessage() ) );\n        }\n        if (from.hasTaskOutputMessage()) {\n            to.setTaskOutputMessage( fromProto( from.getTaskOutputMessage() ) );\n        }\n        return to;\n    }\n\n    public StartWorkflowRequestPb.StartWorkflowRequest toProto(StartWorkflowRequest from) {\n        StartWorkflowRequestPb.StartWorkflowRequest.Builder to = StartWorkflowRequestPb.StartWorkflowRequest.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getVersion() != null) {\n            to.setVersion( from.getVersion() );\n        }\n        if (from.getCorrelationId() != null) {\n            to.setCorrelationId( from.getCorrelationId() );\n        }\n        for (Map.Entry<String, Object> pair : from.getInput().entrySet()) {\n            to.putInput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        to.putAllTaskToDomain( from.getTaskToDomain() );\n        if (from.getWorkflowDef() != null) {\n            to.setWorkflowDef( toProto( from.getWorkflowDef() ) );\n        }\n        if (from.getExternalInputPayloadStoragePath() != null) {\n            to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        }\n        if (from.getPriority() != null) {\n            to.setPriority( from.getPriority() );\n        }\n        return to.build();\n    }\n\n    public StartWorkflowRequest fromProto(StartWorkflowRequestPb.StartWorkflowRequest from) {\n        StartWorkflowRequest to = new StartWorkflowRequest();\n        to.setName( from.getName() );\n        to.setVersion( from.getVersion() );\n        to.setCorrelationId( from.getCorrelationId() );\n        Map<String, Object> inputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputMap().entrySet()) {\n            inputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInput(inputMap);\n        to.setTaskToDomain( from.getTaskToDomainMap() );\n        if (from.hasWorkflowDef()) {\n            to.setWorkflowDef( fromProto( from.getWorkflowDef() ) );\n        }\n        to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        to.setPriority( from.getPriority() );\n        return to;\n    }\n\n    public SubWorkflowParamsPb.SubWorkflowParams toProto(SubWorkflowParams from) {\n        SubWorkflowParamsPb.SubWorkflowParams.Builder to = SubWorkflowParamsPb.SubWorkflowParams.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getVersion() != null) {\n            to.setVersion( from.getVersion() );\n        }\n        to.putAllTaskToDomain( from.getTaskToDomain() );\n        if (from.getWorkflowDefinition() != null) {\n            to.setWorkflowDefinition( toProto( from.getWorkflowDefinition() ) );\n        }\n        return to.build();\n    }\n\n    public SubWorkflowParams fromProto(SubWorkflowParamsPb.SubWorkflowParams from) {\n        SubWorkflowParams to = new SubWorkflowParams();\n        to.setName( from.getName() );\n        to.setVersion( from.getVersion() );\n        to.setTaskToDomain( from.getTaskToDomainMap() );\n        if (from.hasWorkflowDefinition()) {\n            to.setWorkflowDefinition( fromProto( from.getWorkflowDefinition() ) );\n        }\n        return to;\n    }\n\n    public TaskPb.Task toProto(Task from) {\n        TaskPb.Task.Builder to = TaskPb.Task.newBuilder();\n        if (from.getTaskType() != null) {\n            to.setTaskType( from.getTaskType() );\n        }\n        if (from.getStatus() != null) {\n            to.setStatus( toProto( from.getStatus() ) );\n        }\n        for (Map.Entry<String, Object> pair : from.getInputData().entrySet()) {\n            to.putInputData( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getReferenceTaskName() != null) {\n            to.setReferenceTaskName( from.getReferenceTaskName() );\n        }\n        to.setRetryCount( from.getRetryCount() );\n        to.setSeq( from.getSeq() );\n        if (from.getCorrelationId() != null) {\n            to.setCorrelationId( from.getCorrelationId() );\n        }\n        to.setPollCount( from.getPollCount() );\n        if (from.getTaskDefName() != null) {\n            to.setTaskDefName( from.getTaskDefName() );\n        }\n        to.setScheduledTime( from.getScheduledTime() );\n        to.setStartTime( from.getStartTime() );\n        to.setEndTime( from.getEndTime() );\n        to.setUpdateTime( from.getUpdateTime() );\n        to.setStartDelayInSeconds( from.getStartDelayInSeconds() );\n        if (from.getRetriedTaskId() != null) {\n            to.setRetriedTaskId( from.getRetriedTaskId() );\n        }\n        to.setRetried( from.isRetried() );\n        to.setExecuted( from.isExecuted() );\n        to.setCallbackFromWorker( from.isCallbackFromWorker() );\n        to.setResponseTimeoutSeconds( from.getResponseTimeoutSeconds() );\n        if (from.getWorkflowInstanceId() != null) {\n            to.setWorkflowInstanceId( from.getWorkflowInstanceId() );\n        }\n        if (from.getWorkflowType() != null) {\n            to.setWorkflowType( from.getWorkflowType() );\n        }\n        if (from.getTaskId() != null) {\n            to.setTaskId( from.getTaskId() );\n        }\n        if (from.getReasonForIncompletion() != null) {\n            to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        }\n        to.setCallbackAfterSeconds( from.getCallbackAfterSeconds() );\n        if (from.getWorkerId() != null) {\n            to.setWorkerId( from.getWorkerId() );\n        }\n        for (Map.Entry<String, Object> pair : from.getOutputData().entrySet()) {\n            to.putOutputData( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getWorkflowTask() != null) {\n            to.setWorkflowTask( toProto( from.getWorkflowTask() ) );\n        }\n        if (from.getDomain() != null) {\n            to.setDomain( from.getDomain() );\n        }\n        if (from.getInputMessage() != null) {\n            to.setInputMessage( toProto( from.getInputMessage() ) );\n        }\n        if (from.getOutputMessage() != null) {\n            to.setOutputMessage( toProto( from.getOutputMessage() ) );\n        }\n        to.setRateLimitPerFrequency( from.getRateLimitPerFrequency() );\n        to.setRateLimitFrequencyInSeconds( from.getRateLimitFrequencyInSeconds() );\n        if (from.getExternalInputPayloadStoragePath() != null) {\n            to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        }\n        if (from.getExternalOutputPayloadStoragePath() != null) {\n            to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        }\n        to.setWorkflowPriority( from.getWorkflowPriority() );\n        if (from.getExecutionNameSpace() != null) {\n            to.setExecutionNameSpace( from.getExecutionNameSpace() );\n        }\n        if (from.getIsolationGroupId() != null) {\n            to.setIsolationGroupId( from.getIsolationGroupId() );\n        }\n        to.setIteration( from.getIteration() );\n        if (from.getSubWorkflowId() != null) {\n            to.setSubWorkflowId( from.getSubWorkflowId() );\n        }\n        to.setSubworkflowChanged( from.isSubworkflowChanged() );\n        return to.build();\n    }\n\n    public Task fromProto(TaskPb.Task from) {\n        Task to = new Task();\n        to.setTaskType( from.getTaskType() );\n        to.setStatus( fromProto( from.getStatus() ) );\n        Map<String, Object> inputDataMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputDataMap().entrySet()) {\n            inputDataMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInputData(inputDataMap);\n        to.setReferenceTaskName( from.getReferenceTaskName() );\n        to.setRetryCount( from.getRetryCount() );\n        to.setSeq( from.getSeq() );\n        to.setCorrelationId( from.getCorrelationId() );\n        to.setPollCount( from.getPollCount() );\n        to.setTaskDefName( from.getTaskDefName() );\n        to.setScheduledTime( from.getScheduledTime() );\n        to.setStartTime( from.getStartTime() );\n        to.setEndTime( from.getEndTime() );\n        to.setUpdateTime( from.getUpdateTime() );\n        to.setStartDelayInSeconds( from.getStartDelayInSeconds() );\n        to.setRetriedTaskId( from.getRetriedTaskId() );\n        to.setRetried( from.getRetried() );\n        to.setExecuted( from.getExecuted() );\n        to.setCallbackFromWorker( from.getCallbackFromWorker() );\n        to.setResponseTimeoutSeconds( from.getResponseTimeoutSeconds() );\n        to.setWorkflowInstanceId( from.getWorkflowInstanceId() );\n        to.setWorkflowType( from.getWorkflowType() );\n        to.setTaskId( from.getTaskId() );\n        to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        to.setCallbackAfterSeconds( from.getCallbackAfterSeconds() );\n        to.setWorkerId( from.getWorkerId() );\n        Map<String, Object> outputDataMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getOutputDataMap().entrySet()) {\n            outputDataMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setOutputData(outputDataMap);\n        if (from.hasWorkflowTask()) {\n            to.setWorkflowTask( fromProto( from.getWorkflowTask() ) );\n        }\n        to.setDomain( from.getDomain() );\n        if (from.hasInputMessage()) {\n            to.setInputMessage( fromProto( from.getInputMessage() ) );\n        }\n        if (from.hasOutputMessage()) {\n            to.setOutputMessage( fromProto( from.getOutputMessage() ) );\n        }\n        to.setRateLimitPerFrequency( from.getRateLimitPerFrequency() );\n        to.setRateLimitFrequencyInSeconds( from.getRateLimitFrequencyInSeconds() );\n        to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        to.setWorkflowPriority( from.getWorkflowPriority() );\n        to.setExecutionNameSpace( from.getExecutionNameSpace() );\n        to.setIsolationGroupId( from.getIsolationGroupId() );\n        to.setIteration( from.getIteration() );\n        to.setSubWorkflowId( from.getSubWorkflowId() );\n        to.setSubworkflowChanged( from.getSubworkflowChanged() );\n        return to;\n    }\n\n    public TaskPb.Task.Status toProto(Task.Status from) {\n        TaskPb.Task.Status to;\n        switch (from) {\n            case IN_PROGRESS: to = TaskPb.Task.Status.IN_PROGRESS; break;\n            case CANCELED: to = TaskPb.Task.Status.CANCELED; break;\n            case FAILED: to = TaskPb.Task.Status.FAILED; break;\n            case FAILED_WITH_TERMINAL_ERROR: to = TaskPb.Task.Status.FAILED_WITH_TERMINAL_ERROR; break;\n            case COMPLETED: to = TaskPb.Task.Status.COMPLETED; break;\n            case COMPLETED_WITH_ERRORS: to = TaskPb.Task.Status.COMPLETED_WITH_ERRORS; break;\n            case SCHEDULED: to = TaskPb.Task.Status.SCHEDULED; break;\n            case TIMED_OUT: to = TaskPb.Task.Status.TIMED_OUT; break;\n            case SKIPPED: to = TaskPb.Task.Status.SKIPPED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public Task.Status fromProto(TaskPb.Task.Status from) {\n        Task.Status to;\n        switch (from) {\n            case IN_PROGRESS: to = Task.Status.IN_PROGRESS; break;\n            case CANCELED: to = Task.Status.CANCELED; break;\n            case FAILED: to = Task.Status.FAILED; break;\n            case FAILED_WITH_TERMINAL_ERROR: to = Task.Status.FAILED_WITH_TERMINAL_ERROR; break;\n            case COMPLETED: to = Task.Status.COMPLETED; break;\n            case COMPLETED_WITH_ERRORS: to = Task.Status.COMPLETED_WITH_ERRORS; break;\n            case SCHEDULED: to = Task.Status.SCHEDULED; break;\n            case TIMED_OUT: to = Task.Status.TIMED_OUT; break;\n            case SKIPPED: to = Task.Status.SKIPPED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public TaskDefPb.TaskDef toProto(TaskDef from) {\n        TaskDefPb.TaskDef.Builder to = TaskDefPb.TaskDef.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getDescription() != null) {\n            to.setDescription( from.getDescription() );\n        }\n        to.setRetryCount( from.getRetryCount() );\n        to.setTimeoutSeconds( from.getTimeoutSeconds() );\n        to.addAllInputKeys( from.getInputKeys() );\n        to.addAllOutputKeys( from.getOutputKeys() );\n        if (from.getTimeoutPolicy() != null) {\n            to.setTimeoutPolicy( toProto( from.getTimeoutPolicy() ) );\n        }\n        if (from.getRetryLogic() != null) {\n            to.setRetryLogic( toProto( from.getRetryLogic() ) );\n        }\n        to.setRetryDelaySeconds( from.getRetryDelaySeconds() );\n        to.setResponseTimeoutSeconds( from.getResponseTimeoutSeconds() );\n        if (from.getConcurrentExecLimit() != null) {\n            to.setConcurrentExecLimit( from.getConcurrentExecLimit() );\n        }\n        for (Map.Entry<String, Object> pair : from.getInputTemplate().entrySet()) {\n            to.putInputTemplate( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getRateLimitPerFrequency() != null) {\n            to.setRateLimitPerFrequency( from.getRateLimitPerFrequency() );\n        }\n        if (from.getRateLimitFrequencyInSeconds() != null) {\n            to.setRateLimitFrequencyInSeconds( from.getRateLimitFrequencyInSeconds() );\n        }\n        if (from.getIsolationGroupId() != null) {\n            to.setIsolationGroupId( from.getIsolationGroupId() );\n        }\n        if (from.getExecutionNameSpace() != null) {\n            to.setExecutionNameSpace( from.getExecutionNameSpace() );\n        }\n        if (from.getOwnerEmail() != null) {\n            to.setOwnerEmail( from.getOwnerEmail() );\n        }\n        if (from.getPollTimeoutSeconds() != null) {\n            to.setPollTimeoutSeconds( from.getPollTimeoutSeconds() );\n        }\n        if (from.getBackoffScaleFactor() != null) {\n            to.setBackoffScaleFactor( from.getBackoffScaleFactor() );\n        }\n        return to.build();\n    }\n\n    public TaskDef fromProto(TaskDefPb.TaskDef from) {\n        TaskDef to = new TaskDef();\n        to.setName( from.getName() );\n        to.setDescription( from.getDescription() );\n        to.setRetryCount( from.getRetryCount() );\n        to.setTimeoutSeconds( from.getTimeoutSeconds() );\n        to.setInputKeys( from.getInputKeysList().stream().collect(Collectors.toCollection(ArrayList::new)) );\n        to.setOutputKeys( from.getOutputKeysList().stream().collect(Collectors.toCollection(ArrayList::new)) );\n        to.setTimeoutPolicy( fromProto( from.getTimeoutPolicy() ) );\n        to.setRetryLogic( fromProto( from.getRetryLogic() ) );\n        to.setRetryDelaySeconds( from.getRetryDelaySeconds() );\n        to.setResponseTimeoutSeconds( from.getResponseTimeoutSeconds() );\n        to.setConcurrentExecLimit( from.getConcurrentExecLimit() );\n        Map<String, Object> inputTemplateMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputTemplateMap().entrySet()) {\n            inputTemplateMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInputTemplate(inputTemplateMap);\n        to.setRateLimitPerFrequency( from.getRateLimitPerFrequency() );\n        to.setRateLimitFrequencyInSeconds( from.getRateLimitFrequencyInSeconds() );\n        to.setIsolationGroupId( from.getIsolationGroupId() );\n        to.setExecutionNameSpace( from.getExecutionNameSpace() );\n        to.setOwnerEmail( from.getOwnerEmail() );\n        to.setPollTimeoutSeconds( from.getPollTimeoutSeconds() );\n        to.setBackoffScaleFactor( from.getBackoffScaleFactor() );\n        return to;\n    }\n\n    public TaskDefPb.TaskDef.TimeoutPolicy toProto(TaskDef.TimeoutPolicy from) {\n        TaskDefPb.TaskDef.TimeoutPolicy to;\n        switch (from) {\n            case RETRY: to = TaskDefPb.TaskDef.TimeoutPolicy.RETRY; break;\n            case TIME_OUT_WF: to = TaskDefPb.TaskDef.TimeoutPolicy.TIME_OUT_WF; break;\n            case ALERT_ONLY: to = TaskDefPb.TaskDef.TimeoutPolicy.ALERT_ONLY; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public TaskDef.TimeoutPolicy fromProto(TaskDefPb.TaskDef.TimeoutPolicy from) {\n        TaskDef.TimeoutPolicy to;\n        switch (from) {\n            case RETRY: to = TaskDef.TimeoutPolicy.RETRY; break;\n            case TIME_OUT_WF: to = TaskDef.TimeoutPolicy.TIME_OUT_WF; break;\n            case ALERT_ONLY: to = TaskDef.TimeoutPolicy.ALERT_ONLY; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public TaskDefPb.TaskDef.RetryLogic toProto(TaskDef.RetryLogic from) {\n        TaskDefPb.TaskDef.RetryLogic to;\n        switch (from) {\n            case FIXED: to = TaskDefPb.TaskDef.RetryLogic.FIXED; break;\n            case EXPONENTIAL_BACKOFF: to = TaskDefPb.TaskDef.RetryLogic.EXPONENTIAL_BACKOFF; break;\n            case LINEAR_BACKOFF: to = TaskDefPb.TaskDef.RetryLogic.LINEAR_BACKOFF; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public TaskDef.RetryLogic fromProto(TaskDefPb.TaskDef.RetryLogic from) {\n        TaskDef.RetryLogic to;\n        switch (from) {\n            case FIXED: to = TaskDef.RetryLogic.FIXED; break;\n            case EXPONENTIAL_BACKOFF: to = TaskDef.RetryLogic.EXPONENTIAL_BACKOFF; break;\n            case LINEAR_BACKOFF: to = TaskDef.RetryLogic.LINEAR_BACKOFF; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public TaskExecLogPb.TaskExecLog toProto(TaskExecLog from) {\n        TaskExecLogPb.TaskExecLog.Builder to = TaskExecLogPb.TaskExecLog.newBuilder();\n        if (from.getLog() != null) {\n            to.setLog( from.getLog() );\n        }\n        if (from.getTaskId() != null) {\n            to.setTaskId( from.getTaskId() );\n        }\n        to.setCreatedTime( from.getCreatedTime() );\n        return to.build();\n    }\n\n    public TaskExecLog fromProto(TaskExecLogPb.TaskExecLog from) {\n        TaskExecLog to = new TaskExecLog();\n        to.setLog( from.getLog() );\n        to.setTaskId( from.getTaskId() );\n        to.setCreatedTime( from.getCreatedTime() );\n        return to;\n    }\n\n    public TaskResultPb.TaskResult toProto(TaskResult from) {\n        TaskResultPb.TaskResult.Builder to = TaskResultPb.TaskResult.newBuilder();\n        if (from.getWorkflowInstanceId() != null) {\n            to.setWorkflowInstanceId( from.getWorkflowInstanceId() );\n        }\n        if (from.getTaskId() != null) {\n            to.setTaskId( from.getTaskId() );\n        }\n        if (from.getReasonForIncompletion() != null) {\n            to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        }\n        to.setCallbackAfterSeconds( from.getCallbackAfterSeconds() );\n        if (from.getWorkerId() != null) {\n            to.setWorkerId( from.getWorkerId() );\n        }\n        if (from.getStatus() != null) {\n            to.setStatus( toProto( from.getStatus() ) );\n        }\n        for (Map.Entry<String, Object> pair : from.getOutputData().entrySet()) {\n            to.putOutputData( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getOutputMessage() != null) {\n            to.setOutputMessage( toProto( from.getOutputMessage() ) );\n        }\n        return to.build();\n    }\n\n    public TaskResult fromProto(TaskResultPb.TaskResult from) {\n        TaskResult to = new TaskResult();\n        to.setWorkflowInstanceId( from.getWorkflowInstanceId() );\n        to.setTaskId( from.getTaskId() );\n        to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        to.setCallbackAfterSeconds( from.getCallbackAfterSeconds() );\n        to.setWorkerId( from.getWorkerId() );\n        to.setStatus( fromProto( from.getStatus() ) );\n        Map<String, Object> outputDataMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getOutputDataMap().entrySet()) {\n            outputDataMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setOutputData(outputDataMap);\n        if (from.hasOutputMessage()) {\n            to.setOutputMessage( fromProto( from.getOutputMessage() ) );\n        }\n        return to;\n    }\n\n    public TaskResultPb.TaskResult.Status toProto(TaskResult.Status from) {\n        TaskResultPb.TaskResult.Status to;\n        switch (from) {\n            case IN_PROGRESS: to = TaskResultPb.TaskResult.Status.IN_PROGRESS; break;\n            case FAILED: to = TaskResultPb.TaskResult.Status.FAILED; break;\n            case FAILED_WITH_TERMINAL_ERROR: to = TaskResultPb.TaskResult.Status.FAILED_WITH_TERMINAL_ERROR; break;\n            case COMPLETED: to = TaskResultPb.TaskResult.Status.COMPLETED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public TaskResult.Status fromProto(TaskResultPb.TaskResult.Status from) {\n        TaskResult.Status to;\n        switch (from) {\n            case IN_PROGRESS: to = TaskResult.Status.IN_PROGRESS; break;\n            case FAILED: to = TaskResult.Status.FAILED; break;\n            case FAILED_WITH_TERMINAL_ERROR: to = TaskResult.Status.FAILED_WITH_TERMINAL_ERROR; break;\n            case COMPLETED: to = TaskResult.Status.COMPLETED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public TaskSummaryPb.TaskSummary toProto(TaskSummary from) {\n        TaskSummaryPb.TaskSummary.Builder to = TaskSummaryPb.TaskSummary.newBuilder();\n        if (from.getWorkflowId() != null) {\n            to.setWorkflowId( from.getWorkflowId() );\n        }\n        if (from.getWorkflowType() != null) {\n            to.setWorkflowType( from.getWorkflowType() );\n        }\n        if (from.getCorrelationId() != null) {\n            to.setCorrelationId( from.getCorrelationId() );\n        }\n        if (from.getScheduledTime() != null) {\n            to.setScheduledTime( from.getScheduledTime() );\n        }\n        if (from.getStartTime() != null) {\n            to.setStartTime( from.getStartTime() );\n        }\n        if (from.getUpdateTime() != null) {\n            to.setUpdateTime( from.getUpdateTime() );\n        }\n        if (from.getEndTime() != null) {\n            to.setEndTime( from.getEndTime() );\n        }\n        if (from.getStatus() != null) {\n            to.setStatus( toProto( from.getStatus() ) );\n        }\n        if (from.getReasonForIncompletion() != null) {\n            to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        }\n        to.setExecutionTime( from.getExecutionTime() );\n        to.setQueueWaitTime( from.getQueueWaitTime() );\n        if (from.getTaskDefName() != null) {\n            to.setTaskDefName( from.getTaskDefName() );\n        }\n        if (from.getTaskType() != null) {\n            to.setTaskType( from.getTaskType() );\n        }\n        if (from.getInput() != null) {\n            to.setInput( from.getInput() );\n        }\n        if (from.getOutput() != null) {\n            to.setOutput( from.getOutput() );\n        }\n        if (from.getTaskId() != null) {\n            to.setTaskId( from.getTaskId() );\n        }\n        if (from.getExternalInputPayloadStoragePath() != null) {\n            to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        }\n        if (from.getExternalOutputPayloadStoragePath() != null) {\n            to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        }\n        to.setWorkflowPriority( from.getWorkflowPriority() );\n        if (from.getDomain() != null) {\n            to.setDomain( from.getDomain() );\n        }\n        return to.build();\n    }\n\n    public TaskSummary fromProto(TaskSummaryPb.TaskSummary from) {\n        TaskSummary to = new TaskSummary();\n        to.setWorkflowId( from.getWorkflowId() );\n        to.setWorkflowType( from.getWorkflowType() );\n        to.setCorrelationId( from.getCorrelationId() );\n        to.setScheduledTime( from.getScheduledTime() );\n        to.setStartTime( from.getStartTime() );\n        to.setUpdateTime( from.getUpdateTime() );\n        to.setEndTime( from.getEndTime() );\n        to.setStatus( fromProto( from.getStatus() ) );\n        to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        to.setExecutionTime( from.getExecutionTime() );\n        to.setQueueWaitTime( from.getQueueWaitTime() );\n        to.setTaskDefName( from.getTaskDefName() );\n        to.setTaskType( from.getTaskType() );\n        to.setInput( from.getInput() );\n        to.setOutput( from.getOutput() );\n        to.setTaskId( from.getTaskId() );\n        to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        to.setWorkflowPriority( from.getWorkflowPriority() );\n        to.setDomain( from.getDomain() );\n        return to;\n    }\n\n    public WorkflowPb.Workflow toProto(Workflow from) {\n        WorkflowPb.Workflow.Builder to = WorkflowPb.Workflow.newBuilder();\n        if (from.getStatus() != null) {\n            to.setStatus( toProto( from.getStatus() ) );\n        }\n        to.setEndTime( from.getEndTime() );\n        if (from.getWorkflowId() != null) {\n            to.setWorkflowId( from.getWorkflowId() );\n        }\n        if (from.getParentWorkflowId() != null) {\n            to.setParentWorkflowId( from.getParentWorkflowId() );\n        }\n        if (from.getParentWorkflowTaskId() != null) {\n            to.setParentWorkflowTaskId( from.getParentWorkflowTaskId() );\n        }\n        for (Task elem : from.getTasks()) {\n            to.addTasks( toProto(elem) );\n        }\n        for (Map.Entry<String, Object> pair : from.getInput().entrySet()) {\n            to.putInput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        for (Map.Entry<String, Object> pair : from.getOutput().entrySet()) {\n            to.putOutput( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getCorrelationId() != null) {\n            to.setCorrelationId( from.getCorrelationId() );\n        }\n        if (from.getReRunFromWorkflowId() != null) {\n            to.setReRunFromWorkflowId( from.getReRunFromWorkflowId() );\n        }\n        if (from.getReasonForIncompletion() != null) {\n            to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        }\n        if (from.getEvent() != null) {\n            to.setEvent( from.getEvent() );\n        }\n        to.putAllTaskToDomain( from.getTaskToDomain() );\n        to.addAllFailedReferenceTaskNames( from.getFailedReferenceTaskNames() );\n        if (from.getWorkflowDefinition() != null) {\n            to.setWorkflowDefinition( toProto( from.getWorkflowDefinition() ) );\n        }\n        if (from.getExternalInputPayloadStoragePath() != null) {\n            to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        }\n        if (from.getExternalOutputPayloadStoragePath() != null) {\n            to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        }\n        to.setPriority( from.getPriority() );\n        for (Map.Entry<String, Object> pair : from.getVariables().entrySet()) {\n            to.putVariables( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        to.setLastRetriedTime( from.getLastRetriedTime() );\n        to.addAllFailedTaskNames( from.getFailedTaskNames() );\n        return to.build();\n    }\n\n    public Workflow fromProto(WorkflowPb.Workflow from) {\n        Workflow to = new Workflow();\n        to.setStatus( fromProto( from.getStatus() ) );\n        to.setEndTime( from.getEndTime() );\n        to.setWorkflowId( from.getWorkflowId() );\n        to.setParentWorkflowId( from.getParentWorkflowId() );\n        to.setParentWorkflowTaskId( from.getParentWorkflowTaskId() );\n        to.setTasks( from.getTasksList().stream().map(this::fromProto).collect(Collectors.toCollection(ArrayList::new)) );\n        Map<String, Object> inputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputMap().entrySet()) {\n            inputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInput(inputMap);\n        Map<String, Object> outputMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getOutputMap().entrySet()) {\n            outputMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setOutput(outputMap);\n        to.setCorrelationId( from.getCorrelationId() );\n        to.setReRunFromWorkflowId( from.getReRunFromWorkflowId() );\n        to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        to.setEvent( from.getEvent() );\n        to.setTaskToDomain( from.getTaskToDomainMap() );\n        to.setFailedReferenceTaskNames( from.getFailedReferenceTaskNamesList().stream().collect(Collectors.toCollection(HashSet::new)) );\n        if (from.hasWorkflowDefinition()) {\n            to.setWorkflowDefinition( fromProto( from.getWorkflowDefinition() ) );\n        }\n        to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        to.setPriority( from.getPriority() );\n        Map<String, Object> variablesMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getVariablesMap().entrySet()) {\n            variablesMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setVariables(variablesMap);\n        to.setLastRetriedTime( from.getLastRetriedTime() );\n        to.setFailedTaskNames( from.getFailedTaskNamesList().stream().collect(Collectors.toCollection(HashSet::new)) );\n        return to;\n    }\n\n    public WorkflowPb.Workflow.WorkflowStatus toProto(Workflow.WorkflowStatus from) {\n        WorkflowPb.Workflow.WorkflowStatus to;\n        switch (from) {\n            case RUNNING: to = WorkflowPb.Workflow.WorkflowStatus.RUNNING; break;\n            case COMPLETED: to = WorkflowPb.Workflow.WorkflowStatus.COMPLETED; break;\n            case FAILED: to = WorkflowPb.Workflow.WorkflowStatus.FAILED; break;\n            case TIMED_OUT: to = WorkflowPb.Workflow.WorkflowStatus.TIMED_OUT; break;\n            case TERMINATED: to = WorkflowPb.Workflow.WorkflowStatus.TERMINATED; break;\n            case PAUSED: to = WorkflowPb.Workflow.WorkflowStatus.PAUSED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public Workflow.WorkflowStatus fromProto(WorkflowPb.Workflow.WorkflowStatus from) {\n        Workflow.WorkflowStatus to;\n        switch (from) {\n            case RUNNING: to = Workflow.WorkflowStatus.RUNNING; break;\n            case COMPLETED: to = Workflow.WorkflowStatus.COMPLETED; break;\n            case FAILED: to = Workflow.WorkflowStatus.FAILED; break;\n            case TIMED_OUT: to = Workflow.WorkflowStatus.TIMED_OUT; break;\n            case TERMINATED: to = Workflow.WorkflowStatus.TERMINATED; break;\n            case PAUSED: to = Workflow.WorkflowStatus.PAUSED; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public WorkflowDefPb.WorkflowDef toProto(WorkflowDef from) {\n        WorkflowDefPb.WorkflowDef.Builder to = WorkflowDefPb.WorkflowDef.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getDescription() != null) {\n            to.setDescription( from.getDescription() );\n        }\n        to.setVersion( from.getVersion() );\n        for (WorkflowTask elem : from.getTasks()) {\n            to.addTasks( toProto(elem) );\n        }\n        to.addAllInputParameters( from.getInputParameters() );\n        for (Map.Entry<String, Object> pair : from.getOutputParameters().entrySet()) {\n            to.putOutputParameters( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getFailureWorkflow() != null) {\n            to.setFailureWorkflow( from.getFailureWorkflow() );\n        }\n        to.setSchemaVersion( from.getSchemaVersion() );\n        to.setRestartable( from.isRestartable() );\n        to.setWorkflowStatusListenerEnabled( from.isWorkflowStatusListenerEnabled() );\n        if (from.getOwnerEmail() != null) {\n            to.setOwnerEmail( from.getOwnerEmail() );\n        }\n        if (from.getTimeoutPolicy() != null) {\n            to.setTimeoutPolicy( toProto( from.getTimeoutPolicy() ) );\n        }\n        to.setTimeoutSeconds( from.getTimeoutSeconds() );\n        for (Map.Entry<String, Object> pair : from.getVariables().entrySet()) {\n            to.putVariables( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        for (Map.Entry<String, Object> pair : from.getInputTemplate().entrySet()) {\n            to.putInputTemplate( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        return to.build();\n    }\n\n    public WorkflowDef fromProto(WorkflowDefPb.WorkflowDef from) {\n        WorkflowDef to = new WorkflowDef();\n        to.setName( from.getName() );\n        to.setDescription( from.getDescription() );\n        to.setVersion( from.getVersion() );\n        to.setTasks( from.getTasksList().stream().map(this::fromProto).collect(Collectors.toCollection(ArrayList::new)) );\n        to.setInputParameters( from.getInputParametersList().stream().collect(Collectors.toCollection(ArrayList::new)) );\n        Map<String, Object> outputParametersMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getOutputParametersMap().entrySet()) {\n            outputParametersMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setOutputParameters(outputParametersMap);\n        to.setFailureWorkflow( from.getFailureWorkflow() );\n        to.setSchemaVersion( from.getSchemaVersion() );\n        to.setRestartable( from.getRestartable() );\n        to.setWorkflowStatusListenerEnabled( from.getWorkflowStatusListenerEnabled() );\n        to.setOwnerEmail( from.getOwnerEmail() );\n        to.setTimeoutPolicy( fromProto( from.getTimeoutPolicy() ) );\n        to.setTimeoutSeconds( from.getTimeoutSeconds() );\n        Map<String, Object> variablesMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getVariablesMap().entrySet()) {\n            variablesMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setVariables(variablesMap);\n        Map<String, Object> inputTemplateMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputTemplateMap().entrySet()) {\n            inputTemplateMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInputTemplate(inputTemplateMap);\n        return to;\n    }\n\n    public WorkflowDefPb.WorkflowDef.TimeoutPolicy toProto(WorkflowDef.TimeoutPolicy from) {\n        WorkflowDefPb.WorkflowDef.TimeoutPolicy to;\n        switch (from) {\n            case TIME_OUT_WF: to = WorkflowDefPb.WorkflowDef.TimeoutPolicy.TIME_OUT_WF; break;\n            case ALERT_ONLY: to = WorkflowDefPb.WorkflowDef.TimeoutPolicy.ALERT_ONLY; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public WorkflowDef.TimeoutPolicy fromProto(WorkflowDefPb.WorkflowDef.TimeoutPolicy from) {\n        WorkflowDef.TimeoutPolicy to;\n        switch (from) {\n            case TIME_OUT_WF: to = WorkflowDef.TimeoutPolicy.TIME_OUT_WF; break;\n            case ALERT_ONLY: to = WorkflowDef.TimeoutPolicy.ALERT_ONLY; break;\n            default: throw new IllegalArgumentException(\"Unexpected enum constant: \" + from);\n        }\n        return to;\n    }\n\n    public WorkflowDefSummaryPb.WorkflowDefSummary toProto(WorkflowDefSummary from) {\n        WorkflowDefSummaryPb.WorkflowDefSummary.Builder to = WorkflowDefSummaryPb.WorkflowDefSummary.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        to.setVersion( from.getVersion() );\n        if (from.getCreateTime() != null) {\n            to.setCreateTime( from.getCreateTime() );\n        }\n        return to.build();\n    }\n\n    public WorkflowDefSummary fromProto(WorkflowDefSummaryPb.WorkflowDefSummary from) {\n        WorkflowDefSummary to = new WorkflowDefSummary();\n        to.setName( from.getName() );\n        to.setVersion( from.getVersion() );\n        to.setCreateTime( from.getCreateTime() );\n        return to;\n    }\n\n    public WorkflowSummaryPb.WorkflowSummary toProto(WorkflowSummary from) {\n        WorkflowSummaryPb.WorkflowSummary.Builder to = WorkflowSummaryPb.WorkflowSummary.newBuilder();\n        if (from.getWorkflowType() != null) {\n            to.setWorkflowType( from.getWorkflowType() );\n        }\n        to.setVersion( from.getVersion() );\n        if (from.getWorkflowId() != null) {\n            to.setWorkflowId( from.getWorkflowId() );\n        }\n        if (from.getCorrelationId() != null) {\n            to.setCorrelationId( from.getCorrelationId() );\n        }\n        if (from.getStartTime() != null) {\n            to.setStartTime( from.getStartTime() );\n        }\n        if (from.getUpdateTime() != null) {\n            to.setUpdateTime( from.getUpdateTime() );\n        }\n        if (from.getEndTime() != null) {\n            to.setEndTime( from.getEndTime() );\n        }\n        if (from.getStatus() != null) {\n            to.setStatus( toProto( from.getStatus() ) );\n        }\n        if (from.getInput() != null) {\n            to.setInput( from.getInput() );\n        }\n        if (from.getOutput() != null) {\n            to.setOutput( from.getOutput() );\n        }\n        if (from.getReasonForIncompletion() != null) {\n            to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        }\n        to.setExecutionTime( from.getExecutionTime() );\n        if (from.getEvent() != null) {\n            to.setEvent( from.getEvent() );\n        }\n        if (from.getFailedReferenceTaskNames() != null) {\n            to.setFailedReferenceTaskNames( from.getFailedReferenceTaskNames() );\n        }\n        if (from.getExternalInputPayloadStoragePath() != null) {\n            to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        }\n        if (from.getExternalOutputPayloadStoragePath() != null) {\n            to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        }\n        to.setPriority( from.getPriority() );\n        to.addAllFailedTaskNames( from.getFailedTaskNames() );\n        return to.build();\n    }\n\n    public WorkflowSummary fromProto(WorkflowSummaryPb.WorkflowSummary from) {\n        WorkflowSummary to = new WorkflowSummary();\n        to.setWorkflowType( from.getWorkflowType() );\n        to.setVersion( from.getVersion() );\n        to.setWorkflowId( from.getWorkflowId() );\n        to.setCorrelationId( from.getCorrelationId() );\n        to.setStartTime( from.getStartTime() );\n        to.setUpdateTime( from.getUpdateTime() );\n        to.setEndTime( from.getEndTime() );\n        to.setStatus( fromProto( from.getStatus() ) );\n        to.setInput( from.getInput() );\n        to.setOutput( from.getOutput() );\n        to.setReasonForIncompletion( from.getReasonForIncompletion() );\n        to.setExecutionTime( from.getExecutionTime() );\n        to.setEvent( from.getEvent() );\n        to.setFailedReferenceTaskNames( from.getFailedReferenceTaskNames() );\n        to.setExternalInputPayloadStoragePath( from.getExternalInputPayloadStoragePath() );\n        to.setExternalOutputPayloadStoragePath( from.getExternalOutputPayloadStoragePath() );\n        to.setPriority( from.getPriority() );\n        to.setFailedTaskNames( from.getFailedTaskNamesList().stream().collect(Collectors.toCollection(HashSet::new)) );\n        return to;\n    }\n\n    public WorkflowTaskPb.WorkflowTask toProto(WorkflowTask from) {\n        WorkflowTaskPb.WorkflowTask.Builder to = WorkflowTaskPb.WorkflowTask.newBuilder();\n        if (from.getName() != null) {\n            to.setName( from.getName() );\n        }\n        if (from.getTaskReferenceName() != null) {\n            to.setTaskReferenceName( from.getTaskReferenceName() );\n        }\n        if (from.getDescription() != null) {\n            to.setDescription( from.getDescription() );\n        }\n        for (Map.Entry<String, Object> pair : from.getInputParameters().entrySet()) {\n            to.putInputParameters( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getType() != null) {\n            to.setType( from.getType() );\n        }\n        if (from.getDynamicTaskNameParam() != null) {\n            to.setDynamicTaskNameParam( from.getDynamicTaskNameParam() );\n        }\n        if (from.getCaseValueParam() != null) {\n            to.setCaseValueParam( from.getCaseValueParam() );\n        }\n        if (from.getCaseExpression() != null) {\n            to.setCaseExpression( from.getCaseExpression() );\n        }\n        if (from.getScriptExpression() != null) {\n            to.setScriptExpression( from.getScriptExpression() );\n        }\n        for (Map.Entry<String, List<WorkflowTask>> pair : from.getDecisionCases().entrySet()) {\n            to.putDecisionCases( pair.getKey(), toProto( pair.getValue() ) );\n        }\n        if (from.getDynamicForkTasksParam() != null) {\n            to.setDynamicForkTasksParam( from.getDynamicForkTasksParam() );\n        }\n        if (from.getDynamicForkTasksInputParamName() != null) {\n            to.setDynamicForkTasksInputParamName( from.getDynamicForkTasksInputParamName() );\n        }\n        for (WorkflowTask elem : from.getDefaultCase()) {\n            to.addDefaultCase( toProto(elem) );\n        }\n        for (List<WorkflowTask> elem : from.getForkTasks()) {\n            to.addForkTasks( toProto(elem) );\n        }\n        to.setStartDelay( from.getStartDelay() );\n        if (from.getSubWorkflowParam() != null) {\n            to.setSubWorkflowParam( toProto( from.getSubWorkflowParam() ) );\n        }\n        to.addAllJoinOn( from.getJoinOn() );\n        if (from.getSink() != null) {\n            to.setSink( from.getSink() );\n        }\n        to.setOptional( from.isOptional() );\n        if (from.getTaskDefinition() != null) {\n            to.setTaskDefinition( toProto( from.getTaskDefinition() ) );\n        }\n        if (from.isRateLimited() != null) {\n            to.setRateLimited( from.isRateLimited() );\n        }\n        to.addAllDefaultExclusiveJoinTask( from.getDefaultExclusiveJoinTask() );\n        if (from.isAsyncComplete() != null) {\n            to.setAsyncComplete( from.isAsyncComplete() );\n        }\n        if (from.getLoopCondition() != null) {\n            to.setLoopCondition( from.getLoopCondition() );\n        }\n        for (WorkflowTask elem : from.getLoopOver()) {\n            to.addLoopOver( toProto(elem) );\n        }\n        if (from.getRetryCount() != null) {\n            to.setRetryCount( from.getRetryCount() );\n        }\n        if (from.getEvaluatorType() != null) {\n            to.setEvaluatorType( from.getEvaluatorType() );\n        }\n        if (from.getExpression() != null) {\n            to.setExpression( from.getExpression() );\n        }\n        return to.build();\n    }\n\n    public WorkflowTask fromProto(WorkflowTaskPb.WorkflowTask from) {\n        WorkflowTask to = new WorkflowTask();\n        to.setName( from.getName() );\n        to.setTaskReferenceName( from.getTaskReferenceName() );\n        to.setDescription( from.getDescription() );\n        Map<String, Object> inputParametersMap = new HashMap<String, Object>();\n        for (Map.Entry<String, Value> pair : from.getInputParametersMap().entrySet()) {\n            inputParametersMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setInputParameters(inputParametersMap);\n        to.setType( from.getType() );\n        to.setDynamicTaskNameParam( from.getDynamicTaskNameParam() );\n        to.setCaseValueParam( from.getCaseValueParam() );\n        to.setCaseExpression( from.getCaseExpression() );\n        to.setScriptExpression( from.getScriptExpression() );\n        Map<String, List<WorkflowTask>> decisionCasesMap = new HashMap<String, List<WorkflowTask>>();\n        for (Map.Entry<String, WorkflowTaskPb.WorkflowTask.WorkflowTaskList> pair : from.getDecisionCasesMap().entrySet()) {\n            decisionCasesMap.put( pair.getKey(), fromProto( pair.getValue() ) );\n        }\n        to.setDecisionCases(decisionCasesMap);\n        to.setDynamicForkTasksParam( from.getDynamicForkTasksParam() );\n        to.setDynamicForkTasksInputParamName( from.getDynamicForkTasksInputParamName() );\n        to.setDefaultCase( from.getDefaultCaseList().stream().map(this::fromProto).collect(Collectors.toCollection(ArrayList::new)) );\n        to.setForkTasks( from.getForkTasksList().stream().map(this::fromProto).collect(Collectors.toCollection(ArrayList::new)) );\n        to.setStartDelay( from.getStartDelay() );\n        if (from.hasSubWorkflowParam()) {\n            to.setSubWorkflowParam( fromProto( from.getSubWorkflowParam() ) );\n        }\n        to.setJoinOn( from.getJoinOnList().stream().collect(Collectors.toCollection(ArrayList::new)) );\n        to.setSink( from.getSink() );\n        to.setOptional( from.getOptional() );\n        if (from.hasTaskDefinition()) {\n            to.setTaskDefinition( fromProto( from.getTaskDefinition() ) );\n        }\n        to.setRateLimited( from.getRateLimited() );\n        to.setDefaultExclusiveJoinTask( from.getDefaultExclusiveJoinTaskList().stream().collect(Collectors.toCollection(ArrayList::new)) );\n        to.setAsyncComplete( from.getAsyncComplete() );\n        to.setLoopCondition( from.getLoopCondition() );\n        to.setLoopOver( from.getLoopOverList().stream().map(this::fromProto).collect(Collectors.toCollection(ArrayList::new)) );\n        to.setRetryCount( from.getRetryCount() );\n        to.setEvaluatorType( from.getEvaluatorType() );\n        to.setExpression( from.getExpression() );\n        return to;\n    }\n\n    public abstract WorkflowTaskPb.WorkflowTask.WorkflowTaskList toProto(List<WorkflowTask> in);\n\n    public abstract List<WorkflowTask> fromProto(WorkflowTaskPb.WorkflowTask.WorkflowTaskList in);\n\n    public abstract Value toProto(Object in);\n\n    public abstract Object fromProto(Value in);\n\n    public abstract Any toProto(Any in);\n\n    public abstract Any fromProto(Any in);\n}\n"
  },
  {
    "path": "grpc/src/main/java/com/netflix/conductor/grpc/ProtoMapper.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc;\n\nimport com.google.protobuf.Any;\nimport com.google.protobuf.ListValue;\nimport com.google.protobuf.NullValue;\nimport com.google.protobuf.Struct;\nimport com.google.protobuf.Value;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.proto.WorkflowTaskPb;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * ProtoMapper implements conversion code between the internal models\n * used by Conductor (POJOs) and their corresponding equivalents in\n * the exposed Protocol Buffers interface.\n *\n * The vast majority of the mapping logic is implemented in the autogenerated\n * {@link AbstractProtoMapper} class. This class only implements the custom\n * logic for objects that need to be special cased in the API.\n */\npublic final class ProtoMapper extends AbstractProtoMapper {\n    public static final ProtoMapper INSTANCE = new ProtoMapper();\n    private static final int NO_RETRY_VALUE = -1;\n\n    private ProtoMapper() {}\n\n    /**\n     * Convert an {@link Object} instance into its equivalent {@link Value}\n     * ProtoBuf object.\n     *\n     * The {@link Value} ProtoBuf message is a variant type that can define any\n     * value representable as a native JSON type. Consequently, this method expects\n     * the given {@link Object} instance to be a Java object instance of JSON-native\n     * value, namely: null, {@link Boolean}, {@link Double}, {@link String},\n     * {@link Map}, {@link List}.\n     *\n     * Any other values will cause an exception to be thrown.\n     * See {@link ProtoMapper#fromProto(Value)} for the reverse mapping.\n     *\n     * @param val a Java object that can be represented natively in JSON\n     * @return an instance of a {@link Value} ProtoBuf message\n     */\n    @Override\n    public Value toProto(Object val) {\n        Value.Builder builder = Value.newBuilder();\n\n        if (val == null) {\n            builder.setNullValue(NullValue.NULL_VALUE);\n        } else if (val instanceof Boolean) {\n            builder.setBoolValue((Boolean) val);\n        } else if (val instanceof Double) {\n            builder.setNumberValue((Double) val);\n        } else if (val instanceof String) {\n            builder.setStringValue((String) val);\n        } else if (val instanceof Map) {\n            Map<String, Object> map = (Map<String, Object>) val;\n            Struct.Builder struct = Struct.newBuilder();\n            for (Map.Entry<String, Object> pair : map.entrySet()) {\n                struct.putFields(pair.getKey(), toProto(pair.getValue()));\n            }\n            builder.setStructValue(struct.build());\n        } else if (val instanceof List) {\n            ListValue.Builder list = ListValue.newBuilder();\n            for (Object obj : (List<Object>)val) {\n                list.addValues(toProto(obj));\n            }\n            builder.setListValue(list.build());\n        } else {\n            throw new ClassCastException(\"cannot map to Value type: \"+val);\n        }\n        return builder.build();\n    }\n\n    /**\n     * Convert a ProtoBuf {@link Value} message into its native Java object\n     * equivalent.\n     *\n     * See {@link ProtoMapper#toProto(Object)} for the reverse mapping and the\n     * possible values that can be returned from this method.\n     *\n     * @param any an instance of a ProtoBuf {@link Value} message\n     * @return a native Java object representing the value\n     */\n    @Override\n    public Object fromProto(Value any) {\n        switch (any.getKindCase()) {\n            case NULL_VALUE:\n                return null;\n            case BOOL_VALUE:\n                return any.getBoolValue();\n            case NUMBER_VALUE:\n                return any.getNumberValue();\n            case STRING_VALUE:\n                return any.getStringValue();\n            case STRUCT_VALUE:\n                Struct struct = any.getStructValue();\n                Map<String, Object> map = new HashMap<>();\n                for (Map.Entry<String, Value> pair : struct.getFieldsMap().entrySet()) {\n                    map.put(pair.getKey(), fromProto(pair.getValue()));\n                }\n                return map;\n            case LIST_VALUE:\n                List<Object> list = new ArrayList<>();\n                for (Value val : any.getListValue().getValuesList()) {\n                    list.add(fromProto(val));\n                }\n                return list;\n            default:\n                throw new ClassCastException(\"unset Value element: \"+any);\n        }\n    }\n\n    /**\n     * Convert a WorkflowTaskList message wrapper into a {@link List} instance\n     * with its contents.\n     *\n     * @param list an instance of a ProtoBuf message\n     * @return a list with the contents of the message\n     */\n    @Override\n    public List<WorkflowTask> fromProto(WorkflowTaskPb.WorkflowTask.WorkflowTaskList list) {\n        return list.getTasksList().stream().map(this::fromProto).collect(Collectors.toList());\n    }\n\n    @Override public WorkflowTaskPb.WorkflowTask toProto(final WorkflowTask from) {\n        final WorkflowTaskPb.WorkflowTask.Builder to = WorkflowTaskPb.WorkflowTask.newBuilder(super.toProto(from));\n        if (from.getRetryCount() == null) {\n            to.setRetryCount(NO_RETRY_VALUE);\n        }\n        return to.build();\n    }\n\n    @Override public WorkflowTask fromProto(final WorkflowTaskPb.WorkflowTask from) {\n        final WorkflowTask workflowTask = super.fromProto(from);\n        if (from.getRetryCount() == NO_RETRY_VALUE) {\n            workflowTask.setRetryCount(null);\n        }\n        return workflowTask;\n    }\n\n\n\n    /**\n     * Convert a list of {@link WorkflowTask} instances into a ProtoBuf wrapper object.\n     *\n     * @param list a list of {@link WorkflowTask} instances\n     * @return a ProtoBuf message wrapping the contents of the list\n     */\n    @Override\n    public WorkflowTaskPb.WorkflowTask.WorkflowTaskList toProto(List<WorkflowTask> list) {\n        return WorkflowTaskPb.WorkflowTask.WorkflowTaskList.newBuilder()\n                .addAllTasks(list.stream().map(this::toProto)::iterator)\n                .build();\n    }\n\n    @Override\n    public Any toProto(Any in) {\n        return in;\n    }\n\n    @Override\n    public Any fromProto(Any in) {\n        return in;\n    }\n}\n"
  },
  {
    "path": "grpc/src/main/proto/grpc/event_service.proto",
    "content": "syntax = \"proto3\";\npackage conductor.grpc.events;\n\nimport \"model/eventhandler.proto\";\n\noption java_package = \"com.netflix.conductor.grpc\";\noption java_outer_classname = \"EventServicePb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/grpc/events\";\n\nservice EventService {\n    // POST /\n    rpc AddEventHandler(AddEventHandlerRequest) returns (AddEventHandlerResponse);\n\n    // PUT /\n    rpc UpdateEventHandler(UpdateEventHandlerRequest) returns (UpdateEventHandlerResponse);\n\n    // DELETE /{name}\n    rpc RemoveEventHandler(RemoveEventHandlerRequest) returns (RemoveEventHandlerResponse);\n\n    // GET /\n    rpc GetEventHandlers(GetEventHandlersRequest) returns (stream conductor.proto.EventHandler);\n\n    // GET /{name}\n    rpc GetEventHandlersForEvent(GetEventHandlersForEventRequest) returns (stream conductor.proto.EventHandler);\n}\n\nmessage AddEventHandlerRequest {\n    conductor.proto.EventHandler handler = 1;\n}\n\nmessage AddEventHandlerResponse {}\n\nmessage UpdateEventHandlerRequest {\n    conductor.proto.EventHandler handler = 1;\n}\n\nmessage UpdateEventHandlerResponse {}\n\nmessage RemoveEventHandlerRequest {\n    string name = 1;\n}\n\nmessage RemoveEventHandlerResponse {}\n\nmessage GetEventHandlersRequest {}\n\nmessage GetEventHandlersForEventRequest {\n    string event = 1;\n    bool active_only = 2;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/grpc/metadata_service.proto",
    "content": "syntax = \"proto3\";\npackage conductor.grpc.metadata;\n\nimport \"model/taskdef.proto\";\nimport \"model/workflowdef.proto\";\n\noption java_package = \"com.netflix.conductor.grpc\";\noption java_outer_classname = \"MetadataServicePb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/grpc/metadata\";\n\nservice MetadataService {\n    // POST /workflow\n    rpc CreateWorkflow(CreateWorkflowRequest) returns (CreateWorkflowResponse);\n\n    // POST /workflow/validate\n    rpc ValidateWorkflow(ValidateWorkflowRequest) returns (ValidateWorkflowResponse);\n\n    // PUT /workflow\n    rpc UpdateWorkflows(UpdateWorkflowsRequest) returns (UpdateWorkflowsResponse);\n\n    // GET /workflow/{name}\n    rpc GetWorkflow(GetWorkflowRequest) returns (GetWorkflowResponse);\n\n    // POST /taskdefs\n    rpc CreateTasks(CreateTasksRequest) returns (CreateTasksResponse);\n\n    // PUT /taskdefs\n    rpc UpdateTask(UpdateTaskRequest) returns (UpdateTaskResponse);\n\n    // GET /taskdefs/{tasktype}\n    rpc GetTask(GetTaskRequest) returns (GetTaskResponse);\n\n    // DELETE /taskdefs/{tasktype}\n    rpc DeleteTask(DeleteTaskRequest) returns (DeleteTaskResponse);\n}\n\nmessage CreateWorkflowRequest {\n    conductor.proto.WorkflowDef workflow = 1;\n}\n\nmessage CreateWorkflowResponse {}\n\nmessage ValidateWorkflowRequest {\n    conductor.proto.WorkflowDef workflow = 1;\n}\n\nmessage ValidateWorkflowResponse {}\n\nmessage UpdateWorkflowsRequest {\n    repeated conductor.proto.WorkflowDef defs = 1;\n}\n\nmessage UpdateWorkflowsResponse {}\n\nmessage GetWorkflowRequest {\n    string name = 1;\n    int32 version = 2;\n}\n\nmessage GetWorkflowResponse {\n    conductor.proto.WorkflowDef workflow = 1;\n}\n\nmessage CreateTasksRequest {\n    repeated conductor.proto.TaskDef defs = 1;\n}\n\nmessage CreateTasksResponse {}\n\nmessage UpdateTaskRequest {\n    conductor.proto.TaskDef task = 1;\n}\n\nmessage UpdateTaskResponse {}\n\n\nmessage GetTaskRequest {\n    string task_type = 1;\n}\n\nmessage GetTaskResponse {\n    conductor.proto.TaskDef task = 1;\n}\n\nmessage DeleteTaskRequest {\n    string task_type = 1;\n}\n\nmessage DeleteTaskResponse {}\n"
  },
  {
    "path": "grpc/src/main/proto/grpc/search.proto",
    "content": "syntax = \"proto3\";\npackage conductor.grpc.search;\n\noption java_package = \"com.netflix.conductor.grpc\";\noption java_outer_classname = \"SearchPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/grpc/search\";\n\nmessage Request {\n    int32 start = 1;\n    int32 size = 2;\n    string sort = 3;\n    string free_text = 4;\n    string query = 5;\n}\n\n"
  },
  {
    "path": "grpc/src/main/proto/grpc/task_service.proto",
    "content": "syntax = \"proto3\";\npackage conductor.grpc.tasks;\n\nimport \"model/taskexeclog.proto\";\nimport \"model/taskresult.proto\";\nimport \"model/tasksummary.proto\";\nimport \"model/task.proto\";\nimport \"grpc/search.proto\";\n\noption java_package = \"com.netflix.conductor.grpc\";\noption java_outer_classname = \"TaskServicePb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/grpc/tasks\";\n\nservice TaskService {\n    // GET /poll/{tasktype}\n    rpc Poll(PollRequest) returns (PollResponse);\n\n    // /poll/batch/{tasktype}\n    rpc BatchPoll(BatchPollRequest) returns (stream conductor.proto.Task);\n\n    // POST /\n    rpc UpdateTask(UpdateTaskRequest) returns (UpdateTaskResponse);\n\n    // POST /{taskId}/log\n    rpc AddLog(AddLogRequest) returns (AddLogResponse);\n\n    // GET {taskId}/log\n    rpc GetTaskLogs(GetTaskLogsRequest) returns (GetTaskLogsResponse);\n\n    // GET /{taskId}\n    rpc GetTask(GetTaskRequest) returns (GetTaskResponse);\n\n    // GET /queue/sizes\n    rpc GetQueueSizesForTasks(QueueSizesRequest) returns (QueueSizesResponse);\n\n    // GET /queue/all\n    rpc GetQueueInfo(QueueInfoRequest) returns (QueueInfoResponse);\n\n    // GET /queue/all/verbose\n    rpc GetQueueAllInfo(QueueAllInfoRequest) returns (QueueAllInfoResponse);\n\n    // GET /search\n    rpc Search(conductor.grpc.search.Request) returns (TaskSummarySearchResult);\n\n    // GET /searchV2\n    rpc SearchV2(conductor.grpc.search.Request) returns (TaskSearchResult);\n}\n\nmessage PollRequest {\n    string task_type = 1;\n    string worker_id = 2;\n    string domain = 3;\n}\n\nmessage PollResponse {\n    conductor.proto.Task task = 1;\n}\n\nmessage BatchPollRequest {\n    string task_type = 1;\n    string worker_id = 2;\n    string domain = 3;\n    int32 count = 4;\n    int32 timeout = 5;\n}\n\nmessage UpdateTaskRequest {\n    conductor.proto.TaskResult result = 1;\n}\n\nmessage UpdateTaskResponse {\n    string task_id = 1;\n}\n\nmessage AddLogRequest {\n    string task_id = 1;\n    string log = 2;\n}\n\nmessage AddLogResponse {}\n\nmessage GetTaskLogsRequest {\n    string task_id = 1;\n}\n\nmessage GetTaskLogsResponse {\n    repeated conductor.proto.TaskExecLog logs = 1;\n}\n\nmessage GetTaskRequest {\n    string task_id = 1;\n}\n\nmessage GetTaskResponse {\n    conductor.proto.Task task = 1;\n}\n\nmessage QueueSizesRequest {\n    repeated string task_types = 1;\n}\n\nmessage QueueSizesResponse {\n    map<string, int32> queue_for_task = 1;\n}\n\nmessage QueueInfoRequest {}\n\nmessage QueueInfoResponse {\n    map<string, int64> queues = 1;\n}\n\nmessage QueueAllInfoRequest {}\n\nmessage QueueAllInfoResponse {\n    message ShardInfo {\n        int64 size = 1;\n        int64 uacked = 2;\n    }\n    message QueueInfo {\n        map<string, ShardInfo> shards = 1;\n    }\n    map<string, QueueInfo> queues = 1;\n}\n\nmessage TaskSummarySearchResult {\n    int64 total_hits = 1;\n    repeated conductor.proto.TaskSummary results = 2;\n}\n\nmessage TaskSearchResult {\n    int64 total_hits = 1;\n    repeated conductor.proto.Task results = 2;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/grpc/workflow_service.proto",
    "content": "syntax = \"proto3\";\npackage conductor.grpc.workflows;\n\nimport \"grpc/search.proto\";\nimport \"model/workflow.proto\";\nimport \"model/workflowsummary.proto\";\nimport \"model/skiptaskrequest.proto\";\nimport \"model/startworkflowrequest.proto\";\nimport \"model/rerunworkflowrequest.proto\";\n\noption java_package = \"com.netflix.conductor.grpc\";\noption java_outer_classname = \"WorkflowServicePb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/grpc/workflows\";\n\nservice WorkflowService {\n    // POST /\n    rpc StartWorkflow(conductor.proto.StartWorkflowRequest) returns (StartWorkflowResponse);\n\n    // GET /{name}/correlated/{correlationId}\n    rpc GetWorkflows(GetWorkflowsRequest) returns (GetWorkflowsResponse);\n\n    // GET /{workflowId}\n    rpc GetWorkflowStatus(GetWorkflowStatusRequest) returns (conductor.proto.Workflow);\n\n    // DELETE /{workflodId}/remove\n    rpc RemoveWorkflow(RemoveWorkflowRequest) returns (RemoveWorkflowResponse);\n\n    // GET /running/{name}\n    rpc GetRunningWorkflows(GetRunningWorkflowsRequest) returns (GetRunningWorkflowsResponse);\n\n    // PUT /decide/{workflowId}\n    rpc DecideWorkflow(DecideWorkflowRequest) returns (DecideWorkflowResponse);\n\n    // PUT /{workflowId}/pause\n    rpc PauseWorkflow(PauseWorkflowRequest) returns (PauseWorkflowResponse);\n\n    // PUT /{workflowId}/pause\n    rpc ResumeWorkflow(ResumeWorkflowRequest) returns (ResumeWorkflowResponse);\n\n    // PUT /{workflowId}/skiptask/{taskReferenceName}\n    rpc SkipTaskFromWorkflow(SkipTaskRequest) returns (SkipTaskResponse);\n\n    // POST /{workflowId}/rerun\n    rpc RerunWorkflow(conductor.proto.RerunWorkflowRequest) returns (RerunWorkflowResponse);\n\n    // POST /{workflowId}/restart\n    rpc RestartWorkflow(RestartWorkflowRequest) returns (RestartWorkflowResponse);\n\n    // POST /{workflowId}retry\n    rpc RetryWorkflow(RetryWorkflowRequest) returns (RetryWorkflowResponse);\n\n    // POST /{workflowId}/resetcallbacks\n    rpc ResetWorkflowCallbacks(ResetWorkflowCallbacksRequest) returns (ResetWorkflowCallbacksResponse);\n\n    // DELETE /{workflowId}\n    rpc TerminateWorkflow(TerminateWorkflowRequest) returns (TerminateWorkflowResponse);\n\n    // GET /search\n    rpc Search(conductor.grpc.search.Request) returns (WorkflowSummarySearchResult);\n    rpc SearchByTasks(conductor.grpc.search.Request) returns (WorkflowSummarySearchResult);\n\n    // GET /searchV2\n    rpc SearchV2(conductor.grpc.search.Request) returns (WorkflowSearchResult);\n    rpc SearchByTasksV2(conductor.grpc.search.Request) returns (WorkflowSearchResult);\n}\n\nmessage StartWorkflowResponse {\n    string workflow_id = 1;\n}\n\nmessage GetWorkflowsRequest {\n    string name = 1;\n    repeated string correlation_id = 2;\n    bool include_closed = 3;\n    bool include_tasks = 4;\n}\n\nmessage GetWorkflowsResponse {\n    message Workflows {\n        repeated conductor.proto.Workflow workflows = 1;\n    }\n    map<string, Workflows> workflows_by_id = 1;\n}\n\nmessage GetWorkflowStatusRequest {\n    string workflow_id = 1;\n    bool include_tasks = 2;\n}\n\nmessage GetWorkflowStatusResponse {\n    conductor.proto.Workflow workflow = 1;\n}\n\nmessage RemoveWorkflowRequest {\n    string workflod_id = 1;\n    bool archive_workflow = 2;\n}\n\nmessage RemoveWorkflowResponse {}\n\nmessage GetRunningWorkflowsRequest {\n    string name = 1;\n    int32 version = 2;\n    int64 start_time = 3;\n    int64 end_time = 4;\n}\n\nmessage GetRunningWorkflowsResponse {\n    repeated string workflow_ids = 1;\n}\n\nmessage DecideWorkflowRequest {\n    string workflow_id = 1;\n}\n\nmessage DecideWorkflowResponse {}\n\nmessage PauseWorkflowRequest {\n    string workflow_id = 1;\n}\n\nmessage PauseWorkflowResponse {}\n\nmessage ResumeWorkflowRequest {\n    string workflow_id = 1;\n}\n\nmessage ResumeWorkflowResponse {}\n\nmessage SkipTaskRequest {\n    string workflow_id = 1;\n    string task_reference_name = 2;\n    conductor.proto.SkipTaskRequest request = 3;\n}\n\nmessage SkipTaskResponse {}\n\nmessage RerunWorkflowResponse {\n    string workflow_id = 1;\n}\n\nmessage RestartWorkflowRequest {\n    string workflow_id = 1;\n    bool use_latest_definitions = 2;\n}\n\nmessage RestartWorkflowResponse {}\n\nmessage RetryWorkflowRequest {\n    string workflow_id = 1;\n    bool resume_subworkflow_tasks = 2;\n}\n\nmessage RetryWorkflowResponse {}\n\nmessage ResetWorkflowCallbacksRequest {\n    string workflow_id = 1;\n}\n\nmessage ResetWorkflowCallbacksResponse {}\n\nmessage TerminateWorkflowRequest {\n    string workflow_id = 1;\n    string reason = 2;\n}\n\nmessage TerminateWorkflowResponse {}\n\nmessage WorkflowSummarySearchResult {\n    int64 total_hits = 1;\n    repeated conductor.proto.WorkflowSummary results = 2;\n}\n\nmessage WorkflowSearchResult {\n    int64 total_hits = 1;\n    repeated conductor.proto.Workflow results = 2;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/dynamicforkjointask.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"DynamicForkJoinTaskPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage DynamicForkJoinTask {\n    string task_name = 1;\n    string workflow_name = 2;\n    string reference_name = 3;\n    map<string, google.protobuf.Value> input = 4;\n    string type = 5;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/dynamicforkjointasklist.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/dynamicforkjointask.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"DynamicForkJoinTaskListPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage DynamicForkJoinTaskList {\n    repeated DynamicForkJoinTask dynamic_tasks = 1;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/eventexecution.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/eventhandler.proto\";\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"EventExecutionPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage EventExecution {\n    enum Status {\n        IN_PROGRESS = 0;\n        COMPLETED = 1;\n        FAILED = 2;\n        SKIPPED = 3;\n    }\n    string id = 1;\n    string message_id = 2;\n    string name = 3;\n    string event = 4;\n    int64 created = 5;\n    EventExecution.Status status = 6;\n    EventHandler.Action.Type action = 7;\n    map<string, google.protobuf.Value> output = 8;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/eventhandler.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"google/protobuf/struct.proto\";\nimport \"google/protobuf/any.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"EventHandlerPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage EventHandler {\n    message StartWorkflow {\n        string name = 1;\n        int32 version = 2;\n        string correlation_id = 3;\n        map<string, google.protobuf.Value> input = 4;\n        google.protobuf.Any input_message = 5;\n        map<string, string> task_to_domain = 6;\n    }\n    message TaskDetails {\n        string workflow_id = 1;\n        string task_ref_name = 2;\n        map<string, google.protobuf.Value> output = 3;\n        google.protobuf.Any output_message = 4;\n        string task_id = 5;\n    }\n    message Action {\n        enum Type {\n            START_WORKFLOW = 0;\n            COMPLETE_TASK = 1;\n            FAIL_TASK = 2;\n        }\n        EventHandler.Action.Type action = 1;\n        EventHandler.StartWorkflow start_workflow = 2;\n        EventHandler.TaskDetails complete_task = 3;\n        EventHandler.TaskDetails fail_task = 4;\n        bool expand_inline_json = 5;\n    }\n    string name = 1;\n    string event = 2;\n    string condition = 3;\n    repeated EventHandler.Action actions = 4;\n    bool active = 5;\n    string evaluator_type = 6;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/polldata.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"PollDataPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage PollData {\n    string queue_name = 1;\n    string domain = 2;\n    string worker_id = 3;\n    int64 last_poll_time = 4;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/rerunworkflowrequest.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"RerunWorkflowRequestPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage RerunWorkflowRequest {\n    string re_run_from_workflow_id = 1;\n    map<string, google.protobuf.Value> workflow_input = 2;\n    string re_run_from_task_id = 3;\n    map<string, google.protobuf.Value> task_input = 4;\n    string correlation_id = 5;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/skiptaskrequest.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"google/protobuf/struct.proto\";\nimport \"google/protobuf/any.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"SkipTaskRequestPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage SkipTaskRequest {\n    map<string, google.protobuf.Value> task_input = 1;\n    map<string, google.protobuf.Value> task_output = 2;\n    google.protobuf.Any task_input_message = 3;\n    google.protobuf.Any task_output_message = 4;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/startworkflowrequest.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/workflowdef.proto\";\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"StartWorkflowRequestPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage StartWorkflowRequest {\n    string name = 1;\n    int32 version = 2;\n    string correlation_id = 3;\n    map<string, google.protobuf.Value> input = 4;\n    map<string, string> task_to_domain = 5;\n    WorkflowDef workflow_def = 6;\n    string external_input_payload_storage_path = 7;\n    int32 priority = 8;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/subworkflowparams.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"SubWorkflowParamsPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage SubWorkflowParams {\n    string name = 1;\n    int32 version = 2;\n    map<string, string> task_to_domain = 3;\n    google.protobuf.Value workflow_definition = 4;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/task.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/workflowtask.proto\";\nimport \"google/protobuf/struct.proto\";\nimport \"google/protobuf/any.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"TaskPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage Task {\n    enum Status {\n        IN_PROGRESS = 0;\n        CANCELED = 1;\n        FAILED = 2;\n        FAILED_WITH_TERMINAL_ERROR = 3;\n        COMPLETED = 4;\n        COMPLETED_WITH_ERRORS = 5;\n        SCHEDULED = 6;\n        TIMED_OUT = 7;\n        SKIPPED = 8;\n    }\n    string task_type = 1;\n    Task.Status status = 2;\n    map<string, google.protobuf.Value> input_data = 3;\n    string reference_task_name = 4;\n    int32 retry_count = 5;\n    int32 seq = 6;\n    string correlation_id = 7;\n    int32 poll_count = 8;\n    string task_def_name = 9;\n    int64 scheduled_time = 10;\n    int64 start_time = 11;\n    int64 end_time = 12;\n    int64 update_time = 13;\n    int32 start_delay_in_seconds = 14;\n    string retried_task_id = 15;\n    bool retried = 16;\n    bool executed = 17;\n    bool callback_from_worker = 18;\n    int64 response_timeout_seconds = 19;\n    string workflow_instance_id = 20;\n    string workflow_type = 21;\n    string task_id = 22;\n    string reason_for_incompletion = 23;\n    int64 callback_after_seconds = 24;\n    string worker_id = 25;\n    map<string, google.protobuf.Value> output_data = 26;\n    WorkflowTask workflow_task = 27;\n    string domain = 28;\n    google.protobuf.Any input_message = 29;\n    google.protobuf.Any output_message = 30;\n    int32 rate_limit_per_frequency = 32;\n    int32 rate_limit_frequency_in_seconds = 33;\n    string external_input_payload_storage_path = 34;\n    string external_output_payload_storage_path = 35;\n    int32 workflow_priority = 36;\n    string execution_name_space = 37;\n    string isolation_group_id = 38;\n    int32 iteration = 40;\n    string sub_workflow_id = 41;\n    bool subworkflow_changed = 42;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/taskdef.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"TaskDefPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage TaskDef {\n    enum TimeoutPolicy {\n        RETRY = 0;\n        TIME_OUT_WF = 1;\n        ALERT_ONLY = 2;\n    }\n    enum RetryLogic {\n        FIXED = 0;\n        EXPONENTIAL_BACKOFF = 1;\n        LINEAR_BACKOFF = 2;\n    }\n    string name = 1;\n    string description = 2;\n    int32 retry_count = 3;\n    int64 timeout_seconds = 4;\n    repeated string input_keys = 5;\n    repeated string output_keys = 6;\n    TaskDef.TimeoutPolicy timeout_policy = 7;\n    TaskDef.RetryLogic retry_logic = 8;\n    int32 retry_delay_seconds = 9;\n    int64 response_timeout_seconds = 10;\n    int32 concurrent_exec_limit = 11;\n    map<string, google.protobuf.Value> input_template = 12;\n    int32 rate_limit_per_frequency = 14;\n    int32 rate_limit_frequency_in_seconds = 15;\n    string isolation_group_id = 16;\n    string execution_name_space = 17;\n    string owner_email = 18;\n    int32 poll_timeout_seconds = 19;\n    int32 backoff_scale_factor = 20;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/taskexeclog.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"TaskExecLogPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage TaskExecLog {\n    string log = 1;\n    string task_id = 2;\n    int64 created_time = 3;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/taskresult.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"google/protobuf/struct.proto\";\nimport \"google/protobuf/any.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"TaskResultPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage TaskResult {\n    enum Status {\n        IN_PROGRESS = 0;\n        FAILED = 1;\n        FAILED_WITH_TERMINAL_ERROR = 2;\n        COMPLETED = 3;\n    }\n    string workflow_instance_id = 1;\n    string task_id = 2;\n    string reason_for_incompletion = 3;\n    int64 callback_after_seconds = 4;\n    string worker_id = 5;\n    TaskResult.Status status = 6;\n    map<string, google.protobuf.Value> output_data = 7;\n    google.protobuf.Any output_message = 8;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/tasksummary.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/task.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"TaskSummaryPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage TaskSummary {\n    string workflow_id = 1;\n    string workflow_type = 2;\n    string correlation_id = 3;\n    string scheduled_time = 4;\n    string start_time = 5;\n    string update_time = 6;\n    string end_time = 7;\n    Task.Status status = 8;\n    string reason_for_incompletion = 9;\n    int64 execution_time = 10;\n    int64 queue_wait_time = 11;\n    string task_def_name = 12;\n    string task_type = 13;\n    string input = 14;\n    string output = 15;\n    string task_id = 16;\n    string external_input_payload_storage_path = 17;\n    string external_output_payload_storage_path = 18;\n    int32 workflow_priority = 19;\n    string domain = 20;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/workflow.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/workflowdef.proto\";\nimport \"model/task.proto\";\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"WorkflowPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage Workflow {\n    enum WorkflowStatus {\n        RUNNING = 0;\n        COMPLETED = 1;\n        FAILED = 2;\n        TIMED_OUT = 3;\n        TERMINATED = 4;\n        PAUSED = 5;\n    }\n    Workflow.WorkflowStatus status = 1;\n    int64 end_time = 2;\n    string workflow_id = 3;\n    string parent_workflow_id = 4;\n    string parent_workflow_task_id = 5;\n    repeated Task tasks = 6;\n    map<string, google.protobuf.Value> input = 8;\n    map<string, google.protobuf.Value> output = 9;\n    string correlation_id = 12;\n    string re_run_from_workflow_id = 13;\n    string reason_for_incompletion = 14;\n    string event = 16;\n    map<string, string> task_to_domain = 17;\n    repeated string failed_reference_task_names = 18;\n    WorkflowDef workflow_definition = 19;\n    string external_input_payload_storage_path = 20;\n    string external_output_payload_storage_path = 21;\n    int32 priority = 22;\n    map<string, google.protobuf.Value> variables = 23;\n    int64 last_retried_time = 24;\n    repeated string failed_task_names = 25;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/workflowdef.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/workflowtask.proto\";\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"WorkflowDefPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage WorkflowDef {\n    enum TimeoutPolicy {\n        TIME_OUT_WF = 0;\n        ALERT_ONLY = 1;\n    }\n    string name = 1;\n    string description = 2;\n    int32 version = 3;\n    repeated WorkflowTask tasks = 4;\n    repeated string input_parameters = 5;\n    map<string, google.protobuf.Value> output_parameters = 6;\n    string failure_workflow = 7;\n    int32 schema_version = 8;\n    bool restartable = 9;\n    bool workflow_status_listener_enabled = 10;\n    string owner_email = 11;\n    WorkflowDef.TimeoutPolicy timeout_policy = 12;\n    int64 timeout_seconds = 13;\n    map<string, google.protobuf.Value> variables = 14;\n    map<string, google.protobuf.Value> input_template = 15;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/workflowdefsummary.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"WorkflowDefSummaryPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage WorkflowDefSummary {\n    string name = 1;\n    int32 version = 2;\n    int64 create_time = 3;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/workflowsummary.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/workflow.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"WorkflowSummaryPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage WorkflowSummary {\n    string workflow_type = 1;\n    int32 version = 2;\n    string workflow_id = 3;\n    string correlation_id = 4;\n    string start_time = 5;\n    string update_time = 6;\n    string end_time = 7;\n    Workflow.WorkflowStatus status = 8;\n    string input = 9;\n    string output = 10;\n    string reason_for_incompletion = 11;\n    int64 execution_time = 12;\n    string event = 13;\n    string failed_reference_task_names = 14;\n    string external_input_payload_storage_path = 15;\n    string external_output_payload_storage_path = 16;\n    int32 priority = 17;\n    repeated string failed_task_names = 18;\n}\n"
  },
  {
    "path": "grpc/src/main/proto/model/workflowtask.proto",
    "content": "syntax = \"proto3\";\npackage conductor.proto;\n\nimport \"model/taskdef.proto\";\nimport \"model/subworkflowparams.proto\";\nimport \"google/protobuf/struct.proto\";\n\noption java_package = \"com.netflix.conductor.proto\";\noption java_outer_classname = \"WorkflowTaskPb\";\noption go_package = \"github.com/netflix/conductor/client/gogrpc/conductor/model\";\n\nmessage WorkflowTask {\n    message WorkflowTaskList {\n        repeated WorkflowTask tasks = 1;\n    }\n    string name = 1;\n    string task_reference_name = 2;\n    string description = 3;\n    map<string, google.protobuf.Value> input_parameters = 4;\n    string type = 5;\n    string dynamic_task_name_param = 6;\n    string case_value_param = 7;\n    string case_expression = 8;\n    string script_expression = 22;\n    map<string, WorkflowTask.WorkflowTaskList> decision_cases = 9;\n    string dynamic_fork_tasks_param = 10;\n    string dynamic_fork_tasks_input_param_name = 11;\n    repeated WorkflowTask default_case = 12;\n    repeated WorkflowTask.WorkflowTaskList fork_tasks = 13;\n    int32 start_delay = 14;\n    SubWorkflowParams sub_workflow_param = 15;\n    repeated string join_on = 16;\n    string sink = 17;\n    bool optional = 18;\n    TaskDef task_definition = 19;\n    bool rate_limited = 20;\n    repeated string default_exclusive_join_task = 21;\n    bool async_complete = 23;\n    string loop_condition = 24;\n    repeated WorkflowTask loop_over = 25;\n    int32 retry_count = 26;\n    string evaluator_type = 27;\n    string expression = 28;\n}\n"
  },
  {
    "path": "grpc/src/test/java/com/netflix/conductor/grpc/TestProtoMapper.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.proto.WorkflowTaskPb;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNull;\n\npublic class TestProtoMapper {\n  private final ProtoMapper mapper = ProtoMapper.INSTANCE;\n\n  @Test\n  public void workflowTaskToProto() {\n    final WorkflowTask taskWithDefaultRetryCount = new WorkflowTask();\n    final WorkflowTask taskWith1RetryCount = new WorkflowTask();\n    taskWith1RetryCount.setRetryCount(1);\n    final WorkflowTask taskWithNoRetryCount = new WorkflowTask();\n    taskWithNoRetryCount.setRetryCount(0);\n    assertEquals(-1, mapper.toProto(taskWithDefaultRetryCount).getRetryCount());\n    assertEquals(1, mapper.toProto(taskWith1RetryCount).getRetryCount());\n    assertEquals(0, mapper.toProto(taskWithNoRetryCount).getRetryCount());\n  }\n\n  @Test\n  public void workflowTaskFromProto() {\n    final WorkflowTaskPb.WorkflowTask taskWithDefaultRetryCount = WorkflowTaskPb.WorkflowTask.newBuilder().build();\n    final WorkflowTaskPb.WorkflowTask taskWith1RetryCount = WorkflowTaskPb.WorkflowTask.newBuilder().setRetryCount(1).build();\n    final WorkflowTaskPb.WorkflowTask taskWithNoRetryCount = WorkflowTaskPb.WorkflowTask.newBuilder().setRetryCount(-1).build();\n    assertEquals(new Integer(0), mapper.fromProto(taskWithDefaultRetryCount).getRetryCount());\n    assertEquals(1, mapper.fromProto(taskWith1RetryCount).getRetryCount().intValue());\n    assertNull(mapper.fromProto(taskWithNoRetryCount).getRetryCount());\n  }\n}\n"
  },
  {
    "path": "grpc-client/build.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\ndependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-grpc')\n\n    implementation \"io.grpc:grpc-netty:${revGrpc}\"\n    implementation \"io.grpc:grpc-protobuf:${revGrpc}\"\n    implementation \"io.grpc:grpc-stub:${revGrpc}\"\n    implementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n    implementation \"org.slf4j:slf4j-api\"\n    implementation \"org.apache.commons:commons-lang3\"\n    implementation \"com.google.guava:guava:${revGuava}\"\n}\n"
  },
  {
    "path": "grpc-client/src/main/java/com/netflix/conductor/client/grpc/ClientBase.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport java.util.concurrent.TimeUnit;\n\nimport javax.annotation.Nullable;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.grpc.SearchPb;\n\nimport io.grpc.ManagedChannel;\nimport io.grpc.ManagedChannelBuilder;\n\nabstract class ClientBase {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ClientBase.class);\n    protected static ProtoMapper protoMapper = ProtoMapper.INSTANCE;\n\n    protected final ManagedChannel channel;\n\n    public ClientBase(String address, int port) {\n        this(ManagedChannelBuilder.forAddress(address, port).usePlaintext());\n    }\n\n    public ClientBase(ManagedChannelBuilder<?> builder) {\n        channel = builder.build();\n    }\n\n    public void shutdown() throws InterruptedException {\n        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);\n    }\n\n    SearchPb.Request createSearchRequest(\n            @Nullable Integer start,\n            @Nullable Integer size,\n            @Nullable String sort,\n            @Nullable String freeText,\n            @Nullable String query) {\n        SearchPb.Request.Builder request = SearchPb.Request.newBuilder();\n        if (start != null) request.setStart(start);\n        if (size != null) request.setSize(size);\n        if (sort != null) request.setSort(sort);\n        if (freeText != null) request.setFreeText(freeText);\n        if (query != null) request.setQuery(query);\n        return request.build();\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/main/java/com/netflix/conductor/client/grpc/EventClient.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport java.util.Iterator;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.grpc.EventServiceGrpc;\nimport com.netflix.conductor.grpc.EventServicePb;\nimport com.netflix.conductor.proto.EventHandlerPb;\n\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.Iterators;\nimport io.grpc.ManagedChannelBuilder;\n\npublic class EventClient extends ClientBase {\n\n    private final EventServiceGrpc.EventServiceBlockingStub stub;\n\n    public EventClient(String address, int port) {\n        super(address, port);\n        this.stub = EventServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    public EventClient(ManagedChannelBuilder<?> builder) {\n        super(builder);\n        this.stub = EventServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    /**\n     * Register an event handler with the server\n     *\n     * @param eventHandler the event handler definition\n     */\n    public void registerEventHandler(EventHandler eventHandler) {\n        Preconditions.checkNotNull(eventHandler, \"Event handler definition cannot be null\");\n        stub.addEventHandler(\n                EventServicePb.AddEventHandlerRequest.newBuilder()\n                        .setHandler(protoMapper.toProto(eventHandler))\n                        .build());\n    }\n\n    /**\n     * Updates an existing event handler\n     *\n     * @param eventHandler the event handler to be updated\n     */\n    public void updateEventHandler(EventHandler eventHandler) {\n        Preconditions.checkNotNull(eventHandler, \"Event handler definition cannot be null\");\n        stub.updateEventHandler(\n                EventServicePb.UpdateEventHandlerRequest.newBuilder()\n                        .setHandler(protoMapper.toProto(eventHandler))\n                        .build());\n    }\n\n    /**\n     * @param event name of the event\n     * @param activeOnly if true, returns only the active handlers\n     * @return Returns the list of all the event handlers for a given event\n     */\n    public Iterator<EventHandler> getEventHandlers(String event, boolean activeOnly) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(event), \"Event cannot be blank\");\n\n        EventServicePb.GetEventHandlersForEventRequest.Builder request =\n                EventServicePb.GetEventHandlersForEventRequest.newBuilder()\n                        .setEvent(event)\n                        .setActiveOnly(activeOnly);\n        Iterator<EventHandlerPb.EventHandler> it = stub.getEventHandlersForEvent(request.build());\n        return Iterators.transform(it, protoMapper::fromProto);\n    }\n\n    /**\n     * Removes the event handler from the conductor server\n     *\n     * @param name the name of the event handler\n     */\n    public void unregisterEventHandler(String name) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(name), \"Name cannot be blank\");\n        stub.removeEventHandler(\n                EventServicePb.RemoveEventHandlerRequest.newBuilder().setName(name).build());\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/main/java/com/netflix/conductor/client/grpc/MetadataClient.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport java.util.List;\n\nimport javax.annotation.Nullable;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.grpc.MetadataServiceGrpc;\nimport com.netflix.conductor.grpc.MetadataServicePb;\n\nimport com.google.common.base.Preconditions;\nimport io.grpc.ManagedChannelBuilder;\n\npublic class MetadataClient extends ClientBase {\n\n    private final MetadataServiceGrpc.MetadataServiceBlockingStub stub;\n\n    public MetadataClient(String address, int port) {\n        super(address, port);\n        this.stub = MetadataServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    public MetadataClient(ManagedChannelBuilder<?> builder) {\n        super(builder);\n        this.stub = MetadataServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    /**\n     * Register a workflow definition with the server\n     *\n     * @param workflowDef the workflow definition\n     */\n    public void registerWorkflowDef(WorkflowDef workflowDef) {\n        Preconditions.checkNotNull(workflowDef, \"Worfklow definition cannot be null\");\n        stub.createWorkflow(\n                MetadataServicePb.CreateWorkflowRequest.newBuilder()\n                        .setWorkflow(protoMapper.toProto(workflowDef))\n                        .build());\n    }\n\n    /**\n     * Updates a list of existing workflow definitions\n     *\n     * @param workflowDefs List of workflow definitions to be updated\n     */\n    public void updateWorkflowDefs(List<WorkflowDef> workflowDefs) {\n        Preconditions.checkNotNull(workflowDefs, \"Workflow defs list cannot be null\");\n        stub.updateWorkflows(\n                MetadataServicePb.UpdateWorkflowsRequest.newBuilder()\n                        .addAllDefs(workflowDefs.stream().map(protoMapper::toProto)::iterator)\n                        .build());\n    }\n\n    /**\n     * Retrieve the workflow definition\n     *\n     * @param name the name of the workflow\n     * @param version the version of the workflow def\n     * @return Workflow definition for the given workflow and version\n     */\n    public WorkflowDef getWorkflowDef(String name, @Nullable Integer version) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(name), \"name cannot be blank\");\n\n        MetadataServicePb.GetWorkflowRequest.Builder request =\n                MetadataServicePb.GetWorkflowRequest.newBuilder().setName(name);\n\n        if (version != null) {\n            request.setVersion(version);\n        }\n\n        return protoMapper.fromProto(stub.getWorkflow(request.build()).getWorkflow());\n    }\n\n    /**\n     * Registers a list of task types with the conductor server\n     *\n     * @param taskDefs List of task types to be registered.\n     */\n    public void registerTaskDefs(List<TaskDef> taskDefs) {\n        Preconditions.checkNotNull(taskDefs, \"Task defs list cannot be null\");\n        stub.createTasks(\n                MetadataServicePb.CreateTasksRequest.newBuilder()\n                        .addAllDefs(taskDefs.stream().map(protoMapper::toProto)::iterator)\n                        .build());\n    }\n\n    /**\n     * Updates an existing task definition\n     *\n     * @param taskDef the task definition to be updated\n     */\n    public void updateTaskDef(TaskDef taskDef) {\n        Preconditions.checkNotNull(taskDef, \"Task definition cannot be null\");\n        stub.updateTask(\n                MetadataServicePb.UpdateTaskRequest.newBuilder()\n                        .setTask(protoMapper.toProto(taskDef))\n                        .build());\n    }\n\n    /**\n     * Retrieve the task definition of a given task type\n     *\n     * @param taskType type of task for which to retrieve the definition\n     * @return Task Definition for the given task type\n     */\n    public TaskDef getTaskDef(String taskType) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskType), \"Task type cannot be blank\");\n        return protoMapper.fromProto(\n                stub.getTask(\n                                MetadataServicePb.GetTaskRequest.newBuilder()\n                                        .setTaskType(taskType)\n                                        .build())\n                        .getTask());\n    }\n\n    /**\n     * Removes the task definition of a task type from the conductor server. Use with caution.\n     *\n     * @param taskType Task type to be unregistered.\n     */\n    public void unregisterTaskDef(String taskType) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskType), \"Task type cannot be blank\");\n        stub.deleteTask(\n                MetadataServicePb.DeleteTaskRequest.newBuilder().setTaskType(taskType).build());\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/main/java/com/netflix/conductor/client/grpc/TaskClient.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport javax.annotation.Nullable;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.TaskServiceGrpc;\nimport com.netflix.conductor.grpc.TaskServicePb;\nimport com.netflix.conductor.proto.TaskPb;\n\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.Iterators;\nimport com.google.common.collect.Lists;\nimport io.grpc.ManagedChannelBuilder;\n\npublic class TaskClient extends ClientBase {\n\n    private final TaskServiceGrpc.TaskServiceBlockingStub stub;\n\n    public TaskClient(String address, int port) {\n        super(address, port);\n        this.stub = TaskServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    public TaskClient(ManagedChannelBuilder<?> builder) {\n        super(builder);\n        this.stub = TaskServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    /**\n     * Perform a poll for a task of a specific task type.\n     *\n     * @param taskType The taskType to poll for\n     * @param domain The domain of the task type\n     * @param workerId Name of the client worker. Used for logging.\n     * @return Task waiting to be executed.\n     */\n    public Task pollTask(String taskType, String workerId, String domain) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskType), \"Task type cannot be blank\");\n        Preconditions.checkArgument(StringUtils.isNotBlank(domain), \"Domain cannot be blank\");\n        Preconditions.checkArgument(StringUtils.isNotBlank(workerId), \"Worker id cannot be blank\");\n\n        TaskServicePb.PollResponse response =\n                stub.poll(\n                        TaskServicePb.PollRequest.newBuilder()\n                                .setTaskType(taskType)\n                                .setWorkerId(workerId)\n                                .setDomain(domain)\n                                .build());\n        return protoMapper.fromProto(response.getTask());\n    }\n\n    /**\n     * Perform a batch poll for tasks by task type. Batch size is configurable by count.\n     *\n     * @param taskType Type of task to poll for\n     * @param workerId Name of the client worker. Used for logging.\n     * @param count Maximum number of tasks to be returned. Actual number of tasks returned can be\n     *     less than this number.\n     * @param timeoutInMillisecond Long poll wait timeout.\n     * @return List of tasks awaiting to be executed.\n     */\n    public List<Task> batchPollTasksByTaskType(\n            String taskType, String workerId, int count, int timeoutInMillisecond) {\n        return Lists.newArrayList(\n                batchPollTasksByTaskTypeAsync(taskType, workerId, count, timeoutInMillisecond));\n    }\n\n    /**\n     * Perform a batch poll for tasks by task type. Batch size is configurable by count. Returns an\n     * iterator that streams tasks as they become available through GRPC.\n     *\n     * @param taskType Type of task to poll for\n     * @param workerId Name of the client worker. Used for logging.\n     * @param count Maximum number of tasks to be returned. Actual number of tasks returned can be\n     *     less than this number.\n     * @param timeoutInMillisecond Long poll wait timeout.\n     * @return Iterator of tasks awaiting to be executed.\n     */\n    public Iterator<Task> batchPollTasksByTaskTypeAsync(\n            String taskType, String workerId, int count, int timeoutInMillisecond) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskType), \"Task type cannot be blank\");\n        Preconditions.checkArgument(StringUtils.isNotBlank(workerId), \"Worker id cannot be blank\");\n        Preconditions.checkArgument(count > 0, \"Count must be greater than 0\");\n\n        Iterator<TaskPb.Task> it =\n                stub.batchPoll(\n                        TaskServicePb.BatchPollRequest.newBuilder()\n                                .setTaskType(taskType)\n                                .setWorkerId(workerId)\n                                .setCount(count)\n                                .setTimeout(timeoutInMillisecond)\n                                .build());\n\n        return Iterators.transform(it, protoMapper::fromProto);\n    }\n\n    /**\n     * Updates the result of a task execution.\n     *\n     * @param taskResult TaskResults to be updated.\n     */\n    public void updateTask(TaskResult taskResult) {\n        Preconditions.checkNotNull(taskResult, \"Task result cannot be null\");\n        stub.updateTask(\n                TaskServicePb.UpdateTaskRequest.newBuilder()\n                        .setResult(protoMapper.toProto(taskResult))\n                        .build());\n    }\n\n    /**\n     * Log execution messages for a task.\n     *\n     * @param taskId id of the task\n     * @param logMessage the message to be logged\n     */\n    public void logMessageForTask(String taskId, String logMessage) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskId), \"Task id cannot be blank\");\n        stub.addLog(\n                TaskServicePb.AddLogRequest.newBuilder()\n                        .setTaskId(taskId)\n                        .setLog(logMessage)\n                        .build());\n    }\n\n    /**\n     * Fetch execution logs for a task.\n     *\n     * @param taskId id of the task.\n     */\n    public List<TaskExecLog> getTaskLogs(String taskId) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskId), \"Task id cannot be blank\");\n        return stub\n                .getTaskLogs(\n                        TaskServicePb.GetTaskLogsRequest.newBuilder().setTaskId(taskId).build())\n                .getLogsList()\n                .stream()\n                .map(protoMapper::fromProto)\n                .collect(Collectors.toList());\n    }\n\n    /**\n     * Retrieve information about the task\n     *\n     * @param taskId ID of the task\n     * @return Task details\n     */\n    public Task getTaskDetails(String taskId) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskId), \"Task id cannot be blank\");\n        return protoMapper.fromProto(\n                stub.getTask(TaskServicePb.GetTaskRequest.newBuilder().setTaskId(taskId).build())\n                        .getTask());\n    }\n\n    public int getQueueSizeForTask(String taskType) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(taskType), \"Task type cannot be blank\");\n\n        TaskServicePb.QueueSizesResponse sizes =\n                stub.getQueueSizesForTasks(\n                        TaskServicePb.QueueSizesRequest.newBuilder()\n                                .addTaskTypes(taskType)\n                                .build());\n\n        return sizes.getQueueForTaskOrDefault(taskType, 0);\n    }\n\n    public SearchResult<TaskSummary> search(String query) {\n        return search(null, null, null, null, query);\n    }\n\n    public SearchResult<Task> searchV2(String query) {\n        return searchV2(null, null, null, null, query);\n    }\n\n    public SearchResult<TaskSummary> search(\n            @Nullable Integer start,\n            @Nullable Integer size,\n            @Nullable String sort,\n            @Nullable String freeText,\n            @Nullable String query) {\n        SearchPb.Request searchRequest = createSearchRequest(start, size, sort, freeText, query);\n        TaskServicePb.TaskSummarySearchResult result = stub.search(searchRequest);\n        return new SearchResult<>(\n                result.getTotalHits(),\n                result.getResultsList().stream()\n                        .map(protoMapper::fromProto)\n                        .collect(Collectors.toList()));\n    }\n\n    public SearchResult<Task> searchV2(\n            @Nullable Integer start,\n            @Nullable Integer size,\n            @Nullable String sort,\n            @Nullable String freeText,\n            @Nullable String query) {\n        SearchPb.Request searchRequest = createSearchRequest(start, size, sort, freeText, query);\n        TaskServicePb.TaskSearchResult result = stub.searchV2(searchRequest);\n        return new SearchResult<>(\n                result.getTotalHits(),\n                result.getResultsList().stream()\n                        .map(protoMapper::fromProto)\n                        .collect(Collectors.toList()));\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/main/java/com/netflix/conductor/client/grpc/WorkflowClient.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport javax.annotation.Nullable;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.WorkflowServiceGrpc;\nimport com.netflix.conductor.grpc.WorkflowServicePb;\nimport com.netflix.conductor.proto.WorkflowPb;\n\nimport com.google.common.base.Preconditions;\nimport io.grpc.ManagedChannelBuilder;\n\npublic class WorkflowClient extends ClientBase {\n\n    private final WorkflowServiceGrpc.WorkflowServiceBlockingStub stub;\n\n    public WorkflowClient(String address, int port) {\n        super(address, port);\n        this.stub = WorkflowServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    public WorkflowClient(ManagedChannelBuilder<?> builder) {\n        super(builder);\n        this.stub = WorkflowServiceGrpc.newBlockingStub(this.channel);\n    }\n\n    /**\n     * Starts a workflow\n     *\n     * @param startWorkflowRequest the {@link StartWorkflowRequest} object to start the workflow\n     * @return the id of the workflow instance that can be used for tracking\n     */\n    public String startWorkflow(StartWorkflowRequest startWorkflowRequest) {\n        Preconditions.checkNotNull(startWorkflowRequest, \"StartWorkflowRequest cannot be null\");\n        return stub.startWorkflow(protoMapper.toProto(startWorkflowRequest)).getWorkflowId();\n    }\n\n    /**\n     * Retrieve a workflow by workflow id\n     *\n     * @param workflowId the id of the workflow\n     * @param includeTasks specify if the tasks in the workflow need to be returned\n     * @return the requested workflow\n     */\n    public Workflow getWorkflow(String workflowId, boolean includeTasks) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        WorkflowPb.Workflow workflow =\n                stub.getWorkflowStatus(\n                        WorkflowServicePb.GetWorkflowStatusRequest.newBuilder()\n                                .setWorkflowId(workflowId)\n                                .setIncludeTasks(includeTasks)\n                                .build());\n        return protoMapper.fromProto(workflow);\n    }\n\n    /**\n     * Retrieve all workflows for a given correlation id and name\n     *\n     * @param name the name of the workflow\n     * @param correlationId the correlation id\n     * @param includeClosed specify if all workflows are to be returned or only running workflows\n     * @param includeTasks specify if the tasks in the workflow need to be returned\n     * @return list of workflows for the given correlation id and name\n     */\n    public List<Workflow> getWorkflows(\n            String name, String correlationId, boolean includeClosed, boolean includeTasks) {\n        Preconditions.checkArgument(StringUtils.isNotBlank(name), \"name cannot be blank\");\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(correlationId), \"correlationId cannot be blank\");\n\n        WorkflowServicePb.GetWorkflowsResponse workflows =\n                stub.getWorkflows(\n                        WorkflowServicePb.GetWorkflowsRequest.newBuilder()\n                                .setName(name)\n                                .addCorrelationId(correlationId)\n                                .setIncludeClosed(includeClosed)\n                                .setIncludeTasks(includeTasks)\n                                .build());\n\n        if (!workflows.containsWorkflowsById(correlationId)) {\n            return Collections.emptyList();\n        }\n\n        return workflows.getWorkflowsByIdOrThrow(correlationId).getWorkflowsList().stream()\n                .map(protoMapper::fromProto)\n                .collect(Collectors.toList());\n    }\n\n    /**\n     * Removes a workflow from the system\n     *\n     * @param workflowId the id of the workflow to be deleted\n     * @param archiveWorkflow flag to indicate if the workflow should be archived before deletion\n     */\n    public void deleteWorkflow(String workflowId, boolean archiveWorkflow) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"Workflow id cannot be blank\");\n        stub.removeWorkflow(\n                WorkflowServicePb.RemoveWorkflowRequest.newBuilder()\n                        .setWorkflodId(workflowId)\n                        .setArchiveWorkflow(archiveWorkflow)\n                        .build());\n    }\n\n    /*\n     * Retrieve all running workflow instances for a given name and version\n     *\n     * @param workflowName the name of the workflow\n     * @param version      the version of the wokflow definition. Defaults to 1.\n     * @return the list of running workflow instances\n     */\n    public List<String> getRunningWorkflow(String workflowName, @Nullable Integer version) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowName), \"Workflow name cannot be blank\");\n\n        WorkflowServicePb.GetRunningWorkflowsResponse workflows =\n                stub.getRunningWorkflows(\n                        WorkflowServicePb.GetRunningWorkflowsRequest.newBuilder()\n                                .setName(workflowName)\n                                .setVersion(version == null ? 1 : version)\n                                .build());\n        return workflows.getWorkflowIdsList();\n    }\n\n    /**\n     * Retrieve all workflow instances for a given workflow name between a specific time period\n     *\n     * @param workflowName the name of the workflow\n     * @param version the version of the workflow definition. Defaults to 1.\n     * @param startTime the start time of the period\n     * @param endTime the end time of the period\n     * @return returns a list of workflows created during the specified during the time period\n     */\n    public List<String> getWorkflowsByTimePeriod(\n            String workflowName, int version, Long startTime, Long endTime) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowName), \"Workflow name cannot be blank\");\n        Preconditions.checkNotNull(startTime, \"Start time cannot be null\");\n        Preconditions.checkNotNull(endTime, \"End time cannot be null\");\n        // TODO\n        return null;\n    }\n\n    /*\n     * Starts the decision task for the given workflow instance\n     *\n     * @param workflowId the id of the workflow instance\n     */\n    public void runDecider(String workflowId) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        stub.decideWorkflow(\n                WorkflowServicePb.DecideWorkflowRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .build());\n    }\n\n    /**\n     * Pause a workflow by workflow id\n     *\n     * @param workflowId the workflow id of the workflow to be paused\n     */\n    public void pauseWorkflow(String workflowId) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        stub.pauseWorkflow(\n                WorkflowServicePb.PauseWorkflowRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .build());\n    }\n\n    /**\n     * Resume a paused workflow by workflow id\n     *\n     * @param workflowId the workflow id of the paused workflow\n     */\n    public void resumeWorkflow(String workflowId) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        stub.resumeWorkflow(\n                WorkflowServicePb.ResumeWorkflowRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .build());\n    }\n\n    /**\n     * Skips a given task from a current RUNNING workflow\n     *\n     * @param workflowId the id of the workflow instance\n     * @param taskReferenceName the reference name of the task to be skipped\n     */\n    public void skipTaskFromWorkflow(String workflowId, String taskReferenceName) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(taskReferenceName), \"Task reference name cannot be blank\");\n        stub.skipTaskFromWorkflow(\n                WorkflowServicePb.SkipTaskRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .setTaskReferenceName(taskReferenceName)\n                        .build());\n    }\n\n    /**\n     * Reruns the workflow from a specific task\n     *\n     * @param rerunWorkflowRequest the request containing the task to rerun from\n     * @return the id of the workflow\n     */\n    public String rerunWorkflow(RerunWorkflowRequest rerunWorkflowRequest) {\n        Preconditions.checkNotNull(rerunWorkflowRequest, \"RerunWorkflowRequest cannot be null\");\n        return stub.rerunWorkflow(protoMapper.toProto(rerunWorkflowRequest)).getWorkflowId();\n    }\n\n    /**\n     * Restart a completed workflow\n     *\n     * @param workflowId the workflow id of the workflow to be restarted\n     */\n    public void restart(String workflowId, boolean useLatestDefinitions) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        stub.restartWorkflow(\n                WorkflowServicePb.RestartWorkflowRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .setUseLatestDefinitions(useLatestDefinitions)\n                        .build());\n    }\n\n    /**\n     * Retries the last failed task in a workflow\n     *\n     * @param workflowId the workflow id of the workflow with the failed task\n     */\n    public void retryLastFailedTask(String workflowId, boolean resumeSubworkflowTasks) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        stub.retryWorkflow(\n                WorkflowServicePb.RetryWorkflowRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .setResumeSubworkflowTasks(resumeSubworkflowTasks)\n                        .build());\n    }\n\n    /**\n     * Resets the callback times of all IN PROGRESS tasks to 0 for the given workflow\n     *\n     * @param workflowId the id of the workflow\n     */\n    public void resetCallbacksForInProgressTasks(String workflowId) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        stub.resetWorkflowCallbacks(\n                WorkflowServicePb.ResetWorkflowCallbacksRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .build());\n    }\n\n    /**\n     * Terminates the execution of the given workflow instance\n     *\n     * @param workflowId the id of the workflow to be terminated\n     * @param reason the reason to be logged and displayed\n     */\n    public void terminateWorkflow(String workflowId, String reason) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(workflowId), \"workflow id cannot be blank\");\n        stub.terminateWorkflow(\n                WorkflowServicePb.TerminateWorkflowRequest.newBuilder()\n                        .setWorkflowId(workflowId)\n                        .setReason(reason)\n                        .build());\n    }\n\n    /**\n     * Search for workflows based on payload\n     *\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link WorkflowSummary} that match the query\n     */\n    public SearchResult<WorkflowSummary> search(String query) {\n        return search(null, null, null, null, query);\n    }\n\n    /**\n     * Search for workflows based on payload\n     *\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link Workflow} that match the query\n     */\n    public SearchResult<Workflow> searchV2(String query) {\n        return searchV2(null, null, null, null, query);\n    }\n\n    /**\n     * Paginated search for workflows based on payload\n     *\n     * @param start start value of page\n     * @param size number of workflows to be returned\n     * @param sort sort order\n     * @param freeText additional free text query\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link WorkflowSummary} that match the query\n     */\n    public SearchResult<WorkflowSummary> search(\n            @Nullable Integer start,\n            @Nullable Integer size,\n            @Nullable String sort,\n            @Nullable String freeText,\n            @Nullable String query) {\n\n        SearchPb.Request searchRequest = createSearchRequest(start, size, sort, freeText, query);\n        WorkflowServicePb.WorkflowSummarySearchResult result = stub.search(searchRequest);\n        return new SearchResult<>(\n                result.getTotalHits(),\n                result.getResultsList().stream()\n                        .map(protoMapper::fromProto)\n                        .collect(Collectors.toList()));\n    }\n\n    /**\n     * Paginated search for workflows based on payload\n     *\n     * @param start start value of page\n     * @param size number of workflows to be returned\n     * @param sort sort order\n     * @param freeText additional free text query\n     * @param query the search query\n     * @return the {@link SearchResult} containing the {@link Workflow} that match the query\n     */\n    public SearchResult<Workflow> searchV2(\n            @Nullable Integer start,\n            @Nullable Integer size,\n            @Nullable String sort,\n            @Nullable String freeText,\n            @Nullable String query) {\n        SearchPb.Request searchRequest = createSearchRequest(start, size, sort, freeText, query);\n        WorkflowServicePb.WorkflowSearchResult result = stub.searchV2(searchRequest);\n        return new SearchResult<>(\n                result.getTotalHits(),\n                result.getResultsList().stream()\n                        .map(protoMapper::fromProto)\n                        .collect(Collectors.toList()));\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/test/java/com/netflix/conductor/client/grpc/EventClientTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.util.ReflectionTestUtils;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.grpc.EventServiceGrpc;\nimport com.netflix.conductor.grpc.EventServicePb;\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.proto.EventHandlerPb;\n\nimport static junit.framework.TestCase.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@RunWith(SpringRunner.class)\npublic class EventClientTest {\n\n    @Mock ProtoMapper mockedProtoMapper;\n\n    @Mock EventServiceGrpc.EventServiceBlockingStub mockedStub;\n\n    EventClient eventClient;\n\n    @Before\n    public void init() {\n        eventClient = new EventClient(\"test\", 0);\n        ReflectionTestUtils.setField(eventClient, \"stub\", mockedStub);\n        ReflectionTestUtils.setField(eventClient, \"protoMapper\", mockedProtoMapper);\n    }\n\n    @Test\n    public void testRegisterEventHandler() {\n        EventHandler eventHandler = mock(EventHandler.class);\n        EventHandlerPb.EventHandler eventHandlerPB = mock(EventHandlerPb.EventHandler.class);\n        when(mockedProtoMapper.toProto(eventHandler)).thenReturn(eventHandlerPB);\n\n        EventServicePb.AddEventHandlerRequest request =\n                EventServicePb.AddEventHandlerRequest.newBuilder()\n                        .setHandler(eventHandlerPB)\n                        .build();\n        eventClient.registerEventHandler(eventHandler);\n        verify(mockedStub, times(1)).addEventHandler(request);\n    }\n\n    @Test\n    public void testUpdateEventHandler() {\n        EventHandler eventHandler = mock(EventHandler.class);\n        EventHandlerPb.EventHandler eventHandlerPB = mock(EventHandlerPb.EventHandler.class);\n        when(mockedProtoMapper.toProto(eventHandler)).thenReturn(eventHandlerPB);\n\n        EventServicePb.UpdateEventHandlerRequest request =\n                EventServicePb.UpdateEventHandlerRequest.newBuilder()\n                        .setHandler(eventHandlerPB)\n                        .build();\n        eventClient.updateEventHandler(eventHandler);\n        verify(mockedStub, times(1)).updateEventHandler(request);\n    }\n\n    @Test\n    public void testGetEventHandlers() {\n        EventHandler eventHandler = mock(EventHandler.class);\n        EventHandlerPb.EventHandler eventHandlerPB = mock(EventHandlerPb.EventHandler.class);\n        when(mockedProtoMapper.fromProto(eventHandlerPB)).thenReturn(eventHandler);\n        EventServicePb.GetEventHandlersForEventRequest request =\n                EventServicePb.GetEventHandlersForEventRequest.newBuilder()\n                        .setEvent(\"test\")\n                        .setActiveOnly(true)\n                        .build();\n        List<EventHandlerPb.EventHandler> result = new ArrayList<>();\n        result.add(eventHandlerPB);\n        when(mockedStub.getEventHandlersForEvent(request)).thenReturn(result.iterator());\n        Iterator<EventHandler> response = eventClient.getEventHandlers(\"test\", true);\n        verify(mockedStub, times(1)).getEventHandlersForEvent(request);\n        assertEquals(response.next(), eventHandler);\n    }\n\n    @Test\n    public void testUnregisterEventHandler() {\n        EventClient eventClient = createClientWithManagedChannel();\n        EventServicePb.RemoveEventHandlerRequest request =\n                EventServicePb.RemoveEventHandlerRequest.newBuilder().setName(\"test\").build();\n        eventClient.unregisterEventHandler(\"test\");\n        verify(mockedStub, times(1)).removeEventHandler(request);\n    }\n\n    @Test\n    public void testUnregisterEventHandlerWithManagedChannel() {\n        EventServicePb.RemoveEventHandlerRequest request =\n                EventServicePb.RemoveEventHandlerRequest.newBuilder().setName(\"test\").build();\n        eventClient.unregisterEventHandler(\"test\");\n        verify(mockedStub, times(1)).removeEventHandler(request);\n    }\n\n    public EventClient createClientWithManagedChannel() {\n        EventClient eventClient = new EventClient(\"test\", 0);\n        ReflectionTestUtils.setField(eventClient, \"stub\", mockedStub);\n        ReflectionTestUtils.setField(eventClient, \"protoMapper\", mockedProtoMapper);\n        return eventClient;\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/test/java/com/netflix/conductor/client/grpc/TaskClientTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.util.ReflectionTestUtils;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.TaskServiceGrpc;\nimport com.netflix.conductor.grpc.TaskServicePb;\nimport com.netflix.conductor.proto.TaskPb;\nimport com.netflix.conductor.proto.TaskSummaryPb;\n\nimport io.grpc.ManagedChannelBuilder;\n\nimport static junit.framework.TestCase.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@RunWith(SpringRunner.class)\npublic class TaskClientTest {\n\n    @Mock ProtoMapper mockedProtoMapper;\n\n    @Mock TaskServiceGrpc.TaskServiceBlockingStub mockedStub;\n\n    TaskClient taskClient;\n\n    @Before\n    public void init() {\n        taskClient = new TaskClient(\"test\", 0);\n        ReflectionTestUtils.setField(taskClient, \"stub\", mockedStub);\n        ReflectionTestUtils.setField(taskClient, \"protoMapper\", mockedProtoMapper);\n    }\n\n    @Test\n    public void testSearch() {\n        TaskSummary taskSummary = mock(TaskSummary.class);\n        TaskSummaryPb.TaskSummary taskSummaryPB = mock(TaskSummaryPb.TaskSummary.class);\n        when(mockedProtoMapper.fromProto(taskSummaryPB)).thenReturn(taskSummary);\n        TaskServicePb.TaskSummarySearchResult result =\n                TaskServicePb.TaskSummarySearchResult.newBuilder()\n                        .addResults(taskSummaryPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder().setQuery(\"test query\").build();\n        when(mockedStub.search(searchRequest)).thenReturn(result);\n        SearchResult<TaskSummary> searchResult = taskClient.search(\"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(taskSummary, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchV2() {\n        Task task = mock(Task.class);\n        TaskPb.Task taskPB = mock(TaskPb.Task.class);\n        when(mockedProtoMapper.fromProto(taskPB)).thenReturn(task);\n        TaskServicePb.TaskSearchResult result =\n                TaskServicePb.TaskSearchResult.newBuilder()\n                        .addResults(taskPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder().setQuery(\"test query\").build();\n        when(mockedStub.searchV2(searchRequest)).thenReturn(result);\n        SearchResult<Task> searchResult = taskClient.searchV2(\"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(task, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchWithParams() {\n        TaskSummary taskSummary = mock(TaskSummary.class);\n        TaskSummaryPb.TaskSummary taskSummaryPB = mock(TaskSummaryPb.TaskSummary.class);\n        when(mockedProtoMapper.fromProto(taskSummaryPB)).thenReturn(taskSummary);\n        TaskServicePb.TaskSummarySearchResult result =\n                TaskServicePb.TaskSummarySearchResult.newBuilder()\n                        .addResults(taskSummaryPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(5)\n                        .setSort(\"*\")\n                        .setFreeText(\"*\")\n                        .setQuery(\"test query\")\n                        .build();\n        when(mockedStub.search(searchRequest)).thenReturn(result);\n        SearchResult<TaskSummary> searchResult = taskClient.search(1, 5, \"*\", \"*\", \"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(taskSummary, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchV2WithParams() {\n        Task task = mock(Task.class);\n        TaskPb.Task taskPB = mock(TaskPb.Task.class);\n        when(mockedProtoMapper.fromProto(taskPB)).thenReturn(task);\n        TaskServicePb.TaskSearchResult result =\n                TaskServicePb.TaskSearchResult.newBuilder()\n                        .addResults(taskPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(5)\n                        .setSort(\"*\")\n                        .setFreeText(\"*\")\n                        .setQuery(\"test query\")\n                        .build();\n        when(mockedStub.searchV2(searchRequest)).thenReturn(result);\n        SearchResult<Task> searchResult = taskClient.searchV2(1, 5, \"*\", \"*\", \"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(task, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchWithChannelBuilder() {\n        TaskClient taskClient = createClientWithManagedChannel();\n        TaskSummary taskSummary = mock(TaskSummary.class);\n        TaskSummaryPb.TaskSummary taskSummaryPB = mock(TaskSummaryPb.TaskSummary.class);\n        when(mockedProtoMapper.fromProto(taskSummaryPB)).thenReturn(taskSummary);\n        TaskServicePb.TaskSummarySearchResult result =\n                TaskServicePb.TaskSummarySearchResult.newBuilder()\n                        .addResults(taskSummaryPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder().setQuery(\"test query\").build();\n        when(mockedStub.search(searchRequest)).thenReturn(result);\n        SearchResult<TaskSummary> searchResult = taskClient.search(\"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(taskSummary, searchResult.getResults().get(0));\n    }\n\n    private TaskClient createClientWithManagedChannel() {\n        TaskClient taskClient = new TaskClient(ManagedChannelBuilder.forAddress(\"test\", 0));\n        ReflectionTestUtils.setField(taskClient, \"stub\", mockedStub);\n        ReflectionTestUtils.setField(taskClient, \"protoMapper\", mockedProtoMapper);\n        return taskClient;\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/test/java/com/netflix/conductor/client/grpc/WorkflowClientTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.client.grpc;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.util.ReflectionTestUtils;\n\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.WorkflowServiceGrpc;\nimport com.netflix.conductor.grpc.WorkflowServicePb;\nimport com.netflix.conductor.proto.WorkflowPb;\nimport com.netflix.conductor.proto.WorkflowSummaryPb;\n\nimport io.grpc.ManagedChannelBuilder;\n\nimport static junit.framework.TestCase.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@RunWith(SpringRunner.class)\npublic class WorkflowClientTest {\n\n    @Mock ProtoMapper mockedProtoMapper;\n\n    @Mock WorkflowServiceGrpc.WorkflowServiceBlockingStub mockedStub;\n\n    WorkflowClient workflowClient;\n\n    @Before\n    public void init() {\n        workflowClient = new WorkflowClient(\"test\", 0);\n        ReflectionTestUtils.setField(workflowClient, \"stub\", mockedStub);\n        ReflectionTestUtils.setField(workflowClient, \"protoMapper\", mockedProtoMapper);\n    }\n\n    @Test\n    public void testSearch() {\n        WorkflowSummary workflow = mock(WorkflowSummary.class);\n        WorkflowSummaryPb.WorkflowSummary workflowPB =\n                mock(WorkflowSummaryPb.WorkflowSummary.class);\n        when(mockedProtoMapper.fromProto(workflowPB)).thenReturn(workflow);\n        WorkflowServicePb.WorkflowSummarySearchResult result =\n                WorkflowServicePb.WorkflowSummarySearchResult.newBuilder()\n                        .addResults(workflowPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder().setQuery(\"test query\").build();\n        when(mockedStub.search(searchRequest)).thenReturn(result);\n        SearchResult<WorkflowSummary> searchResult = workflowClient.search(\"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(workflow, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchV2() {\n        Workflow workflow = mock(Workflow.class);\n        WorkflowPb.Workflow workflowPB = mock(WorkflowPb.Workflow.class);\n        when(mockedProtoMapper.fromProto(workflowPB)).thenReturn(workflow);\n        WorkflowServicePb.WorkflowSearchResult result =\n                WorkflowServicePb.WorkflowSearchResult.newBuilder()\n                        .addResults(workflowPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder().setQuery(\"test query\").build();\n        when(mockedStub.searchV2(searchRequest)).thenReturn(result);\n        SearchResult<Workflow> searchResult = workflowClient.searchV2(\"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(workflow, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchWithParams() {\n        WorkflowSummary workflow = mock(WorkflowSummary.class);\n        WorkflowSummaryPb.WorkflowSummary workflowPB =\n                mock(WorkflowSummaryPb.WorkflowSummary.class);\n        when(mockedProtoMapper.fromProto(workflowPB)).thenReturn(workflow);\n        WorkflowServicePb.WorkflowSummarySearchResult result =\n                WorkflowServicePb.WorkflowSummarySearchResult.newBuilder()\n                        .addResults(workflowPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(5)\n                        .setSort(\"*\")\n                        .setFreeText(\"*\")\n                        .setQuery(\"test query\")\n                        .build();\n        when(mockedStub.search(searchRequest)).thenReturn(result);\n        SearchResult<WorkflowSummary> searchResult =\n                workflowClient.search(1, 5, \"*\", \"*\", \"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(workflow, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchV2WithParams() {\n        Workflow workflow = mock(Workflow.class);\n        WorkflowPb.Workflow workflowPB = mock(WorkflowPb.Workflow.class);\n        when(mockedProtoMapper.fromProto(workflowPB)).thenReturn(workflow);\n        WorkflowServicePb.WorkflowSearchResult result =\n                WorkflowServicePb.WorkflowSearchResult.newBuilder()\n                        .addResults(workflowPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(5)\n                        .setSort(\"*\")\n                        .setFreeText(\"*\")\n                        .setQuery(\"test query\")\n                        .build();\n        when(mockedStub.searchV2(searchRequest)).thenReturn(result);\n        SearchResult<Workflow> searchResult = workflowClient.searchV2(1, 5, \"*\", \"*\", \"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(workflow, searchResult.getResults().get(0));\n    }\n\n    @Test\n    public void testSearchV2WithParamsWithManagedChannel() {\n        WorkflowClient workflowClient = createClientWithManagedChannel();\n        Workflow workflow = mock(Workflow.class);\n        WorkflowPb.Workflow workflowPB = mock(WorkflowPb.Workflow.class);\n        when(mockedProtoMapper.fromProto(workflowPB)).thenReturn(workflow);\n        WorkflowServicePb.WorkflowSearchResult result =\n                WorkflowServicePb.WorkflowSearchResult.newBuilder()\n                        .addResults(workflowPB)\n                        .setTotalHits(1)\n                        .build();\n        SearchPb.Request searchRequest =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(5)\n                        .setSort(\"*\")\n                        .setFreeText(\"*\")\n                        .setQuery(\"test query\")\n                        .build();\n        when(mockedStub.searchV2(searchRequest)).thenReturn(result);\n        SearchResult<Workflow> searchResult = workflowClient.searchV2(1, 5, \"*\", \"*\", \"test query\");\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(workflow, searchResult.getResults().get(0));\n    }\n\n    public WorkflowClient createClientWithManagedChannel() {\n        WorkflowClient workflowClient =\n                new WorkflowClient(ManagedChannelBuilder.forAddress(\"test\", 0));\n        ReflectionTestUtils.setField(workflowClient, \"stub\", mockedStub);\n        ReflectionTestUtils.setField(workflowClient, \"protoMapper\", mockedProtoMapper);\n        return workflowClient;\n    }\n}\n"
  },
  {
    "path": "grpc-client/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-inline\r\n"
  },
  {
    "path": "grpc-server/build.gradle",
    "content": "dependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    implementation project(':conductor-grpc')\n\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n\n    implementation \"io.grpc:grpc-netty:${revGrpc}\"\n    implementation \"io.grpc:grpc-services:${revGrpc}\"\n    implementation \"io.grpc:grpc-protobuf:${revGrpc}\"\n    implementation \"org.apache.commons:commons-lang3\"\n\n    testImplementation \"io.grpc:grpc-testing:${revGrpc}\"\n    testImplementation \"io.grpc:grpc-protobuf:${revGrpc}\"\n    testImplementation \"org.testinfected.hamcrest-matchers:all-matchers:${revHamcrestAllMatchers}\"\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/GRPCServer.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport javax.annotation.PostConstruct;\nimport javax.annotation.PreDestroy;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport io.grpc.BindableService;\nimport io.grpc.Server;\nimport io.grpc.ServerBuilder;\n\npublic class GRPCServer {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(GRPCServer.class);\n\n    private final Server server;\n\n    public GRPCServer(int port, List<BindableService> services) {\n        ServerBuilder<?> builder = ServerBuilder.forPort(port);\n        services.forEach(builder::addService);\n        server = builder.build();\n    }\n\n    @PostConstruct\n    public void start() throws IOException {\n        server.start();\n        LOGGER.info(\"grpc: Server started, listening on \" + server.getPort());\n    }\n\n    @PreDestroy\n    public void stop() {\n        if (server != null) {\n            LOGGER.info(\"grpc: server shutting down\");\n            server.shutdown();\n        }\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/GRPCServerProperties.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@ConfigurationProperties(\"conductor.grpc-server\")\npublic class GRPCServerProperties {\n\n    /** The port at which the gRPC server will serve requests */\n    private int port = 8090;\n\n    /** Enables the reflection service for Protobuf services */\n    private boolean reflectionEnabled = true;\n\n    public int getPort() {\n        return port;\n    }\n\n    public void setPort(int port) {\n        this.port = port;\n    }\n\n    public boolean isReflectionEnabled() {\n        return reflectionEnabled;\n    }\n\n    public void setReflectionEnabled(boolean reflectionEnabled) {\n        this.reflectionEnabled = reflectionEnabled;\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/GrpcConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server;\n\nimport java.util.List;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport io.grpc.BindableService;\nimport io.grpc.protobuf.services.ProtoReflectionService;\n\n@Configuration\n@ConditionalOnProperty(name = \"conductor.grpc-server.enabled\", havingValue = \"true\")\n@EnableConfigurationProperties(GRPCServerProperties.class)\npublic class GrpcConfiguration {\n\n    @Bean\n    public GRPCServer grpcServer(\n            List<BindableService> bindableServices, // all gRPC service implementations\n            GRPCServerProperties grpcServerProperties) {\n        if (grpcServerProperties.isReflectionEnabled()) {\n            bindableServices.add(ProtoReflectionService.newInstance());\n        }\n\n        return new GRPCServer(grpcServerProperties.getPort(), bindableServices);\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/service/EventServiceImpl.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.grpc.EventServiceGrpc;\nimport com.netflix.conductor.grpc.EventServicePb;\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.proto.EventHandlerPb;\nimport com.netflix.conductor.service.MetadataService;\n\nimport io.grpc.stub.StreamObserver;\n\n@Service(\"grpcEventService\")\npublic class EventServiceImpl extends EventServiceGrpc.EventServiceImplBase {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(EventServiceImpl.class);\n\n    private static final ProtoMapper PROTO_MAPPER = ProtoMapper.INSTANCE;\n\n    private final MetadataService metadataService;\n\n    public EventServiceImpl(MetadataService metadataService) {\n        this.metadataService = metadataService;\n    }\n\n    @Override\n    public void addEventHandler(\n            EventServicePb.AddEventHandlerRequest req,\n            StreamObserver<EventServicePb.AddEventHandlerResponse> response) {\n        metadataService.addEventHandler(PROTO_MAPPER.fromProto(req.getHandler()));\n        response.onNext(EventServicePb.AddEventHandlerResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void updateEventHandler(\n            EventServicePb.UpdateEventHandlerRequest req,\n            StreamObserver<EventServicePb.UpdateEventHandlerResponse> response) {\n        metadataService.updateEventHandler(PROTO_MAPPER.fromProto(req.getHandler()));\n        response.onNext(EventServicePb.UpdateEventHandlerResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void removeEventHandler(\n            EventServicePb.RemoveEventHandlerRequest req,\n            StreamObserver<EventServicePb.RemoveEventHandlerResponse> response) {\n        metadataService.removeEventHandlerStatus(req.getName());\n        response.onNext(EventServicePb.RemoveEventHandlerResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getEventHandlers(\n            EventServicePb.GetEventHandlersRequest req,\n            StreamObserver<EventHandlerPb.EventHandler> response) {\n        metadataService.getAllEventHandlers().stream()\n                .map(PROTO_MAPPER::toProto)\n                .forEach(response::onNext);\n        response.onCompleted();\n    }\n\n    @Override\n    public void getEventHandlersForEvent(\n            EventServicePb.GetEventHandlersForEventRequest req,\n            StreamObserver<EventHandlerPb.EventHandler> response) {\n        metadataService.getEventHandlersForEvent(req.getEvent(), req.getActiveOnly()).stream()\n                .map(PROTO_MAPPER::toProto)\n                .forEach(response::onNext);\n        response.onCompleted();\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/service/GRPCHelper.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport java.util.Arrays;\n\nimport javax.annotation.Nonnull;\n\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\n\nimport com.google.rpc.DebugInfo;\nimport io.grpc.Metadata;\nimport io.grpc.Status;\nimport io.grpc.StatusException;\nimport io.grpc.protobuf.lite.ProtoLiteUtils;\nimport io.grpc.stub.StreamObserver;\n\npublic class GRPCHelper {\n\n    private final Logger logger;\n\n    private static final Metadata.Key<DebugInfo> STATUS_DETAILS_KEY =\n            Metadata.Key.of(\n                    \"grpc-status-details-bin\",\n                    ProtoLiteUtils.metadataMarshaller(DebugInfo.getDefaultInstance()));\n\n    public GRPCHelper(Logger log) {\n        this.logger = log;\n    }\n\n    /**\n     * Converts an internal exception thrown by Conductor into an StatusException that uses modern\n     * \"Status\" metadata for GRPC.\n     *\n     * <p>Note that this is trickier than it ought to be because the GRPC APIs have not been\n     * upgraded yet. Here's a quick breakdown of how this works in practice:\n     *\n     * <p>Reporting a \"status\" result back to a client with GRPC is pretty straightforward. GRPC\n     * implementations simply serialize the status into several HTTP/2 trailer headers that are sent\n     * back to the client before shutting down the HTTP/2 stream.\n     *\n     * <p>- 'grpc-status', which is a string representation of a {@link com.google.rpc.Code} -\n     * 'grpc-message', which is the description of the returned status - 'grpc-status-details-bin'\n     * (optional), which is an arbitrary payload with a serialized ProtoBuf object, containing an\n     * accurate description of the error in case the status is not successful.\n     *\n     * <p>By convention, Google provides a default set of ProtoBuf messages for the most common\n     * error cases. Here, we'll be using {@link DebugInfo}, as we're reporting an internal Java\n     * exception which we couldn't properly handle.\n     *\n     * <p>Now, how do we go about sending all those headers _and_ the {@link DebugInfo} payload\n     * using the Java GRPC API?\n     *\n     * <p>The only way we can return an error with the Java API is by passing an instance of {@link\n     * io.grpc.StatusException} or {@link io.grpc.StatusRuntimeException} to {@link\n     * StreamObserver#onError(Throwable)}. The easiest way to create either of these exceptions is\n     * by using the {@link Status} class and one of its predefined code identifiers (in this case,\n     * {@link Status#INTERNAL} because we're reporting an internal exception). The {@link Status}\n     * class has setters to set its most relevant attributes, namely those that will be\n     * automatically serialized into the 'grpc-status' and 'grpc-message' trailers in the response.\n     * There is, however, no setter to pass an arbitrary ProtoBuf message to be serialized into a\n     * `grpc-status-details-bin` trailer. This feature exists in the other language implementations\n     * but it hasn't been brought to Java yet.\n     *\n     * <p>Fortunately, {@link Status#asException(Metadata)} exists, allowing us to pass any amount\n     * of arbitrary trailers before we close the response. So we're using this API to manually craft\n     * the 'grpc-status-detail-bin' trailer, in the same way that the GRPC server implementations\n     * for Go and C++ craft and serialize the header. This will allow us to access the metadata\n     * cleanly from Go and C++ clients by using the 'details' method which _has_ been implemented in\n     * those two clients.\n     *\n     * @param t The exception to convert\n     * @return an instance of {@link StatusException} which will properly serialize all its headers\n     *     into the response.\n     */\n    private StatusException throwableToStatusException(Throwable t) {\n        String[] frames = ExceptionUtils.getStackFrames(t);\n        Metadata metadata = new Metadata();\n        metadata.put(\n                STATUS_DETAILS_KEY,\n                DebugInfo.newBuilder()\n                        .addAllStackEntries(Arrays.asList(frames))\n                        .setDetail(ExceptionUtils.getMessage(t))\n                        .build());\n\n        return Status.INTERNAL.withDescription(t.getMessage()).withCause(t).asException(metadata);\n    }\n\n    void onError(StreamObserver<?> response, Throwable t) {\n        logger.error(\"internal exception during GRPC request\", t);\n        response.onError(throwableToStatusException(t));\n    }\n\n    /**\n     * Convert a non-null String instance to a possibly null String instance based on ProtoBuf's\n     * rules for optional arguments.\n     *\n     * <p>This helper converts an String instance from a ProtoBuf object into a possibly null\n     * String. In ProtoBuf objects, String fields are not nullable, but an empty String field is\n     * considered to be \"missing\".\n     *\n     * <p>The internal Conductor APIs expect missing arguments to be passed as null values, so this\n     * helper performs such conversion.\n     *\n     * @param str a string from a ProtoBuf object\n     * @return the original string, or null\n     */\n    String optional(@Nonnull String str) {\n        return str.isEmpty() ? null : str;\n    }\n\n    /**\n     * Check if a given non-null String instance is \"missing\" according to ProtoBuf's missing field\n     * rules. If the String is missing, the given default value will be returned. Otherwise, the\n     * string itself will be returned.\n     *\n     * @param str the input String\n     * @param defaults the default value for the string\n     * @return 'str' if it is not empty according to ProtoBuf rules; 'defaults' otherwise\n     */\n    String optionalOr(@Nonnull String str, String defaults) {\n        return str.isEmpty() ? defaults : str;\n    }\n\n    /**\n     * Convert a non-null Integer instance to a possibly null Integer instance based on ProtoBuf's\n     * rules for optional arguments.\n     *\n     * <p>This helper converts an Integer instance from a ProtoBuf object into a possibly null\n     * Integer. In ProtoBuf objects, Integer fields are not nullable, but a zero-value Integer field\n     * is considered to be \"missing\".\n     *\n     * <p>The internal Conductor APIs expect missing arguments to be passed as null values, so this\n     * helper performs such conversion.\n     *\n     * @param i an Integer from a ProtoBuf object\n     * @return the original Integer, or null\n     */\n    Integer optional(@Nonnull Integer i) {\n        return i == 0 ? null : i;\n    }\n\n    /**\n     * Check if a given non-null Integer instance is \"missing\" according to ProtoBuf's missing field\n     * rules. If the Integer is missing (i.e. if it has a zero-value), the given default value will\n     * be returned. Otherwise, the Integer itself will be returned.\n     *\n     * @param i the input Integer\n     * @param defaults the default value for the Integer\n     * @return 'i' if it is not a zero-value according to ProtoBuf rules; 'defaults' otherwise\n     */\n    Integer optionalOr(@Nonnull Integer i, int defaults) {\n        return i == 0 ? defaults : i;\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/service/HealthServiceImpl.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport org.springframework.stereotype.Service;\n\nimport io.grpc.health.v1.HealthCheckRequest;\nimport io.grpc.health.v1.HealthCheckResponse;\nimport io.grpc.health.v1.HealthGrpc;\nimport io.grpc.stub.StreamObserver;\n\n@Service(\"grpcHealthService\")\npublic class HealthServiceImpl extends HealthGrpc.HealthImplBase {\n\n    // SBMTODO: Move this Spring boot health check\n    @Override\n    public void check(\n            HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {\n        responseObserver.onNext(\n                HealthCheckResponse.newBuilder()\n                        .setStatus(HealthCheckResponse.ServingStatus.SERVING)\n                        .build());\n        responseObserver.onCompleted();\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/service/MetadataServiceImpl.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.grpc.MetadataServiceGrpc;\nimport com.netflix.conductor.grpc.MetadataServicePb;\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.proto.TaskDefPb;\nimport com.netflix.conductor.proto.WorkflowDefPb;\nimport com.netflix.conductor.service.MetadataService;\n\nimport io.grpc.Status;\nimport io.grpc.stub.StreamObserver;\n\n@Service(\"grpcMetadataService\")\npublic class MetadataServiceImpl extends MetadataServiceGrpc.MetadataServiceImplBase {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(MetadataServiceImpl.class);\n    private static final ProtoMapper PROTO_MAPPER = ProtoMapper.INSTANCE;\n    private static final GRPCHelper GRPC_HELPER = new GRPCHelper(LOGGER);\n\n    private final MetadataService service;\n\n    public MetadataServiceImpl(MetadataService service) {\n        this.service = service;\n    }\n\n    @Override\n    public void createWorkflow(\n            MetadataServicePb.CreateWorkflowRequest req,\n            StreamObserver<MetadataServicePb.CreateWorkflowResponse> response) {\n        WorkflowDef workflow = PROTO_MAPPER.fromProto(req.getWorkflow());\n        service.registerWorkflowDef(workflow);\n        response.onNext(MetadataServicePb.CreateWorkflowResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void validateWorkflow(\n            MetadataServicePb.ValidateWorkflowRequest req,\n            StreamObserver<MetadataServicePb.ValidateWorkflowResponse> response) {\n        WorkflowDef workflow = PROTO_MAPPER.fromProto(req.getWorkflow());\n        service.validateWorkflowDef(workflow);\n        response.onNext(MetadataServicePb.ValidateWorkflowResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void updateWorkflows(\n            MetadataServicePb.UpdateWorkflowsRequest req,\n            StreamObserver<MetadataServicePb.UpdateWorkflowsResponse> response) {\n        List<WorkflowDef> workflows =\n                req.getDefsList().stream()\n                        .map(PROTO_MAPPER::fromProto)\n                        .collect(Collectors.toList());\n\n        service.updateWorkflowDef(workflows);\n        response.onNext(MetadataServicePb.UpdateWorkflowsResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getWorkflow(\n            MetadataServicePb.GetWorkflowRequest req,\n            StreamObserver<MetadataServicePb.GetWorkflowResponse> response) {\n        try {\n            WorkflowDef workflowDef =\n                    service.getWorkflowDef(req.getName(), GRPC_HELPER.optional(req.getVersion()));\n            WorkflowDefPb.WorkflowDef workflow = PROTO_MAPPER.toProto(workflowDef);\n            response.onNext(\n                    MetadataServicePb.GetWorkflowResponse.newBuilder()\n                            .setWorkflow(workflow)\n                            .build());\n            response.onCompleted();\n        } catch (NotFoundException e) {\n            // TODO replace this with gRPC exception interceptor.\n            response.onError(\n                    Status.NOT_FOUND\n                            .withDescription(\"No such workflow found by name=\" + req.getName())\n                            .asRuntimeException());\n        }\n    }\n\n    @Override\n    public void createTasks(\n            MetadataServicePb.CreateTasksRequest req,\n            StreamObserver<MetadataServicePb.CreateTasksResponse> response) {\n        service.registerTaskDef(\n                req.getDefsList().stream()\n                        .map(PROTO_MAPPER::fromProto)\n                        .collect(Collectors.toList()));\n        response.onNext(MetadataServicePb.CreateTasksResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void updateTask(\n            MetadataServicePb.UpdateTaskRequest req,\n            StreamObserver<MetadataServicePb.UpdateTaskResponse> response) {\n        TaskDef task = PROTO_MAPPER.fromProto(req.getTask());\n        service.updateTaskDef(task);\n        response.onNext(MetadataServicePb.UpdateTaskResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getTask(\n            MetadataServicePb.GetTaskRequest req,\n            StreamObserver<MetadataServicePb.GetTaskResponse> response) {\n        TaskDef def = service.getTaskDef(req.getTaskType());\n        if (def != null) {\n            TaskDefPb.TaskDef task = PROTO_MAPPER.toProto(def);\n            response.onNext(MetadataServicePb.GetTaskResponse.newBuilder().setTask(task).build());\n            response.onCompleted();\n        } else {\n            response.onError(\n                    Status.NOT_FOUND\n                            .withDescription(\n                                    \"No such TaskDef found by taskType=\" + req.getTaskType())\n                            .asRuntimeException());\n        }\n    }\n\n    @Override\n    public void deleteTask(\n            MetadataServicePb.DeleteTaskRequest req,\n            StreamObserver<MetadataServicePb.DeleteTaskResponse> response) {\n        service.unregisterTaskDef(req.getTaskType());\n        response.onNext(MetadataServicePb.DeleteTaskResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/service/TaskServiceImpl.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.TaskServiceGrpc;\nimport com.netflix.conductor.grpc.TaskServicePb;\nimport com.netflix.conductor.proto.TaskPb;\nimport com.netflix.conductor.service.ExecutionService;\nimport com.netflix.conductor.service.TaskService;\n\nimport io.grpc.Status;\nimport io.grpc.stub.StreamObserver;\n\n@Service(\"grpcTaskService\")\npublic class TaskServiceImpl extends TaskServiceGrpc.TaskServiceImplBase {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskServiceImpl.class);\n    private static final ProtoMapper PROTO_MAPPER = ProtoMapper.INSTANCE;\n    private static final GRPCHelper GRPC_HELPER = new GRPCHelper(LOGGER);\n\n    private static final int POLL_TIMEOUT_MS = 100;\n    private static final int MAX_POLL_TIMEOUT_MS = 5000;\n\n    private final TaskService taskService;\n    private final int maxSearchSize;\n    private final ExecutionService executionService;\n\n    public TaskServiceImpl(\n            ExecutionService executionService,\n            TaskService taskService,\n            @Value(\"${workflow.max.search.size:5000}\") int maxSearchSize) {\n        this.executionService = executionService;\n        this.taskService = taskService;\n        this.maxSearchSize = maxSearchSize;\n    }\n\n    @Override\n    public void poll(\n            TaskServicePb.PollRequest req, StreamObserver<TaskServicePb.PollResponse> response) {\n        try {\n            List<Task> tasks =\n                    executionService.poll(\n                            req.getTaskType(),\n                            req.getWorkerId(),\n                            GRPC_HELPER.optional(req.getDomain()),\n                            1,\n                            POLL_TIMEOUT_MS);\n            if (!tasks.isEmpty()) {\n                TaskPb.Task t = PROTO_MAPPER.toProto(tasks.get(0));\n                response.onNext(TaskServicePb.PollResponse.newBuilder().setTask(t).build());\n            }\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void batchPoll(\n            TaskServicePb.BatchPollRequest req, StreamObserver<TaskPb.Task> response) {\n        final int count = GRPC_HELPER.optionalOr(req.getCount(), 1);\n        final int timeout = GRPC_HELPER.optionalOr(req.getTimeout(), POLL_TIMEOUT_MS);\n\n        if (timeout > MAX_POLL_TIMEOUT_MS) {\n            response.onError(\n                    Status.INVALID_ARGUMENT\n                            .withDescription(\n                                    \"longpoll timeout cannot be longer than \"\n                                            + MAX_POLL_TIMEOUT_MS\n                                            + \"ms\")\n                            .asRuntimeException());\n            return;\n        }\n\n        try {\n            List<Task> polledTasks =\n                    taskService.batchPoll(\n                            req.getTaskType(),\n                            req.getWorkerId(),\n                            GRPC_HELPER.optional(req.getDomain()),\n                            count,\n                            timeout);\n            LOGGER.info(\"polled tasks: \" + polledTasks);\n            polledTasks.stream().map(PROTO_MAPPER::toProto).forEach(response::onNext);\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void updateTask(\n            TaskServicePb.UpdateTaskRequest req,\n            StreamObserver<TaskServicePb.UpdateTaskResponse> response) {\n        try {\n            TaskResult task = PROTO_MAPPER.fromProto(req.getResult());\n            taskService.updateTask(task);\n\n            response.onNext(\n                    TaskServicePb.UpdateTaskResponse.newBuilder()\n                            .setTaskId(task.getTaskId())\n                            .build());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void addLog(\n            TaskServicePb.AddLogRequest req,\n            StreamObserver<TaskServicePb.AddLogResponse> response) {\n        taskService.log(req.getTaskId(), req.getLog());\n        response.onNext(TaskServicePb.AddLogResponse.getDefaultInstance());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getTaskLogs(\n            TaskServicePb.GetTaskLogsRequest req,\n            StreamObserver<TaskServicePb.GetTaskLogsResponse> response) {\n        List<TaskExecLog> logs = taskService.getTaskLogs(req.getTaskId());\n        response.onNext(\n                TaskServicePb.GetTaskLogsResponse.newBuilder()\n                        .addAllLogs(logs.stream().map(PROTO_MAPPER::toProto)::iterator)\n                        .build());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getTask(\n            TaskServicePb.GetTaskRequest req,\n            StreamObserver<TaskServicePb.GetTaskResponse> response) {\n        try {\n            Task task = taskService.getTask(req.getTaskId());\n            if (task == null) {\n                response.onError(\n                        Status.NOT_FOUND\n                                .withDescription(\"No such task found by id=\" + req.getTaskId())\n                                .asRuntimeException());\n            } else {\n                response.onNext(\n                        TaskServicePb.GetTaskResponse.newBuilder()\n                                .setTask(PROTO_MAPPER.toProto(task))\n                                .build());\n                response.onCompleted();\n            }\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void getQueueSizesForTasks(\n            TaskServicePb.QueueSizesRequest req,\n            StreamObserver<TaskServicePb.QueueSizesResponse> response) {\n        Map<String, Integer> sizes = taskService.getTaskQueueSizes(req.getTaskTypesList());\n        response.onNext(\n                TaskServicePb.QueueSizesResponse.newBuilder().putAllQueueForTask(sizes).build());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getQueueInfo(\n            TaskServicePb.QueueInfoRequest req,\n            StreamObserver<TaskServicePb.QueueInfoResponse> response) {\n        Map<String, Long> queueInfo = taskService.getAllQueueDetails();\n\n        response.onNext(\n                TaskServicePb.QueueInfoResponse.newBuilder().putAllQueues(queueInfo).build());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getQueueAllInfo(\n            TaskServicePb.QueueAllInfoRequest req,\n            StreamObserver<TaskServicePb.QueueAllInfoResponse> response) {\n        Map<String, Map<String, Map<String, Long>>> info = taskService.allVerbose();\n        TaskServicePb.QueueAllInfoResponse.Builder queuesBuilder =\n                TaskServicePb.QueueAllInfoResponse.newBuilder();\n\n        for (Map.Entry<String, Map<String, Map<String, Long>>> queue : info.entrySet()) {\n            final String queueName = queue.getKey();\n            final Map<String, Map<String, Long>> queueShards = queue.getValue();\n\n            TaskServicePb.QueueAllInfoResponse.QueueInfo.Builder queueInfoBuilder =\n                    TaskServicePb.QueueAllInfoResponse.QueueInfo.newBuilder();\n\n            for (Map.Entry<String, Map<String, Long>> shard : queueShards.entrySet()) {\n                final String shardName = shard.getKey();\n                final Map<String, Long> shardInfo = shard.getValue();\n\n                // FIXME: make shardInfo an actual type\n                // shardInfo is an immutable map with predefined keys, so we can always\n                // access 'size' and 'uacked'. It would be better if shardInfo\n                // were actually a POJO.\n                queueInfoBuilder.putShards(\n                        shardName,\n                        TaskServicePb.QueueAllInfoResponse.ShardInfo.newBuilder()\n                                .setSize(shardInfo.get(\"size\"))\n                                .setUacked(shardInfo.get(\"uacked\"))\n                                .build());\n            }\n\n            queuesBuilder.putQueues(queueName, queueInfoBuilder.build());\n        }\n\n        response.onNext(queuesBuilder.build());\n        response.onCompleted();\n    }\n\n    @Override\n    public void search(\n            SearchPb.Request req, StreamObserver<TaskServicePb.TaskSummarySearchResult> response) {\n        final int start = req.getStart();\n        final int size = GRPC_HELPER.optionalOr(req.getSize(), maxSearchSize);\n        final String sort = req.getSort();\n        final String freeText = GRPC_HELPER.optionalOr(req.getFreeText(), \"*\");\n        final String query = req.getQuery();\n        if (size > maxSearchSize) {\n            response.onError(\n                    Status.INVALID_ARGUMENT\n                            .withDescription(\n                                    \"Cannot return more than \" + maxSearchSize + \" results\")\n                            .asRuntimeException());\n            return;\n        }\n        SearchResult<TaskSummary> searchResult =\n                taskService.search(start, size, sort, freeText, query);\n        response.onNext(\n                TaskServicePb.TaskSummarySearchResult.newBuilder()\n                        .setTotalHits(searchResult.getTotalHits())\n                        .addAllResults(\n                                searchResult.getResults().stream().map(PROTO_MAPPER::toProto)\n                                        ::iterator)\n                        .build());\n        response.onCompleted();\n    }\n\n    @Override\n    public void searchV2(\n            SearchPb.Request req, StreamObserver<TaskServicePb.TaskSearchResult> response) {\n        final int start = req.getStart();\n        final int size = GRPC_HELPER.optionalOr(req.getSize(), maxSearchSize);\n        final String sort = req.getSort();\n        final String freeText = GRPC_HELPER.optionalOr(req.getFreeText(), \"*\");\n        final String query = req.getQuery();\n\n        if (size > maxSearchSize) {\n            response.onError(\n                    Status.INVALID_ARGUMENT\n                            .withDescription(\n                                    \"Cannot return more than \" + maxSearchSize + \" results\")\n                            .asRuntimeException());\n            return;\n        }\n\n        SearchResult<Task> searchResult = taskService.searchV2(start, size, sort, freeText, query);\n        response.onNext(\n                TaskServicePb.TaskSearchResult.newBuilder()\n                        .setTotalHits(searchResult.getTotalHits())\n                        .addAllResults(\n                                searchResult.getResults().stream().map(PROTO_MAPPER::toProto)\n                                        ::iterator)\n                        .build());\n        response.onCompleted();\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/main/java/com/netflix/conductor/grpc/server/service/WorkflowServiceImpl.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport com.netflix.conductor.common.metadata.workflow.SkipTaskRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.grpc.ProtoMapper;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.WorkflowServiceGrpc;\nimport com.netflix.conductor.grpc.WorkflowServicePb;\nimport com.netflix.conductor.proto.RerunWorkflowRequestPb;\nimport com.netflix.conductor.proto.StartWorkflowRequestPb;\nimport com.netflix.conductor.proto.WorkflowPb;\nimport com.netflix.conductor.service.WorkflowService;\n\nimport io.grpc.Status;\nimport io.grpc.stub.StreamObserver;\n\n@Service(\"grpcWorkflowService\")\npublic class WorkflowServiceImpl extends WorkflowServiceGrpc.WorkflowServiceImplBase {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskServiceImpl.class);\n    private static final ProtoMapper PROTO_MAPPER = ProtoMapper.INSTANCE;\n    private static final GRPCHelper GRPC_HELPER = new GRPCHelper(LOGGER);\n\n    private final WorkflowService workflowService;\n    private final int maxSearchSize;\n\n    public WorkflowServiceImpl(\n            WorkflowService workflowService,\n            @Value(\"${workflow.max.search.size:5000}\") int maxSearchSize) {\n        this.workflowService = workflowService;\n        this.maxSearchSize = maxSearchSize;\n    }\n\n    @Override\n    public void startWorkflow(\n            StartWorkflowRequestPb.StartWorkflowRequest pbRequest,\n            StreamObserver<WorkflowServicePb.StartWorkflowResponse> response) {\n\n        // TODO: better handling of optional 'version'\n        final StartWorkflowRequest request = PROTO_MAPPER.fromProto(pbRequest);\n        try {\n            String id =\n                    workflowService.startWorkflow(\n                            pbRequest.getName(),\n                            GRPC_HELPER.optional(request.getVersion()),\n                            request.getCorrelationId(),\n                            request.getPriority(),\n                            request.getInput(),\n                            request.getExternalInputPayloadStoragePath(),\n                            request.getTaskToDomain(),\n                            request.getWorkflowDef());\n\n            response.onNext(\n                    WorkflowServicePb.StartWorkflowResponse.newBuilder().setWorkflowId(id).build());\n            response.onCompleted();\n        } catch (NotFoundException nfe) {\n            response.onError(\n                    Status.NOT_FOUND\n                            .withDescription(\"No such workflow found by name=\" + request.getName())\n                            .asRuntimeException());\n        } catch (Exception e) {\n\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void getWorkflows(\n            WorkflowServicePb.GetWorkflowsRequest req,\n            StreamObserver<WorkflowServicePb.GetWorkflowsResponse> response) {\n        final String name = req.getName();\n        final boolean includeClosed = req.getIncludeClosed();\n        final boolean includeTasks = req.getIncludeTasks();\n\n        WorkflowServicePb.GetWorkflowsResponse.Builder builder =\n                WorkflowServicePb.GetWorkflowsResponse.newBuilder();\n\n        for (String correlationId : req.getCorrelationIdList()) {\n            List<Workflow> workflows =\n                    workflowService.getWorkflows(name, correlationId, includeClosed, includeTasks);\n            builder.putWorkflowsById(\n                    correlationId,\n                    WorkflowServicePb.GetWorkflowsResponse.Workflows.newBuilder()\n                            .addAllWorkflows(\n                                    workflows.stream().map(PROTO_MAPPER::toProto)::iterator)\n                            .build());\n        }\n\n        response.onNext(builder.build());\n        response.onCompleted();\n    }\n\n    @Override\n    public void getWorkflowStatus(\n            WorkflowServicePb.GetWorkflowStatusRequest req,\n            StreamObserver<WorkflowPb.Workflow> response) {\n        try {\n            Workflow workflow =\n                    workflowService.getExecutionStatus(req.getWorkflowId(), req.getIncludeTasks());\n            response.onNext(PROTO_MAPPER.toProto(workflow));\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void removeWorkflow(\n            WorkflowServicePb.RemoveWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.RemoveWorkflowResponse> response) {\n        try {\n            workflowService.deleteWorkflow(req.getWorkflodId(), req.getArchiveWorkflow());\n            response.onNext(WorkflowServicePb.RemoveWorkflowResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void getRunningWorkflows(\n            WorkflowServicePb.GetRunningWorkflowsRequest req,\n            StreamObserver<WorkflowServicePb.GetRunningWorkflowsResponse> response) {\n        try {\n            List<String> workflowIds =\n                    workflowService.getRunningWorkflows(\n                            req.getName(), req.getVersion(), req.getStartTime(), req.getEndTime());\n\n            response.onNext(\n                    WorkflowServicePb.GetRunningWorkflowsResponse.newBuilder()\n                            .addAllWorkflowIds(workflowIds)\n                            .build());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void decideWorkflow(\n            WorkflowServicePb.DecideWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.DecideWorkflowResponse> response) {\n        try {\n            workflowService.decideWorkflow(req.getWorkflowId());\n            response.onNext(WorkflowServicePb.DecideWorkflowResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void pauseWorkflow(\n            WorkflowServicePb.PauseWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.PauseWorkflowResponse> response) {\n        try {\n            workflowService.pauseWorkflow(req.getWorkflowId());\n            response.onNext(WorkflowServicePb.PauseWorkflowResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void resumeWorkflow(\n            WorkflowServicePb.ResumeWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.ResumeWorkflowResponse> response) {\n        try {\n            workflowService.resumeWorkflow(req.getWorkflowId());\n            response.onNext(WorkflowServicePb.ResumeWorkflowResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void skipTaskFromWorkflow(\n            WorkflowServicePb.SkipTaskRequest req,\n            StreamObserver<WorkflowServicePb.SkipTaskResponse> response) {\n        try {\n            SkipTaskRequest skipTask = PROTO_MAPPER.fromProto(req.getRequest());\n\n            workflowService.skipTaskFromWorkflow(\n                    req.getWorkflowId(), req.getTaskReferenceName(), skipTask);\n            response.onNext(WorkflowServicePb.SkipTaskResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void rerunWorkflow(\n            RerunWorkflowRequestPb.RerunWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.RerunWorkflowResponse> response) {\n        try {\n            String id =\n                    workflowService.rerunWorkflow(\n                            req.getReRunFromWorkflowId(), PROTO_MAPPER.fromProto(req));\n            response.onNext(\n                    WorkflowServicePb.RerunWorkflowResponse.newBuilder().setWorkflowId(id).build());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void restartWorkflow(\n            WorkflowServicePb.RestartWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.RestartWorkflowResponse> response) {\n        try {\n            workflowService.restartWorkflow(req.getWorkflowId(), req.getUseLatestDefinitions());\n            response.onNext(WorkflowServicePb.RestartWorkflowResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void retryWorkflow(\n            WorkflowServicePb.RetryWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.RetryWorkflowResponse> response) {\n        try {\n            workflowService.retryWorkflow(req.getWorkflowId(), req.getResumeSubworkflowTasks());\n            response.onNext(WorkflowServicePb.RetryWorkflowResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void resetWorkflowCallbacks(\n            WorkflowServicePb.ResetWorkflowCallbacksRequest req,\n            StreamObserver<WorkflowServicePb.ResetWorkflowCallbacksResponse> response) {\n        try {\n            workflowService.resetWorkflow(req.getWorkflowId());\n            response.onNext(WorkflowServicePb.ResetWorkflowCallbacksResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    @Override\n    public void terminateWorkflow(\n            WorkflowServicePb.TerminateWorkflowRequest req,\n            StreamObserver<WorkflowServicePb.TerminateWorkflowResponse> response) {\n        try {\n            workflowService.terminateWorkflow(req.getWorkflowId(), req.getReason());\n            response.onNext(WorkflowServicePb.TerminateWorkflowResponse.getDefaultInstance());\n            response.onCompleted();\n        } catch (Exception e) {\n            GRPC_HELPER.onError(response, e);\n        }\n    }\n\n    private void doSearch(\n            boolean searchByTask,\n            SearchPb.Request req,\n            StreamObserver<WorkflowServicePb.WorkflowSummarySearchResult> response) {\n        final int start = req.getStart();\n        final int size = GRPC_HELPER.optionalOr(req.getSize(), maxSearchSize);\n        final List<String> sort = convertSort(req.getSort());\n        final String freeText = GRPC_HELPER.optionalOr(req.getFreeText(), \"*\");\n        final String query = req.getQuery();\n\n        if (size > maxSearchSize) {\n            response.onError(\n                    Status.INVALID_ARGUMENT\n                            .withDescription(\n                                    \"Cannot return more than \" + maxSearchSize + \" results\")\n                            .asRuntimeException());\n            return;\n        }\n\n        SearchResult<WorkflowSummary> search;\n        if (searchByTask) {\n            search = workflowService.searchWorkflowsByTasks(start, size, sort, freeText, query);\n        } else {\n            search = workflowService.searchWorkflows(start, size, sort, freeText, query);\n        }\n\n        response.onNext(\n                WorkflowServicePb.WorkflowSummarySearchResult.newBuilder()\n                        .setTotalHits(search.getTotalHits())\n                        .addAllResults(\n                                search.getResults().stream().map(PROTO_MAPPER::toProto)::iterator)\n                        .build());\n        response.onCompleted();\n    }\n\n    private void doSearchV2(\n            boolean searchByTask,\n            SearchPb.Request req,\n            StreamObserver<WorkflowServicePb.WorkflowSearchResult> response) {\n        final int start = req.getStart();\n        final int size = GRPC_HELPER.optionalOr(req.getSize(), maxSearchSize);\n        final List<String> sort = convertSort(req.getSort());\n        final String freeText = GRPC_HELPER.optionalOr(req.getFreeText(), \"*\");\n        final String query = req.getQuery();\n\n        if (size > maxSearchSize) {\n            response.onError(\n                    Status.INVALID_ARGUMENT\n                            .withDescription(\n                                    \"Cannot return more than \" + maxSearchSize + \" results\")\n                            .asRuntimeException());\n            return;\n        }\n\n        SearchResult<Workflow> search;\n        if (searchByTask) {\n            search = workflowService.searchWorkflowsByTasksV2(start, size, sort, freeText, query);\n        } else {\n            search = workflowService.searchWorkflowsV2(start, size, sort, freeText, query);\n        }\n\n        response.onNext(\n                WorkflowServicePb.WorkflowSearchResult.newBuilder()\n                        .setTotalHits(search.getTotalHits())\n                        .addAllResults(\n                                search.getResults().stream().map(PROTO_MAPPER::toProto)::iterator)\n                        .build());\n        response.onCompleted();\n    }\n\n    private List<String> convertSort(String sortStr) {\n        List<String> list = new ArrayList<>();\n        if (sortStr != null && sortStr.length() != 0) {\n            list = Arrays.asList(sortStr.split(\"\\\\|\"));\n        }\n        return list;\n    }\n\n    @Override\n    public void search(\n            SearchPb.Request request,\n            StreamObserver<WorkflowServicePb.WorkflowSummarySearchResult> responseObserver) {\n        doSearch(false, request, responseObserver);\n    }\n\n    @Override\n    public void searchByTasks(\n            SearchPb.Request request,\n            StreamObserver<WorkflowServicePb.WorkflowSummarySearchResult> responseObserver) {\n        doSearch(true, request, responseObserver);\n    }\n\n    @Override\n    public void searchV2(\n            SearchPb.Request request,\n            StreamObserver<WorkflowServicePb.WorkflowSearchResult> responseObserver) {\n        doSearchV2(false, request, responseObserver);\n    }\n\n    @Override\n    public void searchByTasksV2(\n            SearchPb.Request request,\n            StreamObserver<WorkflowServicePb.WorkflowSearchResult> responseObserver) {\n        doSearchV2(true, request, responseObserver);\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/test/java/com/netflix/conductor/grpc/server/service/HealthServiceImplTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\npublic class HealthServiceImplTest {\n\n    // SBMTODO: Move this Spring boot health check\n    //    @Rule\n    //    public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();\n    //\n    //    @Rule\n    //    public ExpectedException thrown = ExpectedException.none();\n    //\n    //    @Test\n    //    public void healthServing() throws Exception {\n    //        // Generate a unique in-process server name.\n    //        String serverName = InProcessServerBuilder.generateName();\n    //        HealthCheckAggregator hca = mock(HealthCheckAggregator.class);\n    //        CompletableFuture<HealthCheckStatus> hcsf = mock(CompletableFuture.class);\n    //        HealthCheckStatus hcs = mock(HealthCheckStatus.class);\n    //        when(hcs.isHealthy()).thenReturn(true);\n    //        when(hcsf.get()).thenReturn(hcs);\n    //        when(hca.check()).thenReturn(hcsf);\n    //        HealthServiceImpl healthyService = new HealthServiceImpl(hca);\n    //\n    //        addService(serverName, healthyService);\n    //        HealthGrpc.HealthBlockingStub blockingStub = HealthGrpc.newBlockingStub(\n    //                // Create a client channel and register for automatic graceful shutdown.\n    //\n    // grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));\n    //\n    //\n    //        HealthCheckResponse reply =\n    // blockingStub.check(HealthCheckRequest.newBuilder().build());\n    //\n    //        assertEquals(HealthCheckResponse.ServingStatus.SERVING, reply.getStatus());\n    //    }\n    //\n    //    @Test\n    //    public void healthNotServing() throws Exception {\n    //        // Generate a unique in-process server name.\n    //        String serverName = InProcessServerBuilder.generateName();\n    //        HealthCheckAggregator hca = mock(HealthCheckAggregator.class);\n    //        CompletableFuture<HealthCheckStatus> hcsf = mock(CompletableFuture.class);\n    //        HealthCheckStatus hcs = mock(HealthCheckStatus.class);\n    //        when(hcs.isHealthy()).thenReturn(false);\n    //        when(hcsf.get()).thenReturn(hcs);\n    //        when(hca.check()).thenReturn(hcsf);\n    //        HealthServiceImpl healthyService = new HealthServiceImpl(hca);\n    //\n    //        addService(serverName, healthyService);\n    //        HealthGrpc.HealthBlockingStub blockingStub = HealthGrpc.newBlockingStub(\n    //                // Create a client channel and register for automatic graceful shutdown.\n    //\n    // grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));\n    //\n    //\n    //        HealthCheckResponse reply =\n    // blockingStub.check(HealthCheckRequest.newBuilder().build());\n    //\n    //        assertEquals(HealthCheckResponse.ServingStatus.NOT_SERVING, reply.getStatus());\n    //    }\n    //\n    //    @Test\n    //    public void healthException() throws Exception {\n    //        // Generate a unique in-process server name.\n    //        String serverName = InProcessServerBuilder.generateName();\n    //        HealthCheckAggregator hca = mock(HealthCheckAggregator.class);\n    //        CompletableFuture<HealthCheckStatus> hcsf = mock(CompletableFuture.class);\n    //        when(hcsf.get()).thenThrow(InterruptedException.class);\n    //        when(hca.check()).thenReturn(hcsf);\n    //        HealthServiceImpl healthyService = new HealthServiceImpl(hca);\n    //\n    //        addService(serverName, healthyService);\n    //        HealthGrpc.HealthBlockingStub blockingStub = HealthGrpc.newBlockingStub(\n    //                // Create a client channel and register for automatic graceful shutdown.\n    //\n    // grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));\n    //\n    //        thrown.expect(StatusRuntimeException.class);\n    //        thrown.expect(hasProperty(\"status\", is(Status.INTERNAL)));\n    //        blockingStub.check(HealthCheckRequest.newBuilder().build());\n    //\n    //    }\n    //\n    //    private void addService(String name, BindableService service) throws Exception {\n    //        // Create a server, add service, start, and register for automatic graceful shutdown.\n    //        grpcCleanup.register(InProcessServerBuilder\n    //                .forName(name).directExecutor().addService(service).build().start());\n    //    }\n}\n"
  },
  {
    "path": "grpc-server/src/test/java/com/netflix/conductor/grpc/server/service/TaskServiceImplTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport java.util.Collections;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mock;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.TaskServicePb;\nimport com.netflix.conductor.proto.TaskPb;\nimport com.netflix.conductor.proto.TaskSummaryPb;\nimport com.netflix.conductor.service.ExecutionService;\nimport com.netflix.conductor.service.TaskService;\n\nimport io.grpc.stub.StreamObserver;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.*;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class TaskServiceImplTest {\n\n    @Mock private TaskService taskService;\n\n    @Mock private ExecutionService executionService;\n\n    private TaskServiceImpl taskServiceImpl;\n\n    @Before\n    public void init() {\n        initMocks(this);\n        taskServiceImpl = new TaskServiceImpl(executionService, taskService, 5000);\n    }\n\n    @Test\n    public void searchExceptionTest() throws InterruptedException {\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<Throwable> throwable = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(50000)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"*\")\n                        .build();\n\n        StreamObserver<TaskServicePb.TaskSummarySearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(TaskServicePb.TaskSummarySearchResult value) {}\n\n                    @Override\n                    public void onError(Throwable t) {\n                        throwable.set(t);\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        taskServiceImpl.search(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        assertEquals(\n                \"INVALID_ARGUMENT: Cannot return more than 5000 results\",\n                throwable.get().getMessage());\n    }\n\n    @Test\n    public void searchV2ExceptionTest() throws InterruptedException {\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<Throwable> throwable = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(50000)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"*\")\n                        .build();\n\n        StreamObserver<TaskServicePb.TaskSearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(TaskServicePb.TaskSearchResult value) {}\n\n                    @Override\n                    public void onError(Throwable t) {\n                        throwable.set(t);\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        taskServiceImpl.searchV2(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        assertEquals(\n                \"INVALID_ARGUMENT: Cannot return more than 5000 results\",\n                throwable.get().getMessage());\n    }\n\n    @Test\n    public void searchTest() throws InterruptedException {\n\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<TaskServicePb.TaskSummarySearchResult> result = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(1)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"*\")\n                        .build();\n\n        StreamObserver<TaskServicePb.TaskSummarySearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(TaskServicePb.TaskSummarySearchResult value) {\n                        result.set(value);\n                    }\n\n                    @Override\n                    public void onError(Throwable t) {\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        TaskSummary taskSummary = new TaskSummary();\n        SearchResult<TaskSummary> searchResult = new SearchResult<>();\n        searchResult.setTotalHits(1);\n        searchResult.setResults(Collections.singletonList(taskSummary));\n\n        when(taskService.search(1, 1, \"strings\", \"*\", \"\")).thenReturn(searchResult);\n\n        taskServiceImpl.search(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        TaskServicePb.TaskSummarySearchResult taskSummarySearchResult = result.get();\n\n        assertEquals(1, taskSummarySearchResult.getTotalHits());\n        assertEquals(\n                TaskSummaryPb.TaskSummary.newBuilder().build(),\n                taskSummarySearchResult.getResultsList().get(0));\n    }\n\n    @Test\n    public void searchV2Test() throws InterruptedException {\n\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<TaskServicePb.TaskSearchResult> result = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(1)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"*\")\n                        .build();\n\n        StreamObserver<TaskServicePb.TaskSearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(TaskServicePb.TaskSearchResult value) {\n                        result.set(value);\n                    }\n\n                    @Override\n                    public void onError(Throwable t) {\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        Task task = new Task();\n        SearchResult<Task> searchResult = new SearchResult<>();\n        searchResult.setTotalHits(1);\n        searchResult.setResults(Collections.singletonList(task));\n\n        when(taskService.searchV2(1, 1, \"strings\", \"*\", \"\")).thenReturn(searchResult);\n\n        taskServiceImpl.searchV2(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        TaskServicePb.TaskSearchResult taskSearchResult = result.get();\n\n        assertEquals(1, taskSearchResult.getTotalHits());\n        assertEquals(\n                TaskPb.Task.newBuilder().setCallbackFromWorker(true).build(),\n                taskSearchResult.getResultsList().get(0));\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/test/java/com/netflix/conductor/grpc/server/service/WorkflowServiceImplTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.grpc.server.service;\n\nimport java.util.Collections;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mock;\n\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.grpc.SearchPb;\nimport com.netflix.conductor.grpc.WorkflowServicePb;\nimport com.netflix.conductor.proto.WorkflowPb;\nimport com.netflix.conductor.proto.WorkflowSummaryPb;\nimport com.netflix.conductor.service.WorkflowService;\n\nimport io.grpc.stub.StreamObserver;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.*;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\npublic class WorkflowServiceImplTest {\n\n    private static final String WORKFLOW_ID = \"anyWorkflowId\";\n    private static final Boolean RESUME_SUBWORKFLOW_TASKS = true;\n\n    @Mock private WorkflowService workflowService;\n\n    private WorkflowServiceImpl workflowServiceImpl;\n\n    @Before\n    public void init() {\n        initMocks(this);\n        workflowServiceImpl = new WorkflowServiceImpl(workflowService, 5000);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void givenWorkflowIdWhenRetryWorkflowThenRetriedSuccessfully() {\n        // Given\n        WorkflowServicePb.RetryWorkflowRequest req =\n                WorkflowServicePb.RetryWorkflowRequest.newBuilder()\n                        .setWorkflowId(WORKFLOW_ID)\n                        .setResumeSubworkflowTasks(true)\n                        .build();\n        // When\n        workflowServiceImpl.retryWorkflow(req, mock(StreamObserver.class));\n        // Then\n        verify(workflowService).retryWorkflow(WORKFLOW_ID, RESUME_SUBWORKFLOW_TASKS);\n    }\n\n    @Test\n    public void searchExceptionTest() throws InterruptedException {\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<Throwable> throwable = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(50000)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"\")\n                        .build();\n\n        StreamObserver<WorkflowServicePb.WorkflowSummarySearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(WorkflowServicePb.WorkflowSummarySearchResult value) {}\n\n                    @Override\n                    public void onError(Throwable t) {\n                        throwable.set(t);\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        workflowServiceImpl.search(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        assertEquals(\n                \"INVALID_ARGUMENT: Cannot return more than 5000 results\",\n                throwable.get().getMessage());\n    }\n\n    @Test\n    public void searchV2ExceptionTest() throws InterruptedException {\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<Throwable> throwable = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(50000)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"\")\n                        .build();\n\n        StreamObserver<WorkflowServicePb.WorkflowSearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(WorkflowServicePb.WorkflowSearchResult value) {}\n\n                    @Override\n                    public void onError(Throwable t) {\n                        throwable.set(t);\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        workflowServiceImpl.searchV2(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        assertEquals(\n                \"INVALID_ARGUMENT: Cannot return more than 5000 results\",\n                throwable.get().getMessage());\n    }\n\n    @Test\n    public void searchTest() throws InterruptedException {\n\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<WorkflowServicePb.WorkflowSummarySearchResult> result =\n                new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(1)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"\")\n                        .build();\n\n        StreamObserver<WorkflowServicePb.WorkflowSummarySearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(WorkflowServicePb.WorkflowSummarySearchResult value) {\n                        result.set(value);\n                    }\n\n                    @Override\n                    public void onError(Throwable t) {\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        WorkflowSummary workflow = new WorkflowSummary();\n        SearchResult<WorkflowSummary> searchResult = new SearchResult<>();\n        searchResult.setTotalHits(1);\n        searchResult.setResults(Collections.singletonList(workflow));\n\n        when(workflowService.searchWorkflows(\n                        anyInt(), anyInt(), anyList(), anyString(), anyString()))\n                .thenReturn(searchResult);\n\n        workflowServiceImpl.search(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        WorkflowServicePb.WorkflowSummarySearchResult workflowSearchResult = result.get();\n\n        assertEquals(1, workflowSearchResult.getTotalHits());\n        assertEquals(\n                WorkflowSummaryPb.WorkflowSummary.newBuilder().build(),\n                workflowSearchResult.getResultsList().get(0));\n    }\n\n    @Test\n    public void searchByTasksTest() throws InterruptedException {\n\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<WorkflowServicePb.WorkflowSummarySearchResult> result =\n                new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(1)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"\")\n                        .build();\n\n        StreamObserver<WorkflowServicePb.WorkflowSummarySearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(WorkflowServicePb.WorkflowSummarySearchResult value) {\n                        result.set(value);\n                    }\n\n                    @Override\n                    public void onError(Throwable t) {\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        WorkflowSummary workflow = new WorkflowSummary();\n        SearchResult<WorkflowSummary> searchResult = new SearchResult<>();\n        searchResult.setTotalHits(1);\n        searchResult.setResults(Collections.singletonList(workflow));\n\n        when(workflowService.searchWorkflowsByTasks(\n                        anyInt(), anyInt(), anyList(), anyString(), anyString()))\n                .thenReturn(searchResult);\n\n        workflowServiceImpl.searchByTasks(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        WorkflowServicePb.WorkflowSummarySearchResult workflowSearchResult = result.get();\n\n        assertEquals(1, workflowSearchResult.getTotalHits());\n        assertEquals(\n                WorkflowSummaryPb.WorkflowSummary.newBuilder().build(),\n                workflowSearchResult.getResultsList().get(0));\n    }\n\n    @Test\n    public void searchV2Test() throws InterruptedException {\n\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<WorkflowServicePb.WorkflowSearchResult> result = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(1)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"\")\n                        .build();\n\n        StreamObserver<WorkflowServicePb.WorkflowSearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(WorkflowServicePb.WorkflowSearchResult value) {\n                        result.set(value);\n                    }\n\n                    @Override\n                    public void onError(Throwable t) {\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        Workflow workflow = new Workflow();\n        SearchResult<Workflow> searchResult = new SearchResult<>();\n        searchResult.setTotalHits(1);\n        searchResult.setResults(Collections.singletonList(workflow));\n\n        when(workflowService.searchWorkflowsV2(1, 1, Collections.singletonList(\"strings\"), \"*\", \"\"))\n                .thenReturn(searchResult);\n\n        workflowServiceImpl.searchV2(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        WorkflowServicePb.WorkflowSearchResult workflowSearchResult = result.get();\n\n        assertEquals(1, workflowSearchResult.getTotalHits());\n        assertEquals(\n                WorkflowPb.Workflow.newBuilder().build(),\n                workflowSearchResult.getResultsList().get(0));\n    }\n\n    @Test\n    public void searchByTasksV2Test() throws InterruptedException {\n\n        CountDownLatch streamAlive = new CountDownLatch(1);\n        AtomicReference<WorkflowServicePb.WorkflowSearchResult> result = new AtomicReference<>();\n\n        SearchPb.Request req =\n                SearchPb.Request.newBuilder()\n                        .setStart(1)\n                        .setSize(1)\n                        .setSort(\"strings\")\n                        .setQuery(\"\")\n                        .setFreeText(\"\")\n                        .build();\n\n        StreamObserver<WorkflowServicePb.WorkflowSearchResult> streamObserver =\n                new StreamObserver<>() {\n                    @Override\n                    public void onNext(WorkflowServicePb.WorkflowSearchResult value) {\n                        result.set(value);\n                    }\n\n                    @Override\n                    public void onError(Throwable t) {\n                        streamAlive.countDown();\n                    }\n\n                    @Override\n                    public void onCompleted() {\n                        streamAlive.countDown();\n                    }\n                };\n\n        Workflow workflow = new Workflow();\n        SearchResult<Workflow> searchResult = new SearchResult<>();\n        searchResult.setTotalHits(1);\n        searchResult.setResults(Collections.singletonList(workflow));\n\n        when(workflowService.searchWorkflowsByTasksV2(\n                        1, 1, Collections.singletonList(\"strings\"), \"*\", \"\"))\n                .thenReturn(searchResult);\n\n        workflowServiceImpl.searchByTasksV2(req, streamObserver);\n\n        streamAlive.await(10, TimeUnit.MILLISECONDS);\n\n        WorkflowServicePb.WorkflowSearchResult workflowSearchResult = result.get();\n\n        assertEquals(1, workflowSearchResult.getTotalHits());\n        assertEquals(\n                WorkflowPb.Workflow.newBuilder().build(),\n                workflowSearchResult.getResultsList().get(0));\n    }\n}\n"
  },
  {
    "path": "grpc-server/src/test/resources/log4j.properties",
    "content": "#\n# Copyright 2019 Netflix, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n# Set root logger level to WARN and its only appender to A1.\nlog4j.rootLogger=WARN, A1\n\n# A1 is set to be a ConsoleAppender.\nlog4j.appender.A1=org.apache.log4j.ConsoleAppender\n\n# A1 uses PatternLayout.\nlog4j.appender.A1.layout=org.apache.log4j.PatternLayout\nlog4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n"
  },
  {
    "path": "http-task/build.gradle",
    "content": "/*\n *  Copyright 2022 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\ndependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n    compileOnly 'org.springframework.boot:spring-boot-starter-web'\n\n    implementation \"javax.ws.rs:jsr311-api:${revJsr311Api}\"\n\n    testImplementation 'org.springframework.boot:spring-boot-starter-web'\n    testImplementation \"org.testcontainers:mockserver:${revTestContainer}\"\n    testImplementation \"org.mock-server:mockserver-client-java:${revMockServerClient}\"\n    testImplementation \"org.bouncycastle:bcprov-jdk15on:1.70\"\n    testImplementation \"org.bouncycastle:bcpkix-jdk15on:1.70\"\n}"
  },
  {
    "path": "http-task/src/main/java/com/netflix/conductor/tasks/http/HttpTask.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.tasks.http;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.*;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.web.client.RestClientException;\nimport org.springframework.web.client.RestTemplate;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.conductor.tasks.http.providers.RestTemplateProvider;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_HTTP;\n\n/** Task that enables calling another HTTP endpoint as part of its execution */\n@Component(TASK_TYPE_HTTP)\npublic class HttpTask extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(HttpTask.class);\n\n    public static final String REQUEST_PARAMETER_NAME = \"http_request\";\n\n    static final String MISSING_REQUEST =\n            \"Missing HTTP request. Task input MUST have a '\"\n                    + REQUEST_PARAMETER_NAME\n                    + \"' key with HttpTask.Input as value. See documentation for HttpTask for required input parameters\";\n\n    private final TypeReference<Map<String, Object>> mapOfObj =\n            new TypeReference<Map<String, Object>>() {};\n    private final TypeReference<List<Object>> listOfObj = new TypeReference<List<Object>>() {};\n    protected ObjectMapper objectMapper;\n    protected RestTemplateProvider restTemplateProvider;\n    private final String requestParameter;\n\n    @Autowired\n    public HttpTask(RestTemplateProvider restTemplateProvider, ObjectMapper objectMapper) {\n        this(TASK_TYPE_HTTP, restTemplateProvider, objectMapper);\n    }\n\n    public HttpTask(\n            String name, RestTemplateProvider restTemplateProvider, ObjectMapper objectMapper) {\n        super(name);\n        this.restTemplateProvider = restTemplateProvider;\n        this.objectMapper = objectMapper;\n        this.requestParameter = REQUEST_PARAMETER_NAME;\n        LOGGER.info(\"{} initialized...\", getTaskType());\n    }\n\n    @Override\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n        Object request = task.getInputData().get(requestParameter);\n        task.setWorkerId(Utils.getServerId());\n        if (request == null) {\n            task.setReasonForIncompletion(MISSING_REQUEST);\n            task.setStatus(TaskModel.Status.FAILED);\n            return;\n        }\n\n        Input input = objectMapper.convertValue(request, Input.class);\n        if (input.getUri() == null) {\n            String reason =\n                    \"Missing HTTP URI.  See documentation for HttpTask for required input parameters\";\n            task.setReasonForIncompletion(reason);\n            task.setStatus(TaskModel.Status.FAILED);\n            return;\n        }\n\n        if (input.getMethod() == null) {\n            String reason = \"No HTTP method specified\";\n            task.setReasonForIncompletion(reason);\n            task.setStatus(TaskModel.Status.FAILED);\n            return;\n        }\n\n        try {\n            HttpResponse response = httpCall(input);\n            LOGGER.debug(\n                    \"Response: {}, {}, task:{}\",\n                    response.statusCode,\n                    response.body,\n                    task.getTaskId());\n            if (response.statusCode > 199 && response.statusCode < 300) {\n                if (isAsyncComplete(task)) {\n                    task.setStatus(TaskModel.Status.IN_PROGRESS);\n                } else {\n                    task.setStatus(TaskModel.Status.COMPLETED);\n                }\n            } else {\n                if (response.body != null) {\n                    task.setReasonForIncompletion(response.body.toString());\n                } else {\n                    task.setReasonForIncompletion(\"No response from the remote service\");\n                }\n                task.setStatus(TaskModel.Status.FAILED);\n            }\n            //noinspection ConstantConditions\n            if (response != null) {\n                task.addOutput(\"response\", response.asMap());\n            }\n\n        } catch (Exception e) {\n            LOGGER.error(\n                    \"Failed to invoke {} task: {} - uri: {}, vipAddress: {} in workflow: {}\",\n                    getTaskType(),\n                    task.getTaskId(),\n                    input.getUri(),\n                    input.getVipAddress(),\n                    task.getWorkflowInstanceId(),\n                    e);\n            task.setStatus(TaskModel.Status.FAILED);\n            task.setReasonForIncompletion(\n                    \"Failed to invoke \" + getTaskType() + \" task due to: \" + e);\n            task.addOutput(\"response\", e.toString());\n        }\n    }\n\n    /**\n     * @param input HTTP Request\n     * @return Response of the http call\n     * @throws Exception If there was an error making http call Note: protected access is so that\n     *     tasks extended from this task can re-use this to make http calls\n     */\n    protected HttpResponse httpCall(Input input) throws Exception {\n        RestTemplate restTemplate = restTemplateProvider.getRestTemplate(input);\n\n        HttpHeaders headers = new HttpHeaders();\n        headers.setContentType(MediaType.valueOf(input.getContentType()));\n        headers.setAccept(Collections.singletonList(MediaType.valueOf(input.getAccept())));\n\n        input.headers.forEach(\n                (key, value) -> {\n                    if (value != null) {\n                        headers.add(key, value.toString());\n                    }\n                });\n\n        HttpEntity<Object> request = new HttpEntity<>(input.getBody(), headers);\n\n        HttpResponse response = new HttpResponse();\n        try {\n            ResponseEntity<String> responseEntity =\n                    restTemplate.exchange(input.getUri(), input.getMethod(), request, String.class);\n            if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.hasBody()) {\n                response.body = extractBody(responseEntity.getBody());\n            }\n\n            response.statusCode = responseEntity.getStatusCodeValue();\n            response.reasonPhrase = responseEntity.getStatusCode().getReasonPhrase();\n            response.headers = responseEntity.getHeaders();\n            return response;\n        } catch (RestClientException ex) {\n            LOGGER.error(\n                    String.format(\n                            \"Got unexpected http response - uri: %s, vipAddress: %s\",\n                            input.getUri(), input.getVipAddress()),\n                    ex);\n            String reason = ex.getLocalizedMessage();\n            LOGGER.error(reason, ex);\n            throw new Exception(reason);\n        }\n    }\n\n    private Object extractBody(String responseBody) {\n        try {\n            JsonNode node = objectMapper.readTree(responseBody);\n            if (node.isArray()) {\n                return objectMapper.convertValue(node, listOfObj);\n            } else if (node.isObject()) {\n                return objectMapper.convertValue(node, mapOfObj);\n            } else if (node.isNumber()) {\n                return objectMapper.convertValue(node, Double.class);\n            } else {\n                return node.asText();\n            }\n        } catch (IOException jpe) {\n            LOGGER.error(\"Error extracting response body\", jpe);\n            return responseBody;\n        }\n    }\n\n    @Override\n    public boolean execute(WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n        return false;\n    }\n\n    @Override\n    public void cancel(WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n        task.setStatus(TaskModel.Status.CANCELED);\n    }\n\n    @Override\n    public boolean isAsync() {\n        return true;\n    }\n\n    public static class HttpResponse {\n\n        public Object body;\n        public MultiValueMap<String, String> headers;\n        public int statusCode;\n        public String reasonPhrase;\n\n        @Override\n        public String toString() {\n            return \"HttpResponse [body=\"\n                    + body\n                    + \", headers=\"\n                    + headers\n                    + \", statusCode=\"\n                    + statusCode\n                    + \", reasonPhrase=\"\n                    + reasonPhrase\n                    + \"]\";\n        }\n\n        public Map<String, Object> asMap() {\n            Map<String, Object> map = new HashMap<>();\n            map.put(\"body\", body);\n            map.put(\"headers\", headers);\n            map.put(\"statusCode\", statusCode);\n            map.put(\"reasonPhrase\", reasonPhrase);\n            return map;\n        }\n    }\n\n    public static class Input {\n\n        private HttpMethod method; // PUT, POST, GET, DELETE, OPTIONS, HEAD\n        private String vipAddress;\n        private String appName;\n        private Map<String, Object> headers = new HashMap<>();\n        private String uri;\n        private Object body;\n        private String accept = MediaType.APPLICATION_JSON_VALUE;\n        private String contentType = MediaType.APPLICATION_JSON_VALUE;\n        private Integer connectionTimeOut;\n        private Integer readTimeOut;\n\n        /**\n         * @return the method\n         */\n        public HttpMethod getMethod() {\n            return method;\n        }\n\n        /**\n         * @param method the method to set\n         */\n        public void setMethod(String method) {\n            this.method = HttpMethod.valueOf(method);\n        }\n\n        /**\n         * @return the headers\n         */\n        public Map<String, Object> getHeaders() {\n            return headers;\n        }\n\n        /**\n         * @param headers the headers to set\n         */\n        public void setHeaders(Map<String, Object> headers) {\n            this.headers = headers;\n        }\n\n        /**\n         * @return the body\n         */\n        public Object getBody() {\n            return body;\n        }\n\n        /**\n         * @param body the body to set\n         */\n        public void setBody(Object body) {\n            this.body = body;\n        }\n\n        /**\n         * @return the uri\n         */\n        public String getUri() {\n            return uri;\n        }\n\n        /**\n         * @param uri the uri to set\n         */\n        public void setUri(String uri) {\n            this.uri = uri;\n        }\n\n        /**\n         * @return the vipAddress\n         */\n        public String getVipAddress() {\n            return vipAddress;\n        }\n\n        /**\n         * @param vipAddress the vipAddress to set\n         */\n        public void setVipAddress(String vipAddress) {\n            this.vipAddress = vipAddress;\n        }\n\n        /**\n         * @return the accept\n         */\n        public String getAccept() {\n            return accept;\n        }\n\n        /**\n         * @param accept the accept to set\n         */\n        public void setAccept(String accept) {\n            this.accept = accept;\n        }\n\n        /**\n         * @return the MIME content type to use for the request\n         */\n        public String getContentType() {\n            return contentType;\n        }\n\n        /**\n         * @param contentType the MIME content type to set\n         */\n        public void setContentType(String contentType) {\n            this.contentType = contentType;\n        }\n\n        public String getAppName() {\n            return appName;\n        }\n\n        public void setAppName(String appName) {\n            this.appName = appName;\n        }\n\n        /**\n         * @return the connectionTimeOut\n         */\n        public Integer getConnectionTimeOut() {\n            return connectionTimeOut;\n        }\n\n        /**\n         * @return the readTimeOut\n         */\n        public Integer getReadTimeOut() {\n            return readTimeOut;\n        }\n\n        public void setConnectionTimeOut(Integer connectionTimeOut) {\n            this.connectionTimeOut = connectionTimeOut;\n        }\n\n        public void setReadTimeOut(Integer readTimeOut) {\n            this.readTimeOut = readTimeOut;\n        }\n    }\n}\n"
  },
  {
    "path": "http-task/src/main/java/com/netflix/conductor/tasks/http/providers/DefaultRestTemplateProvider.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.tasks.http.providers;\n\nimport java.time.Duration;\nimport java.util.Optional;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.http.client.HttpComponentsClientHttpRequestFactory;\nimport org.springframework.lang.NonNull;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.client.RestTemplate;\n\nimport com.netflix.conductor.tasks.http.HttpTask;\n\n/**\n * Provider for a customized RestTemplateBuilder. This class provides a default {@link\n * RestTemplateBuilder} which can be configured or extended as needed.\n */\n@Component\npublic class DefaultRestTemplateProvider implements RestTemplateProvider {\n\n    private final ThreadLocal<RestTemplate> threadLocalRestTemplate;\n\n    private final int defaultReadTimeout;\n    private final int defaultConnectTimeout;\n\n    @Autowired\n    public DefaultRestTemplateProvider(\n            @Value(\"${conductor.tasks.http.readTimeout:150ms}\") Duration readTimeout,\n            @Value(\"${conductor.tasks.http.connectTimeout:100ms}\") Duration connectTimeout) {\n        this.threadLocalRestTemplate = ThreadLocal.withInitial(RestTemplate::new);\n        this.defaultReadTimeout = (int) readTimeout.toMillis();\n        this.defaultConnectTimeout = (int) connectTimeout.toMillis();\n    }\n\n    @Override\n    public @NonNull RestTemplate getRestTemplate(@NonNull HttpTask.Input input) {\n        RestTemplate restTemplate = threadLocalRestTemplate.get();\n        HttpComponentsClientHttpRequestFactory requestFactory =\n                new HttpComponentsClientHttpRequestFactory();\n        requestFactory.setConnectTimeout(\n                Optional.ofNullable(input.getConnectionTimeOut()).orElse(defaultConnectTimeout));\n        requestFactory.setReadTimeout(\n                Optional.ofNullable(input.getReadTimeOut()).orElse(defaultReadTimeout));\n        restTemplate.setRequestFactory(requestFactory);\n        return restTemplate;\n    }\n}\n"
  },
  {
    "path": "http-task/src/main/java/com/netflix/conductor/tasks/http/providers/RestTemplateProvider.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.tasks.http.providers;\n\nimport org.springframework.lang.NonNull;\nimport org.springframework.web.client.RestTemplate;\n\nimport com.netflix.conductor.tasks.http.HttpTask;\n\n@FunctionalInterface\npublic interface RestTemplateProvider {\n\n    RestTemplate getRestTemplate(@NonNull HttpTask.Input input);\n}\n"
  },
  {
    "path": "http-task/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"properties\": [\n    {\n      \"name\": \"conductor.tasks.http.readTimeout\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"The read timeout of the underlying HttpClient used by the HTTP task.\"\n    },\n    {\n      \"name\": \"conductor.tasks.http.connectTimeout\",\n      \"type\": \"java.lang.Integer\",\n      \"description\": \"The connection timeout of the underlying HttpClient used by the HTTP task.\"\n    }\n  ]\n}\n"
  },
  {
    "path": "http-task/src/test/java/com/netflix/conductor/tasks/http/HttpTaskTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.tasks.http;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.ClassRule;\nimport org.junit.Test;\nimport org.mockserver.client.MockServerClient;\nimport org.mockserver.model.HttpRequest;\nimport org.mockserver.model.HttpResponse;\nimport org.mockserver.model.MediaType;\nimport org.testcontainers.containers.MockServerContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.core.execution.DeciderService;\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.tasks.SystemTaskRegistry;\nimport com.netflix.conductor.core.utils.ExternalPayloadStorageUtils;\nimport com.netflix.conductor.core.utils.IDGenerator;\nimport com.netflix.conductor.core.utils.ParametersUtils;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.conductor.tasks.http.providers.DefaultRestTemplateProvider;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Mockito.mock;\n\n@SuppressWarnings(\"unchecked\")\npublic class HttpTaskTest {\n\n    private static final String ERROR_RESPONSE = \"Something went wrong!\";\n    private static final String TEXT_RESPONSE = \"Text Response\";\n    private static final double NUM_RESPONSE = 42.42d;\n\n    private HttpTask httpTask;\n    private WorkflowExecutor workflowExecutor;\n    private final WorkflowModel workflow = new WorkflowModel();\n\n    private static final ObjectMapper objectMapper = new ObjectMapper();\n    private static String JSON_RESPONSE;\n\n    @ClassRule\n    public static MockServerContainer mockServer =\n            new MockServerContainer(\n                    DockerImageName.parse(\"mockserver/mockserver\").withTag(\"mockserver-5.12.0\"));\n\n    @BeforeClass\n    public static void init() throws Exception {\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"key\", \"value1\");\n        map.put(\"num\", 42);\n        map.put(\"SomeKey\", null);\n        JSON_RESPONSE = objectMapper.writeValueAsString(map);\n\n        final TypeReference<Map<String, Object>> mapOfObj = new TypeReference<>() {};\n        MockServerClient client =\n                new MockServerClient(mockServer.getHost(), mockServer.getServerPort());\n        client.when(HttpRequest.request().withPath(\"/post\").withMethod(\"POST\"))\n                .respond(\n                        request -> {\n                            Map<String, Object> reqBody =\n                                    objectMapper.readValue(request.getBody().toString(), mapOfObj);\n                            Set<String> keys = reqBody.keySet();\n                            Map<String, Object> respBody = new HashMap<>();\n                            keys.forEach(k -> respBody.put(k, k));\n                            return HttpResponse.response()\n                                    .withContentType(MediaType.APPLICATION_JSON)\n                                    .withBody(objectMapper.writeValueAsString(respBody));\n                        });\n        client.when(HttpRequest.request().withPath(\"/post2\").withMethod(\"POST\"))\n                .respond(HttpResponse.response().withStatusCode(204));\n        client.when(HttpRequest.request().withPath(\"/failure\").withMethod(\"GET\"))\n                .respond(\n                        HttpResponse.response()\n                                .withStatusCode(500)\n                                .withContentType(MediaType.TEXT_PLAIN)\n                                .withBody(ERROR_RESPONSE));\n        client.when(HttpRequest.request().withPath(\"/text\").withMethod(\"GET\"))\n                .respond(HttpResponse.response().withBody(TEXT_RESPONSE));\n        client.when(HttpRequest.request().withPath(\"/numeric\").withMethod(\"GET\"))\n                .respond(HttpResponse.response().withBody(String.valueOf(NUM_RESPONSE)));\n        client.when(HttpRequest.request().withPath(\"/json\").withMethod(\"GET\"))\n                .respond(\n                        HttpResponse.response()\n                                .withContentType(MediaType.APPLICATION_JSON)\n                                .withBody(JSON_RESPONSE));\n    }\n\n    @Before\n    public void setup() {\n        workflowExecutor = mock(WorkflowExecutor.class);\n        DefaultRestTemplateProvider defaultRestTemplateProvider =\n                new DefaultRestTemplateProvider(Duration.ofMillis(150), Duration.ofMillis(100));\n        httpTask = new HttpTask(defaultRestTemplateProvider, objectMapper);\n    }\n\n    @Test\n    public void testPost() {\n\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/post\");\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input_key1\", \"value1\");\n        body.put(\"input_key2\", 45.3d);\n        body.put(\"someKey\", null);\n        input.setBody(body);\n        input.setMethod(\"POST\");\n        input.setReadTimeOut(1000);\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(task.getReasonForIncompletion(), TaskModel.Status.COMPLETED, task.getStatus());\n        Map<String, Object> hr = (Map<String, Object>) task.getOutputData().get(\"response\");\n        Object response = hr.get(\"body\");\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertTrue(\"response is: \" + response, response instanceof Map);\n        Map<String, Object> map = (Map<String, Object>) response;\n        Set<String> inputKeys = body.keySet();\n        Set<String> responseKeys = map.keySet();\n        inputKeys.containsAll(responseKeys);\n        responseKeys.containsAll(inputKeys);\n    }\n\n    @Test\n    public void testPostNoContent() {\n\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\n                \"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/post2\");\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input_key1\", \"value1\");\n        body.put(\"input_key2\", 45.3d);\n        input.setBody(body);\n        input.setMethod(\"POST\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(task.getReasonForIncompletion(), TaskModel.Status.COMPLETED, task.getStatus());\n        Map<String, Object> hr = (Map<String, Object>) task.getOutputData().get(\"response\");\n        Object response = hr.get(\"body\");\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertNull(\"response is: \" + response, response);\n    }\n\n    @Test\n    public void testFailure() {\n\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\n                \"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/failure\");\n        input.setMethod(\"GET\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(\n                \"Task output: \" + task.getOutputData(), TaskModel.Status.FAILED, task.getStatus());\n        assertTrue(task.getReasonForIncompletion().contains(ERROR_RESPONSE));\n\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.getInputData().remove(HttpTask.REQUEST_PARAMETER_NAME);\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n        assertEquals(HttpTask.MISSING_REQUEST, task.getReasonForIncompletion());\n    }\n\n    @Test\n    public void testPostAsyncComplete() {\n\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/post\");\n        Map<String, Object> body = new HashMap<>();\n        body.put(\"input_key1\", \"value1\");\n        body.put(\"input_key2\", 45.3d);\n        input.setBody(body);\n        input.setMethod(\"POST\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n        task.getInputData().put(\"asyncComplete\", true);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(\n                task.getReasonForIncompletion(), TaskModel.Status.IN_PROGRESS, task.getStatus());\n        Map<String, Object> hr = (Map<String, Object>) task.getOutputData().get(\"response\");\n        Object response = hr.get(\"body\");\n        assertEquals(TaskModel.Status.IN_PROGRESS, task.getStatus());\n        assertTrue(\"response is: \" + response, response instanceof Map);\n        Map<String, Object> map = (Map<String, Object>) response;\n        Set<String> inputKeys = body.keySet();\n        Set<String> responseKeys = map.keySet();\n        inputKeys.containsAll(responseKeys);\n        responseKeys.containsAll(inputKeys);\n    }\n\n    @Test\n    public void testTextGET() {\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/text\");\n        input.setMethod(\"GET\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        Map<String, Object> hr = (Map<String, Object>) task.getOutputData().get(\"response\");\n        Object response = hr.get(\"body\");\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertEquals(TEXT_RESPONSE, response);\n    }\n\n    @Test\n    public void testNumberGET() {\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\n                \"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/numeric\");\n        input.setMethod(\"GET\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        Map<String, Object> hr = (Map<String, Object>) task.getOutputData().get(\"response\");\n        Object response = hr.get(\"body\");\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertEquals(NUM_RESPONSE, response);\n        assertTrue(response instanceof Number);\n    }\n\n    @Test\n    public void testJsonGET() throws JsonProcessingException {\n\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/json\");\n        input.setMethod(\"GET\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        Map<String, Object> hr = (Map<String, Object>) task.getOutputData().get(\"response\");\n        Object response = hr.get(\"body\");\n        assertEquals(TaskModel.Status.COMPLETED, task.getStatus());\n        assertTrue(response instanceof Map);\n        Map<String, Object> map = (Map<String, Object>) response;\n        assertEquals(JSON_RESPONSE, objectMapper.writeValueAsString(map));\n    }\n\n    @Test\n    public void testExecute() {\n\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/json\");\n        input.setMethod(\"GET\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.setScheduledTime(0);\n\n        boolean executed = httpTask.execute(workflow, task, workflowExecutor);\n        assertFalse(executed);\n    }\n\n    @Test\n    public void testHTTPGetConnectionTimeOut() {\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        Instant start = Instant.now();\n        input.setConnectionTimeOut(110);\n        input.setMethod(\"GET\");\n        input.setUri(\"http://10.255.14.15\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.setScheduledTime(0);\n        httpTask.start(workflow, task, workflowExecutor);\n        Instant end = Instant.now();\n        long diff = end.toEpochMilli() - start.toEpochMilli();\n        assertEquals(task.getStatus(), TaskModel.Status.FAILED);\n        assertTrue(diff >= 110L);\n    }\n\n    @Test\n    public void testHTTPGETReadTimeOut() {\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setReadTimeOut(-1);\n        input.setMethod(\"GET\");\n        input.setUri(\"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/json\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.setScheduledTime(0);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(task.getStatus(), TaskModel.Status.FAILED);\n    }\n\n    @Test\n    public void testOptional() {\n        TaskModel task = new TaskModel();\n        HttpTask.Input input = new HttpTask.Input();\n        input.setUri(\n                \"http://\" + mockServer.getHost() + \":\" + mockServer.getServerPort() + \"/failure\");\n        input.setMethod(\"GET\");\n        task.getInputData().put(HttpTask.REQUEST_PARAMETER_NAME, input);\n\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(\n                \"Task output: \" + task.getOutputData(), TaskModel.Status.FAILED, task.getStatus());\n        assertTrue(task.getReasonForIncompletion().contains(ERROR_RESPONSE));\n        assertFalse(task.getStatus().isSuccessful());\n\n        task.setStatus(TaskModel.Status.SCHEDULED);\n        task.getInputData().remove(HttpTask.REQUEST_PARAMETER_NAME);\n        task.setReferenceTaskName(\"t1\");\n        httpTask.start(workflow, task, workflowExecutor);\n        assertEquals(TaskModel.Status.FAILED, task.getStatus());\n        assertEquals(HttpTask.MISSING_REQUEST, task.getReasonForIncompletion());\n        assertFalse(task.getStatus().isSuccessful());\n\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setOptional(true);\n        workflowTask.setName(\"HTTP\");\n        workflowTask.setWorkflowTaskType(TaskType.USER_DEFINED);\n        workflowTask.setTaskReferenceName(\"t1\");\n\n        WorkflowDef def = new WorkflowDef();\n        def.getTasks().add(workflowTask);\n\n        WorkflowModel workflow = new WorkflowModel();\n        workflow.setWorkflowDefinition(def);\n        workflow.getTasks().add(task);\n\n        MetadataDAO metadataDAO = mock(MetadataDAO.class);\n        ExternalPayloadStorageUtils externalPayloadStorageUtils =\n                mock(ExternalPayloadStorageUtils.class);\n        ParametersUtils parametersUtils = mock(ParametersUtils.class);\n        SystemTaskRegistry systemTaskRegistry = mock(SystemTaskRegistry.class);\n\n        new DeciderService(\n                        new IDGenerator(),\n                        parametersUtils,\n                        metadataDAO,\n                        externalPayloadStorageUtils,\n                        systemTaskRegistry,\n                        Collections.emptyMap(),\n                        Duration.ofMinutes(60))\n                .decide(workflow);\n    }\n}\n"
  },
  {
    "path": "http-task/src/test/java/com/netflix/conductor/tasks/http/providers/DefaultRestTemplateProviderTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.tasks.http.providers;\n\nimport java.time.Duration;\n\nimport org.junit.Test;\nimport org.springframework.web.client.RestTemplate;\n\nimport com.netflix.conductor.tasks.http.HttpTask;\n\nimport static org.junit.Assert.*;\n\npublic class DefaultRestTemplateProviderTest {\n\n    @Test\n    public void differentObjectsForDifferentThreads() throws InterruptedException {\n        DefaultRestTemplateProvider defaultRestTemplateProvider =\n                new DefaultRestTemplateProvider(Duration.ofMillis(150), Duration.ofMillis(100));\n        final RestTemplate restTemplate =\n                defaultRestTemplateProvider.getRestTemplate(new HttpTask.Input());\n        final StringBuilder result = new StringBuilder();\n        Thread t1 =\n                new Thread(\n                        () -> {\n                            RestTemplate restTemplate1 =\n                                    defaultRestTemplateProvider.getRestTemplate(\n                                            new HttpTask.Input());\n                            if (restTemplate1 != restTemplate) {\n                                result.append(\"different\");\n                            }\n                        });\n        t1.start();\n        t1.join();\n        assertEquals(result.toString(), \"different\");\n    }\n\n    @Test\n    public void sameObjectForSameThread() {\n        DefaultRestTemplateProvider defaultRestTemplateProvider =\n                new DefaultRestTemplateProvider(Duration.ofMillis(150), Duration.ofMillis(100));\n        RestTemplate client1 = defaultRestTemplateProvider.getRestTemplate(new HttpTask.Input());\n        RestTemplate client2 = defaultRestTemplateProvider.getRestTemplate(new HttpTask.Input());\n        assertSame(client1, client2);\n        assertNotNull(client1);\n    }\n}\n"
  },
  {
    "path": "java-sdk/README.md",
    "content": "# SDK for Conductor\nConductor SDK allows developers to create, test and execute workflows using code.\n\nThere are three main features of the SDK: \n\n1. [Create and run workflows using code](workflow_sdk.md)\n2. [Create and run strongly typed workers](worker_sdk.md)\n3. [Unit Testing framework for workflows and workers](testing_framework.md)\n\n\n\n"
  },
  {
    "path": "java-sdk/build.gradle",
    "content": "apply plugin: 'groovy'\n\ndependencies {\n\n    implementation project(':conductor-common')\n    implementation project(':conductor-client')\n\n    implementation \"com.fasterxml.jackson.core:jackson-databind:${revFasterXml}\"\n    implementation \"com.google.guava:guava:${revGuava}\"\n    implementation \"cglib:cglib:3.3.0\"\n    implementation \"com.sun.jersey:jersey-client:${revJersey}\"\n    implementation \"javax.ws.rs:javax.ws.rs-api:${revJAXRS}\"\n    implementation \"org.glassfish.jersey.core:jersey-common:${revJerseyCommon}\"\n    implementation \"org.openjdk.nashorn:nashorn-core:15.4\"\n\n    testImplementation \"org.springframework:spring-web\"\n    testImplementation \"org.spockframework:spock-core:${revSpock}\"\n    testImplementation \"org.spockframework:spock-spring:${revSpock}\"\n\n    testImplementation \"com.fasterxml.jackson.core:jackson-core:${revFasterXml}\"\n    testImplementation \"org.apache.commons:commons-lang3\"\n\n    testImplementation \"org.codehaus.groovy:groovy-all:${revGroovy}\"\n\n}\n\ntest {\n    testLogging {\n        exceptionFormat = 'full'\n    }\n}\nsourceSets.main.java.srcDirs += ['example/java', 'example/resources']\n\n"
  },
  {
    "path": "java-sdk/example/java/com/netflix/conductor/sdk/example/shipment/Order.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.example.shipment;\n\nimport java.math.BigDecimal;\n\npublic class Order {\n\n    public enum ShippingMethod {\n        GROUND,\n        NEXT_DAY_AIR,\n        SAME_DAY\n    }\n\n    private String orderNumber;\n\n    private String sku;\n\n    private int quantity;\n\n    private BigDecimal unitPrice;\n\n    private String zipCode;\n\n    private String countryCode;\n\n    private ShippingMethod shippingMethod;\n\n    public Order(String orderNumber, String sku, int quantity, BigDecimal unitPrice) {\n        this.orderNumber = orderNumber;\n        this.sku = sku;\n        this.quantity = quantity;\n        this.unitPrice = unitPrice;\n    }\n\n    public Order() {}\n\n    public String getOrderNumber() {\n        return orderNumber;\n    }\n\n    public void setOrderNumber(String orderNumber) {\n        this.orderNumber = orderNumber;\n    }\n\n    public String getSku() {\n        return sku;\n    }\n\n    public void setSku(String sku) {\n        this.sku = sku;\n    }\n\n    public int getQuantity() {\n        return quantity;\n    }\n\n    public void setQuantity(int quantity) {\n        this.quantity = quantity;\n    }\n\n    public BigDecimal getUnitPrice() {\n        return unitPrice;\n    }\n\n    public void setUnitPrice(BigDecimal unitPrice) {\n        this.unitPrice = unitPrice;\n    }\n\n    public String getZipCode() {\n        return zipCode;\n    }\n\n    public void setZipCode(String zipCode) {\n        this.zipCode = zipCode;\n    }\n\n    public String getCountryCode() {\n        return countryCode;\n    }\n\n    public void setCountryCode(String countryCode) {\n        this.countryCode = countryCode;\n    }\n\n    public ShippingMethod getShippingMethod() {\n        return shippingMethod;\n    }\n\n    public void setShippingMethod(ShippingMethod shippingMethod) {\n        this.shippingMethod = shippingMethod;\n    }\n}\n"
  },
  {
    "path": "java-sdk/example/java/com/netflix/conductor/sdk/example/shipment/Shipment.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.example.shipment;\n\npublic class Shipment {\n\n    private String userId;\n\n    private String orderNo;\n\n    public Shipment(String userId, String orderNo) {\n        this.userId = userId;\n        this.orderNo = orderNo;\n    }\n\n    public Shipment() {}\n\n    public String getUserId() {\n        return userId;\n    }\n\n    public void setUserId(String userId) {\n        this.userId = userId;\n    }\n\n    public String getOrderNo() {\n        return orderNo;\n    }\n\n    public void setOrderNo(String orderNo) {\n        this.orderNo = orderNo;\n    }\n}\n"
  },
  {
    "path": "java-sdk/example/java/com/netflix/conductor/sdk/example/shipment/ShipmentState.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.example.shipment;\n\npublic class ShipmentState {\n\n    private boolean paymentCompleted;\n\n    private boolean emailSent;\n\n    private boolean shipped;\n\n    private String trackingNumber;\n\n    public boolean isPaymentCompleted() {\n        return paymentCompleted;\n    }\n\n    public void setPaymentCompleted(boolean paymentCompleted) {\n        this.paymentCompleted = paymentCompleted;\n    }\n\n    public boolean isEmailSent() {\n        return emailSent;\n    }\n\n    public void setEmailSent(boolean emailSent) {\n        this.emailSent = emailSent;\n    }\n\n    public boolean isShipped() {\n        return shipped;\n    }\n\n    public void setShipped(boolean shipped) {\n        this.shipped = shipped;\n    }\n\n    public String getTrackingNumber() {\n        return trackingNumber;\n    }\n\n    public void setTrackingNumber(String trackingNumber) {\n        this.trackingNumber = trackingNumber;\n    }\n}\n"
  },
  {
    "path": "java-sdk/example/java/com/netflix/conductor/sdk/example/shipment/ShipmentWorkers.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.example.shipment;\n\nimport java.math.BigDecimal;\nimport java.util.*;\n\nimport com.netflix.conductor.sdk.workflow.def.tasks.DynamicForkInput;\nimport com.netflix.conductor.sdk.workflow.def.tasks.SubWorkflow;\nimport com.netflix.conductor.sdk.workflow.def.tasks.Task;\nimport com.netflix.conductor.sdk.workflow.task.InputParam;\nimport com.netflix.conductor.sdk.workflow.task.OutputParam;\nimport com.netflix.conductor.sdk.workflow.task.WorkerTask;\n\npublic class ShipmentWorkers {\n\n    @WorkerTask(value = \"generateDynamicFork\", threadCount = 3)\n    public DynamicForkInput generateDynamicFork(\n            @InputParam(\"orderDetails\") List<Order> orderDetails,\n            @InputParam(\"userDetails\") User userDetails) {\n        DynamicForkInput input = new DynamicForkInput();\n        List<Task<?>> tasks = new ArrayList<>();\n        Map<String, Object> inputs = new HashMap<>();\n\n        for (int i = 0; i < orderDetails.size(); i++) {\n            Order detail = orderDetails.get(i);\n            String referenceName = \"order_flow_sub_\" + i;\n            tasks.add(\n                    new SubWorkflow(referenceName, \"order_flow\", null)\n                            .input(\"orderDetail\", detail)\n                            .input(\"userDetails\", userDetails));\n            inputs.put(referenceName, new HashMap<>());\n        }\n        input.setInputs(inputs);\n        input.setTasks(tasks);\n        return input;\n    }\n\n    @WorkerTask(value = \"get_order_details\", threadCount = 5)\n    public List<Order> getOrderDetails(@InputParam(\"orderNo\") String orderNo) {\n        int lineItemCount = new Random().nextInt(10);\n        List<Order> orderDetails = new ArrayList<>();\n        for (int i = 0; i < lineItemCount; i++) {\n            Order orderDetail = new Order(orderNo, \"sku_\" + i, 2, BigDecimal.valueOf(20.5));\n            orderDetail.setOrderNumber(UUID.randomUUID().toString());\n            orderDetail.setCountryCode(i % 2 == 0 ? \"US\" : \"CA\");\n            if (i % 3 == 0) {\n                orderDetail.setCountryCode(\"UK\");\n            }\n\n            if (orderDetail.getCountryCode().equals(\"US\"))\n                orderDetail.setShippingMethod(Order.ShippingMethod.SAME_DAY);\n            else if (orderDetail.getCountryCode().equals(\"CA\"))\n                orderDetail.setShippingMethod(Order.ShippingMethod.NEXT_DAY_AIR);\n            else orderDetail.setShippingMethod(Order.ShippingMethod.GROUND);\n\n            orderDetails.add(orderDetail);\n        }\n        return orderDetails;\n    }\n\n    @WorkerTask(\"get_user_details\")\n    public User getUserDetails(@InputParam(\"userId\") String userId) {\n        User user =\n                new User(\n                        \"User Name\",\n                        userId + \"@example.com\",\n                        \"1234 forline street\",\n                        \"mountain view\",\n                        \"95030\",\n                        \"US\",\n                        \"Paypal\",\n                        \"biling_001\");\n\n        return user;\n    }\n\n    @WorkerTask(\"calculate_tax_and_total\")\n    public @OutputParam(\"total_amount\") BigDecimal calculateTax(\n            @InputParam(\"orderDetail\") Order orderDetails) {\n        BigDecimal preTaxAmount =\n                orderDetails.getUnitPrice().multiply(new BigDecimal(orderDetails.getQuantity()));\n        BigDecimal tax = BigDecimal.valueOf(0.2).multiply(preTaxAmount);\n        if (!\"US\".equals(orderDetails.getCountryCode())) {\n            tax = BigDecimal.ZERO;\n        }\n        return preTaxAmount.add(tax);\n    }\n\n    @WorkerTask(\"ground_shipping_label\")\n    public @OutputParam(\"reference_number\") String prepareGroundShipping(\n            @InputParam(\"name\") String name,\n            @InputParam(\"address\") String address,\n            @InputParam(\"orderNo\") String orderNo) {\n\n        return \"Ground_\" + orderNo;\n    }\n\n    @WorkerTask(\"air_shipping_label\")\n    public @OutputParam(\"reference_number\") String prepareAirShipping(\n            @InputParam(\"name\") String name,\n            @InputParam(\"address\") String address,\n            @InputParam(\"orderNo\") String orderNo) {\n\n        return \"Air_\" + orderNo;\n    }\n\n    @WorkerTask(\"same_day_shipping_label\")\n    public @OutputParam(\"reference_number\") String prepareSameDayShipping(\n            @InputParam(\"name\") String name,\n            @InputParam(\"address\") String address,\n            @InputParam(\"orderNo\") String orderNo) {\n\n        return \"SameDay_\" + orderNo;\n    }\n\n    @WorkerTask(\"charge_payment\")\n    public @OutputParam(\"reference\") String chargePayment(\n            @InputParam(\"amount\") BigDecimal amount,\n            @InputParam(\"billingId\") String billingId,\n            @InputParam(\"billingType\") String billingType) {\n\n        return UUID.randomUUID().toString();\n    }\n\n    @WorkerTask(\"send_email\")\n    public void sendEmail(\n            @InputParam(\"name\") String name,\n            @InputParam(\"email\") String email,\n            @InputParam(\"orderNo\") String orderNo) {}\n}\n"
  },
  {
    "path": "java-sdk/example/java/com/netflix/conductor/sdk/example/shipment/ShipmentWorkflow.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.example.shipment;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.sdk.workflow.def.ConductorWorkflow;\nimport com.netflix.conductor.sdk.workflow.def.WorkflowBuilder;\nimport com.netflix.conductor.sdk.workflow.def.tasks.*;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\n\npublic class ShipmentWorkflow {\n\n    private final WorkflowExecutor executor;\n\n    public ShipmentWorkflow(WorkflowExecutor executor) {\n        this.executor = executor;\n        this.executor.initWorkers(ShipmentWorkflow.class.getPackageName());\n    }\n\n    public ConductorWorkflow<Order> createOrderFlow() {\n        WorkflowBuilder<Order> builder = new WorkflowBuilder<>(executor);\n        builder.name(\"order_flow\")\n                .version(1)\n                .ownerEmail(\"user@example.com\")\n                .timeoutPolicy(WorkflowDef.TimeoutPolicy.TIME_OUT_WF, 60) // 1 day max\n                .description(\"Workflow to track shipment\")\n                .add(\n                        new SimpleTask(\"calculate_tax_and_total\", \"calculate_tax_and_total\")\n                                .input(\"orderDetail\", ConductorWorkflow.input.get(\"orderDetail\")))\n                .add(\n                        new SimpleTask(\"charge_payment\", \"charge_payment\")\n                                .input(\n                                        \"billingId\",\n                                                ConductorWorkflow.input\n                                                        .map(\"userDetails\")\n                                                        .get(\"billingId\"),\n                                        \"billingType\",\n                                                ConductorWorkflow.input\n                                                        .map(\"userDetails\")\n                                                        .get(\"billingType\"),\n                                        \"amount\", \"${calculate_tax_and_total.output.total_amount}\"))\n                .add(\n                        new Switch(\"shipping_label\", \"${workflow.input.orderDetail.shippingMethod}\")\n                                .switchCase(\n                                        Order.ShippingMethod.GROUND.toString(),\n                                        new SimpleTask(\n                                                        \"ground_shipping_label\",\n                                                        \"ground_shipping_label\")\n                                                .input(\n                                                        \"name\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"userDetails\")\n                                                                        .get(\"name\"),\n                                                        \"address\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"userDetails\")\n                                                                        .get(\"addressLine\"),\n                                                        \"orderNo\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"orderDetail\")\n                                                                        .get(\"orderNumber\")))\n                                .switchCase(\n                                        Order.ShippingMethod.NEXT_DAY_AIR.toString(),\n                                        new SimpleTask(\"air_shipping_label\", \"air_shipping_label\")\n                                                .input(\n                                                        \"name\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"userDetails\")\n                                                                        .get(\"name\"),\n                                                        \"address\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"userDetails\")\n                                                                        .get(\"addressLine\"),\n                                                        \"orderNo\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"orderDetail\")\n                                                                        .get(\"orderNumber\")))\n                                .switchCase(\n                                        Order.ShippingMethod.SAME_DAY.toString(),\n                                        new SimpleTask(\n                                                        \"same_day_shipping_label\",\n                                                        \"same_day_shipping_label\")\n                                                .input(\n                                                        \"name\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"userDetails\")\n                                                                        .get(\"name\"),\n                                                        \"address\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"userDetails\")\n                                                                        .get(\"addressLine\"),\n                                                        \"orderNo\",\n                                                                ConductorWorkflow.input\n                                                                        .map(\"orderDetail\")\n                                                                        .get(\"orderNumber\")))\n                                .defaultCase(\n                                        new Terminate(\n                                                \"unsupported_shipping_type\",\n                                                Workflow.WorkflowStatus.FAILED,\n                                                \"Unsupported Shipping Method\")))\n                .add(\n                        new SimpleTask(\"send_email\", \"send_email\")\n                                .input(\n                                        \"name\",\n                                                ConductorWorkflow.input\n                                                        .map(\"userDetails\")\n                                                        .get(\"name\"),\n                                        \"email\",\n                                                ConductorWorkflow.input\n                                                        .map(\"userDetails\")\n                                                        .get(\"email\"),\n                                        \"orderNo\",\n                                                ConductorWorkflow.input\n                                                        .map(\"orderDetail\")\n                                                        .get(\"orderNumber\")));\n        ConductorWorkflow<Order> conductorWorkflow = builder.build();\n        conductorWorkflow.registerWorkflow(true, true);\n        return conductorWorkflow;\n    }\n\n    public ConductorWorkflow<Shipment> createShipmentWorkflow() {\n\n        WorkflowBuilder<Shipment> builder = new WorkflowBuilder<>(executor);\n\n        SimpleTask getOrderDetails =\n                new SimpleTask(\"get_order_details\", \"get_order_details\")\n                        .input(\"orderNo\", ConductorWorkflow.input.get(\"orderNo\"));\n\n        SimpleTask getUserDetails =\n                new SimpleTask(\"get_user_details\", \"get_user_details\")\n                        .input(\"userId\", ConductorWorkflow.input.get(\"userId\"));\n\n        ConductorWorkflow<Shipment> conductorWorkflow =\n                builder.name(\"shipment_workflow\")\n                        .version(1)\n                        .ownerEmail(\"user@example.com\")\n                        .variables(new ShipmentState())\n                        .timeoutPolicy(WorkflowDef.TimeoutPolicy.TIME_OUT_WF, 60) // 30 days\n                        .description(\"Workflow to track shipment\")\n                        .add(\n                                new ForkJoin(\n                                        \"get_in_parallel\",\n                                        new Task[] {getOrderDetails},\n                                        new Task[] {getUserDetails}))\n\n                        // For all the line items in the order, run in parallel:\n                        // (calculate tax, charge payment, set state, prepare shipment, send\n                        // shipment, set state)\n                        .add(\n                                new DynamicFork(\n                                        \"process_order\",\n                                        new SimpleTask(\"generateDynamicFork\", \"generateDynamicFork\")\n                                                .input(\n                                                        \"orderDetails\",\n                                                        getOrderDetails.taskOutput.get(\"result\"))\n                                                .input(\"userDetails\", getUserDetails.taskOutput)))\n\n                        // Update the workflow state with shipped = true\n                        .add(new SetVariable(\"update_state\").input(\"shipped\", true))\n                        .build();\n\n        conductorWorkflow.registerWorkflow(true, true);\n\n        return conductorWorkflow;\n    }\n\n    public static void main(String[] args) {\n\n        String conductorServerURL =\n                \"http://localhost:8080/api/\"; // Change this to your Conductor server\n        WorkflowExecutor executor = new WorkflowExecutor(conductorServerURL);\n\n        // Create the new shipment workflow\n        ShipmentWorkflow shipmentWorkflow = new ShipmentWorkflow(executor);\n\n        // Create two workflows\n\n        // 1. Order flow that ships an individual order\n        // 2. Shipment Workflow that tracks multiple orders in a shipment\n        shipmentWorkflow.createOrderFlow();\n        ConductorWorkflow<Shipment> workflow = shipmentWorkflow.createShipmentWorkflow();\n\n        // Execute the workflow and wait for it to complete\n        try {\n            Shipment workflowInput = new Shipment(\"userA\", \"order123\");\n\n            // Execute returns a completable future.\n            CompletableFuture<Workflow> executionFuture = workflow.execute(workflowInput);\n\n            // Wait for a maximum of a minute for the workflow to complete.\n            Workflow run = executionFuture.get(1, TimeUnit.MINUTES);\n\n            System.out.println(\"Workflow Id: \" + run);\n            System.out.println(\"Workflow Status: \" + run.getStatus());\n            System.out.println(\"Workflow Output: \" + run.getOutput());\n\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            System.exit(0);\n        }\n\n        System.out.println(\"Done\");\n    }\n}\n"
  },
  {
    "path": "java-sdk/example/java/com/netflix/conductor/sdk/example/shipment/User.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.example.shipment;\n\npublic class User {\n\n    private String name;\n\n    private String email;\n\n    private String addressLine;\n\n    private String city;\n\n    private String zipCode;\n\n    private String countryCode;\n\n    private String billingType;\n\n    private String billingId;\n\n    public User(\n            String name,\n            String email,\n            String addressLine,\n            String city,\n            String zipCode,\n            String countryCode,\n            String billingType,\n            String billingId) {\n        this.name = name;\n        this.email = email;\n        this.addressLine = addressLine;\n        this.city = city;\n        this.zipCode = zipCode;\n        this.countryCode = countryCode;\n        this.billingType = billingType;\n        this.billingId = billingId;\n    }\n\n    public User() {}\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n\n    public String getAddressLine() {\n        return addressLine;\n    }\n\n    public void setAddressLine(String addressLine) {\n        this.addressLine = addressLine;\n    }\n\n    public String getCity() {\n        return city;\n    }\n\n    public void setCity(String city) {\n        this.city = city;\n    }\n\n    public String getZipCode() {\n        return zipCode;\n    }\n\n    public void setZipCode(String zipCode) {\n        this.zipCode = zipCode;\n    }\n\n    public String getCountryCode() {\n        return countryCode;\n    }\n\n    public void setCountryCode(String countryCode) {\n        this.countryCode = countryCode;\n    }\n\n    public String getBillingType() {\n        return billingType;\n    }\n\n    public void setBillingType(String billingType) {\n        this.billingType = billingType;\n    }\n\n    public String getBillingId() {\n        return billingId;\n    }\n\n    public void setBillingId(String billingId) {\n        this.billingId = billingId;\n    }\n}\n"
  },
  {
    "path": "java-sdk/example/resources/script.js",
    "content": "function e() {\n    if ($.value > 1){\n        return {\n            \"key\": \"value\",\n            \"key2\": 42\n        };\n    } else {\n        return {};\n    }\n}\ne();"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/healthcheck/HealthCheckClient.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.healthcheck;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.net.URL;\n\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic class HealthCheckClient {\n\n    private final String healthCheckURL;\n\n    private final ObjectMapper objectMapper;\n\n    public HealthCheckClient(String healthCheckURL) {\n        this.healthCheckURL = healthCheckURL;\n        this.objectMapper =\n                new ObjectMapper()\n                        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\n    }\n\n    public boolean isServerRunning() {\n        try {\n\n            BufferedReader in =\n                    new BufferedReader(new InputStreamReader(new URL(healthCheckURL).openStream()));\n            StringBuilder response = new StringBuilder();\n            String inputLine;\n            while ((inputLine = in.readLine()) != null) {\n                response.append(inputLine);\n            }\n            in.close();\n            HealthCheckResults healthCheckResults =\n                    objectMapper.readValue(response.toString(), HealthCheckResults.class);\n            return healthCheckResults.healthy;\n        } catch (Throwable t) {\n            return false;\n        }\n    }\n\n    private static final class HealthCheckResults {\n\n        private boolean healthy;\n\n        public boolean isHealthy() {\n            return healthy;\n        }\n\n        public void setHealthy(boolean healthy) {\n            this.healthy = healthy;\n        }\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/testing/LocalServerRunner.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.testing;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.sdk.healthcheck.HealthCheckClient;\n\nimport com.google.common.util.concurrent.Uninterruptibles;\n\npublic class LocalServerRunner {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(LocalServerRunner.class);\n\n    private final HealthCheckClient healthCheck;\n\n    private Process serverProcess;\n\n    private final ScheduledExecutorService healthCheckExecutor =\n            Executors.newSingleThreadScheduledExecutor();\n\n    private final CountDownLatch serverProcessLatch = new CountDownLatch(1);\n\n    private final int port;\n\n    private final String conductorVersion;\n\n    private final String serverURL;\n\n    private static Map<Integer, LocalServerRunner> serverInstances = new HashMap<>();\n\n    public LocalServerRunner(int port, String conductorVersion) {\n        this.port = port;\n        this.conductorVersion = conductorVersion;\n        this.serverURL = \"http://localhost:\" + port + \"/\";\n        healthCheck = new HealthCheckClient(serverURL + \"health\");\n    }\n\n    public String getServerAPIUrl() {\n        return this.serverURL + \"api/\";\n    }\n\n    /**\n     * Starts the local server. Downloads the latest conductor build from the maven repo If you want\n     * to start the server from a specific download location, set `repositoryURL` system property\n     * with the link to the actual downloadable server boot jar file.\n     *\n     * <p><b>System Properties that can be set</b> conductorVersion: when specified, uses this\n     * version of conductor to run tests (and downloads from maven repo) repositoryURL: full url\n     * where the server boot jar can be downloaded from. This can be a public repo or internal\n     * repository, allowing full control over the location and version of the conductor server\n     */\n    public void startLocalServer() {\n        synchronized (serverInstances) {\n            if (serverInstances.get(port) != null) {\n                throw new IllegalStateException(\n                        \"Another server has already been started at port \" + port);\n            }\n            serverInstances.put(port, this);\n        }\n\n        try {\n            String downloadURL =\n                    \"https://repo1.maven.org/maven2/com/netflix/conductor/conductor-server/\"\n                            + conductorVersion\n                            + \"/conductor-server-\"\n                            + conductorVersion\n                            + \"-boot.jar\";\n\n            String repositoryURL =\n                    Optional.ofNullable(System.getProperty(\"repositoryURL\")).orElse(downloadURL);\n\n            LOGGER.info(\n                    \"Running conductor with version {} from repo url {}\",\n                    conductorVersion,\n                    repositoryURL);\n\n            Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));\n            installAndStartServer(repositoryURL, port);\n            healthCheckExecutor.scheduleAtFixedRate(\n                    () -> {\n                        try {\n                            if (serverProcessLatch.getCount() > 0) {\n                                boolean isRunning = healthCheck.isServerRunning();\n                                if (isRunning) {\n                                    serverProcessLatch.countDown();\n                                }\n                            }\n                        } catch (Exception e) {\n                            LOGGER.warn(\n                                    \"Caught an exception while polling for server running status {}\",\n                                    e.getMessage());\n                        }\n                    },\n                    100,\n                    100,\n                    TimeUnit.MILLISECONDS);\n            Uninterruptibles.awaitUninterruptibly(serverProcessLatch, 1, TimeUnit.MINUTES);\n\n            if (serverProcessLatch.getCount() > 0) {\n                throw new RuntimeException(\"Server not healthy\");\n            }\n            healthCheckExecutor.shutdownNow();\n\n        } catch (IOException e) {\n            throw new Error(e);\n        }\n    }\n\n    public void shutdown() {\n        if (serverProcess != null) {\n            serverProcess.destroyForcibly();\n            serverInstances.remove(port);\n        }\n    }\n\n    private synchronized void installAndStartServer(String repositoryURL, int localServerPort)\n            throws IOException {\n        if (serverProcess != null) {\n            return;\n        }\n\n        String configFile =\n                LocalServerRunner.class.getResource(\"/test-server.properties\").getFile();\n        String tempDir = System.getProperty(\"java.io.tmpdir\");\n        Path serverFile = Paths.get(tempDir, \"conductor-server.jar\");\n        if (!Files.exists(serverFile)) {\n            Files.copy(new URL(repositoryURL).openStream(), serverFile);\n        }\n\n        String command =\n                \"java -Dserver.port=\"\n                        + localServerPort\n                        + \" -DCONDUCTOR_CONFIG_FILE=\"\n                        + configFile\n                        + \" -jar \"\n                        + serverFile;\n        LOGGER.info(\"Running command {}\", command);\n\n        serverProcess = Runtime.getRuntime().exec(command);\n        BufferedReader error =\n                new BufferedReader(new InputStreamReader(serverProcess.getErrorStream()));\n        BufferedReader op =\n                new BufferedReader(new InputStreamReader(serverProcess.getInputStream()));\n\n        // This captures the stream and copies to a visible log for tracking errors asynchronously\n        // using a separate thread\n        Executors.newSingleThreadScheduledExecutor()\n                .execute(\n                        () -> {\n                            String line = null;\n                            while (true) {\n                                try {\n                                    if ((line = error.readLine()) == null) break;\n                                } catch (IOException e) {\n                                    LOGGER.error(\"Exception reading input stream:\", e);\n                                }\n                                // copy to standard error\n                                LOGGER.error(\"Server error stream - {}\", line);\n                            }\n                        });\n\n        // This captures the stream and copies to a visible log for tracking errors asynchronously\n        // using a separate thread\n        Executors.newSingleThreadScheduledExecutor()\n                .execute(\n                        () -> {\n                            String line = null;\n                            while (true) {\n                                try {\n                                    if ((line = op.readLine()) == null) break;\n                                } catch (IOException e) {\n                                    LOGGER.error(\"Exception reading input stream:\", e);\n                                }\n                                // copy to standard out\n                                LOGGER.trace(\"Server input stream - {}\", line);\n                            }\n                        });\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/testing/WorkflowTestRunner.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.testing;\n\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\nimport com.netflix.conductor.sdk.workflow.executor.task.AnnotatedWorkerExecutor;\n\npublic class WorkflowTestRunner {\n\n    private LocalServerRunner localServerRunner;\n\n    private final AnnotatedWorkerExecutor annotatedWorkerExecutor;\n\n    private final WorkflowExecutor workflowExecutor;\n\n    public WorkflowTestRunner(String serverApiUrl) {\n\n        TaskClient taskClient = new TaskClient();\n        taskClient.setRootURI(serverApiUrl);\n        this.annotatedWorkerExecutor = new AnnotatedWorkerExecutor(taskClient);\n\n        this.workflowExecutor = new WorkflowExecutor(serverApiUrl);\n    }\n\n    public WorkflowTestRunner(int port, String conductorVersion) {\n\n        localServerRunner = new LocalServerRunner(port, conductorVersion);\n\n        TaskClient taskClient = new TaskClient();\n        taskClient.setRootURI(localServerRunner.getServerAPIUrl());\n        this.annotatedWorkerExecutor = new AnnotatedWorkerExecutor(taskClient);\n\n        this.workflowExecutor = new WorkflowExecutor(localServerRunner.getServerAPIUrl());\n    }\n\n    public WorkflowExecutor getWorkflowExecutor() {\n        return workflowExecutor;\n    }\n\n    public void init(String basePackages) {\n        if (localServerRunner != null) {\n            localServerRunner.startLocalServer();\n        }\n        annotatedWorkerExecutor.initWorkers(basePackages);\n    }\n\n    public void shutdown() {\n        localServerRunner.shutdown();\n        annotatedWorkerExecutor.shutdown();\n        workflowExecutor.shutdown();\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/ConductorWorkflow.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def;\n\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\n\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.sdk.workflow.def.tasks.Task;\nimport com.netflix.conductor.sdk.workflow.def.tasks.TaskRegistry;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\nimport com.netflix.conductor.sdk.workflow.utils.InputOutputGetter;\nimport com.netflix.conductor.sdk.workflow.utils.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/**\n * @param <T> Type of the workflow input\n */\npublic class ConductorWorkflow<T> {\n\n    public static final InputOutputGetter input =\n            new InputOutputGetter(\"workflow\", InputOutputGetter.Field.input);\n\n    public static final InputOutputGetter output =\n            new InputOutputGetter(\"workflow\", InputOutputGetter.Field.output);\n\n    private String name;\n\n    private String description;\n\n    private int version;\n\n    private String failureWorkflow;\n\n    private String ownerEmail;\n\n    private WorkflowDef.TimeoutPolicy timeoutPolicy;\n\n    private Map<String, Object> workflowOutput;\n\n    private long timeoutSeconds;\n\n    private boolean restartable = true;\n\n    private T defaultInput;\n\n    private Map<String, Object> variables;\n\n    private List<Task> tasks = new ArrayList<>();\n\n    private final ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    private final WorkflowExecutor workflowExecutor;\n\n    public ConductorWorkflow(WorkflowExecutor workflowExecutor) {\n        this.workflowOutput = new HashMap<>();\n        this.workflowExecutor = workflowExecutor;\n        this.restartable = true;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public void setVersion(int version) {\n        this.version = version;\n    }\n\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    public void setFailureWorkflow(String failureWorkflow) {\n        this.failureWorkflow = failureWorkflow;\n    }\n\n    public void add(Task task) {\n        this.tasks.add(task);\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public int getVersion() {\n        return version;\n    }\n\n    public String getFailureWorkflow() {\n        return failureWorkflow;\n    }\n\n    public String getOwnerEmail() {\n        return ownerEmail;\n    }\n\n    public void setOwnerEmail(String ownerEmail) {\n        this.ownerEmail = ownerEmail;\n    }\n\n    public WorkflowDef.TimeoutPolicy getTimeoutPolicy() {\n        return timeoutPolicy;\n    }\n\n    public void setTimeoutPolicy(WorkflowDef.TimeoutPolicy timeoutPolicy) {\n        this.timeoutPolicy = timeoutPolicy;\n    }\n\n    public long getTimeoutSeconds() {\n        return timeoutSeconds;\n    }\n\n    public void setTimeoutSeconds(long timeoutSeconds) {\n        this.timeoutSeconds = timeoutSeconds;\n    }\n\n    public boolean isRestartable() {\n        return restartable;\n    }\n\n    public void setRestartable(boolean restartable) {\n        this.restartable = restartable;\n    }\n\n    public T getDefaultInput() {\n        return defaultInput;\n    }\n\n    public void setDefaultInput(T defaultInput) {\n        this.defaultInput = defaultInput;\n    }\n\n    public Map<String, Object> getWorkflowOutput() {\n        return workflowOutput;\n    }\n\n    public void setWorkflowOutput(Map<String, Object> workflowOutput) {\n        this.workflowOutput = workflowOutput;\n    }\n\n    public Object getVariables() {\n        return variables;\n    }\n\n    public void setVariables(Map<String, Object> variables) {\n        this.variables = variables;\n    }\n\n    /**\n     * Execute a dynamic workflow without creating a definition in metadata store.\n     *\n     * <p><br>\n     * <b>Note</b>: Use this with caution - as this does not promote re-usability of the workflows\n     *\n     * @param input Workflow Input - The input object is converted a JSON doc as an input to the\n     *     workflow\n     * @return\n     */\n    public CompletableFuture<Workflow> executeDynamic(T input) {\n        return workflowExecutor.executeWorkflow(this, input);\n    }\n\n    /**\n     * Executes the workflow using registered metadata definitions\n     *\n     * @see #registerWorkflow()\n     * @param input\n     * @return\n     */\n    public CompletableFuture<Workflow> execute(T input) {\n        return workflowExecutor.executeWorkflow(this.getName(), this.getVersion(), input);\n    }\n\n    /**\n     * Registers a new workflow in the server.\n     *\n     * @return true if the workflow is successfully registered. False if the workflow cannot be\n     *     registered and the workflow definition already exists on the server with given name +\n     *     version The call will throw a runtime exception if any of the tasks are missing\n     *     definitions on the server.\n     */\n    public boolean registerWorkflow() {\n        return registerWorkflow(false, false);\n    }\n\n    /**\n     * @param overwrite set to true if the workflow should be overwritten if the definition already\n     *     exists with the given name and version. <font color=red>Use with caution</font>\n     * @return true if success, false otherwise.\n     */\n    public boolean registerWorkflow(boolean overwrite) {\n        return registerWorkflow(overwrite, false);\n    }\n\n    /**\n     * @param overwrite set to true if the workflow should be overwritten if the definition already\n     *     exists with the given name and version. <font color=red>Use with caution</font>\n     * @param registerTasks if set to true, missing task definitions are registered with the default\n     *     configuration.\n     * @return true if success, false otherwise.\n     */\n    public boolean registerWorkflow(boolean overwrite, boolean registerTasks) {\n        WorkflowDef workflowDef = toWorkflowDef();\n        List<String> missing = getMissingTasks(workflowDef);\n        if (!missing.isEmpty()) {\n            if (!registerTasks) {\n                throw new RuntimeException(\n                        \"Workflow cannot be registered.  The following tasks do not have definitions.  \"\n                                + \"Please register these tasks before creating the workflow.  Missing Tasks = \"\n                                + missing);\n            } else {\n                String ownerEmail = this.ownerEmail;\n                missing.stream().forEach(taskName -> registerTaskDef(taskName, ownerEmail));\n            }\n        }\n        return workflowExecutor.registerWorkflow(workflowDef, overwrite);\n    }\n\n    /**\n     * @return Convert to the WorkflowDef model used by the Metadata APIs\n     */\n    public WorkflowDef toWorkflowDef() {\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(name);\n        def.setDescription(description);\n        def.setVersion(version);\n        def.setFailureWorkflow(failureWorkflow);\n        def.setOwnerEmail(ownerEmail);\n        def.setTimeoutPolicy(timeoutPolicy);\n        def.setTimeoutSeconds(timeoutSeconds);\n        def.setRestartable(restartable);\n        def.setOutputParameters(workflowOutput);\n        def.setVariables(variables);\n        def.setInputTemplate(objectMapper.convertValue(defaultInput, Map.class));\n\n        for (Task task : tasks) {\n            def.getTasks().addAll(task.getWorkflowDefTasks());\n        }\n        return def;\n    }\n\n    /**\n     * Generate ConductorWorkflow based on the workflow metadata definition\n     *\n     * @param def\n     * @return\n     */\n    public static <T> ConductorWorkflow<T> fromWorkflowDef(WorkflowDef def) {\n        ConductorWorkflow<T> workflow = new ConductorWorkflow<>(null);\n        fromWorkflowDef(workflow, def);\n        return workflow;\n    }\n\n    public ConductorWorkflow<T> from(String workflowName, Integer workflowVersion) {\n        WorkflowDef def =\n                workflowExecutor.getMetadataClient().getWorkflowDef(workflowName, workflowVersion);\n        fromWorkflowDef(this, def);\n        return this;\n    }\n\n    private static <T> void fromWorkflowDef(ConductorWorkflow<T> workflow, WorkflowDef def) {\n        workflow.setName(def.getName());\n        workflow.setVersion(def.getVersion());\n        workflow.setFailureWorkflow(def.getFailureWorkflow());\n        workflow.setRestartable(def.isRestartable());\n        workflow.setVariables(def.getVariables());\n        workflow.setDefaultInput((T) def.getInputTemplate());\n\n        workflow.setWorkflowOutput(def.getOutputParameters());\n        workflow.setOwnerEmail(def.getOwnerEmail());\n        workflow.setDescription(def.getDescription());\n        workflow.setTimeoutSeconds(def.getTimeoutSeconds());\n        workflow.setTimeoutPolicy(def.getTimeoutPolicy());\n\n        List<WorkflowTask> workflowTasks = def.getTasks();\n        for (WorkflowTask workflowTask : workflowTasks) {\n            Task task = TaskRegistry.getTask(workflowTask);\n            workflow.tasks.add(task);\n        }\n    }\n\n    private List<String> getMissingTasks(WorkflowDef workflowDef) {\n        List<String> missing = new ArrayList<>();\n        workflowDef.collectTasks().stream()\n                .filter(workflowTask -> workflowTask.getType().equals(TaskType.TASK_TYPE_SIMPLE))\n                .map(WorkflowTask::getName)\n                .distinct()\n                .parallel()\n                .forEach(\n                        taskName -> {\n                            try {\n                                TaskDef taskDef =\n                                        workflowExecutor.getMetadataClient().getTaskDef(taskName);\n                            } catch (ConductorClientException cce) {\n                                if (cce.getStatus() == 404) {\n                                    missing.add(taskName);\n                                } else {\n                                    throw cce;\n                                }\n                            }\n                        });\n        return missing;\n    }\n\n    private void registerTaskDef(String taskName, String ownerEmail) {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(taskName);\n        taskDef.setOwnerEmail(ownerEmail);\n        workflowExecutor.getMetadataClient().registerTaskDefs(Arrays.asList(taskDef));\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        ConductorWorkflow workflow = (ConductorWorkflow) o;\n        return version == workflow.version && Objects.equals(name, workflow.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(name, version);\n    }\n\n    @Override\n    public String toString() {\n        try {\n            return objectMapper.writeValueAsString(toWorkflowDef());\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/ValidationError.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def;\n\npublic class ValidationError extends RuntimeException {\n\n    public ValidationError(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/WorkflowBuilder.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def;\n\nimport java.util.*;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.def.tasks.*;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\nimport com.netflix.conductor.sdk.workflow.utils.InputOutputGetter;\nimport com.netflix.conductor.sdk.workflow.utils.MapBuilder;\nimport com.netflix.conductor.sdk.workflow.utils.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/**\n * @param <T> Input type for the workflow\n */\npublic class WorkflowBuilder<T> {\n\n    private String name;\n\n    private String description;\n\n    private int version;\n\n    private String failureWorkflow;\n\n    private String ownerEmail;\n\n    private WorkflowDef.TimeoutPolicy timeoutPolicy;\n\n    private long timeoutSeconds;\n\n    private boolean restartable = true;\n\n    private T defaultInput;\n\n    private Map<String, Object> output = new HashMap<>();\n\n    private Map<String, Object> state;\n\n    protected List<Task<?>> tasks = new ArrayList<>();\n\n    private WorkflowExecutor workflowExecutor;\n\n    public final InputOutputGetter input =\n            new InputOutputGetter(\"workflow\", InputOutputGetter.Field.input);\n\n    private final ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    public WorkflowBuilder(WorkflowExecutor workflowExecutor) {\n        this.workflowExecutor = workflowExecutor;\n        this.tasks = new ArrayList<>();\n    }\n\n    public WorkflowBuilder<T> name(String name) {\n        this.name = name;\n        return this;\n    }\n\n    public WorkflowBuilder<T> version(int version) {\n        this.version = version;\n        return this;\n    }\n\n    public WorkflowBuilder<T> description(String description) {\n        this.description = description;\n        return this;\n    }\n\n    public WorkflowBuilder<T> failureWorkflow(String failureWorkflow) {\n        this.failureWorkflow = failureWorkflow;\n        return this;\n    }\n\n    public WorkflowBuilder<T> ownerEmail(String ownerEmail) {\n        this.ownerEmail = ownerEmail;\n        return this;\n    }\n\n    public WorkflowBuilder<T> timeoutPolicy(\n            WorkflowDef.TimeoutPolicy timeoutPolicy, long timeoutSeconds) {\n        this.timeoutPolicy = timeoutPolicy;\n        this.timeoutSeconds = timeoutSeconds;\n        return this;\n    }\n\n    public WorkflowBuilder<T> add(Task<?>... tasks) {\n        Collections.addAll(this.tasks, tasks);\n        return this;\n    }\n\n    public WorkflowBuilder<T> defaultInput(T defaultInput) {\n        this.defaultInput = defaultInput;\n        return this;\n    }\n\n    public WorkflowBuilder<T> restartable(boolean restartable) {\n        this.restartable = restartable;\n        return this;\n    }\n\n    public WorkflowBuilder<T> variables(Object variables) {\n        try {\n            this.state = objectMapper.convertValue(variables, Map.class);\n        } catch (Exception e) {\n            throw new IllegalArgumentException(\n                    \"Workflow Variables cannot be converted to Map.  Supplied: \"\n                            + variables.getClass().getName());\n        }\n        return this;\n    }\n\n    public WorkflowBuilder<T> output(String key, boolean value) {\n        output.put(key, value);\n        return this;\n    }\n\n    public WorkflowBuilder<T> output(String key, String value) {\n        output.put(key, value);\n        return this;\n    }\n\n    public WorkflowBuilder<T> output(String key, Number value) {\n        output.put(key, value);\n        return this;\n    }\n\n    public WorkflowBuilder<T> output(String key, Object value) {\n        output.put(key, value);\n        return this;\n    }\n\n    public WorkflowBuilder<T> output(MapBuilder mapBuilder) {\n        output.putAll(mapBuilder.build());\n        return this;\n    }\n\n    public ConductorWorkflow<T> build() throws ValidationError {\n\n        validate();\n\n        ConductorWorkflow<T> workflow = new ConductorWorkflow<T>(workflowExecutor);\n        if (description != null) {\n            workflow.setDescription(description);\n        }\n\n        workflow.setName(name);\n        workflow.setVersion(version);\n        workflow.setDescription(description);\n        workflow.setFailureWorkflow(failureWorkflow);\n        workflow.setOwnerEmail(ownerEmail);\n        workflow.setTimeoutPolicy(timeoutPolicy);\n        workflow.setTimeoutSeconds(timeoutSeconds);\n        workflow.setRestartable(restartable);\n        workflow.setDefaultInput(defaultInput);\n        workflow.setWorkflowOutput(output);\n        workflow.setVariables(state);\n\n        for (Task task : tasks) {\n            workflow.add(task);\n        }\n\n        return workflow;\n    }\n\n    /**\n     * Validate: 1. There are no tasks with duplicate reference names 2. Each of the task is\n     * consistent with its definition 3.\n     */\n    private void validate() throws ValidationError {\n\n        List<WorkflowTask> allTasks = new ArrayList<>();\n        for (Task task : tasks) {\n            List<WorkflowTask> workflowDefTasks = task.getWorkflowDefTasks();\n            for (WorkflowTask workflowDefTask : workflowDefTasks) {\n                allTasks.addAll(workflowDefTask.collectTasks());\n            }\n        }\n\n        Map<String, WorkflowTask> taskMap = new HashMap<>();\n        Set<String> duplicateTasks = new HashSet<>();\n        for (WorkflowTask task : allTasks) {\n            if (taskMap.containsKey(task.getTaskReferenceName())) {\n                duplicateTasks.add(task.getTaskReferenceName());\n            } else {\n                taskMap.put(task.getTaskReferenceName(), task);\n            }\n        }\n        if (!duplicateTasks.isEmpty()) {\n            throw new ValidationError(\n                    \"Task Reference Names MUST be unique across all the tasks in the workkflow.  \"\n                            + \"Please update/change reference names to be unique for the following tasks: \"\n                            + duplicateTasks);\n        }\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/DoWhile.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\npublic class DoWhile extends Task<DoWhile> {\n\n    private String loopCondition;\n\n    private List<Task<?>> loopTasks = new ArrayList<>();\n\n    /**\n     * Execute tasks in a loop determined by the condition set using condition parameter. The loop\n     * will continue till the condition is true\n     *\n     * @param taskReferenceName\n     * @param condition Javascript that evaluates to a boolean value\n     * @param tasks\n     */\n    public DoWhile(String taskReferenceName, String condition, Task<?>... tasks) {\n        super(taskReferenceName, TaskType.DO_WHILE);\n        Collections.addAll(this.loopTasks, tasks);\n        this.loopCondition = condition;\n    }\n\n    /**\n     * Similar to a for loop, run tasks for N times\n     *\n     * @param taskReferenceName\n     * @param loopCount\n     * @param tasks\n     */\n    public DoWhile(String taskReferenceName, int loopCount, Task<?>... tasks) {\n        super(taskReferenceName, TaskType.DO_WHILE);\n        Collections.addAll(this.loopTasks, tasks);\n        this.loopCondition = getForLoopCondition(loopCount);\n    }\n\n    DoWhile(WorkflowTask workflowTask) {\n        super(workflowTask);\n        this.loopCondition = workflowTask.getLoopCondition();\n        for (WorkflowTask task : workflowTask.getLoopOver()) {\n            Task<?> loopTask = TaskRegistry.getTask(task);\n            this.loopTasks.add(loopTask);\n        }\n    }\n\n    public DoWhile loopOver(Task<?>... tasks) {\n        for (Task<?> task : tasks) {\n            this.loopTasks.add(task);\n        }\n        return this;\n    }\n\n    private String getForLoopCondition(int loopCount) {\n        return \"if ( $.\"\n                + getTaskReferenceName()\n                + \"['iteration'] < \"\n                + loopCount\n                + \") { true; } else { false; }\";\n    }\n\n    public String getLoopCondition() {\n        return loopCondition;\n    }\n\n    public List<? extends Task> getLoopTasks() {\n        return loopTasks;\n    }\n\n    @Override\n    public void updateWorkflowTask(WorkflowTask workflowTask) {\n        workflowTask.setLoopCondition(loopCondition);\n\n        List<WorkflowTask> loopWorkflowTasks = new ArrayList<>();\n        for (Task task : this.loopTasks) {\n            loopWorkflowTasks.addAll(task.getWorkflowDefTasks());\n        }\n        workflowTask.setLoopOver(loopWorkflowTasks);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Dynamic.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.google.common.base.Strings;\n\n/** Wait task */\npublic class Dynamic extends Task<Dynamic> {\n\n    public static final String TASK_NAME_INPUT_PARAM = \"taskToExecute\";\n\n    public Dynamic(String taskReferenceName, String dynamicTaskNameValue) {\n        super(taskReferenceName, TaskType.DYNAMIC);\n        if (Strings.isNullOrEmpty(dynamicTaskNameValue)) {\n            throw new AssertionError(\"Null/Empty dynamicTaskNameValue\");\n        }\n        super.input(TASK_NAME_INPUT_PARAM, dynamicTaskNameValue);\n    }\n\n    Dynamic(WorkflowTask workflowTask) {\n        super(workflowTask);\n    }\n\n    @Override\n    public void updateWorkflowTask(WorkflowTask task) {\n        task.setDynamicTaskNameParam(TASK_NAME_INPUT_PARAM);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/DynamicFork.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\npublic class DynamicFork extends Task<DynamicFork> {\n\n    public static final String FORK_TASK_PARAM = \"forkedTasks\";\n\n    public static final String FORK_TASK_INPUT_PARAM = \"forkedTasksInputs\";\n\n    private String forkTasksParameter;\n\n    private String forkTasksInputsParameter;\n\n    private Join join;\n\n    private SimpleTask forkPrepareTask;\n\n    /**\n     * Dynamic fork task that executes a set of tasks in parallel which are determined at run time.\n     * Use cases: Based on the input, you want to fork N number of processes in parallel to be\n     * executed. The number N is not pre-determined at the definition time and so a regular ForkJoin\n     * cannot be used.\n     *\n     * @param taskReferenceName\n     */\n    public DynamicFork(\n            String taskReferenceName, String forkTasksParameter, String forkTasksInputsParameter) {\n        super(taskReferenceName, TaskType.FORK_JOIN_DYNAMIC);\n        this.join = new Join(taskReferenceName + \"_join\");\n        this.forkTasksParameter = forkTasksParameter;\n        this.forkTasksInputsParameter = forkTasksInputsParameter;\n        super.input(FORK_TASK_PARAM, forkTasksParameter);\n        super.input(FORK_TASK_INPUT_PARAM, forkTasksInputsParameter);\n    }\n\n    /**\n     * Dynamic fork task that executes a set of tasks in parallel which are determined at run time.\n     * Use cases: Based on the input, you want to fork N number of processes in parallel to be\n     * executed. The number N is not pre-determined at the definition time and so a regular ForkJoin\n     * cannot be used.\n     *\n     * @param taskReferenceName\n     * @param forkPrepareTask A Task that produces the output as {@link DynamicForkInput} to specify\n     *     which tasks to fork.\n     */\n    public DynamicFork(String taskReferenceName, SimpleTask forkPrepareTask) {\n        super(taskReferenceName, TaskType.FORK_JOIN_DYNAMIC);\n        this.forkPrepareTask = forkPrepareTask;\n        this.join = new Join(taskReferenceName + \"_join\");\n        this.forkTasksParameter = forkPrepareTask.taskOutput.get(FORK_TASK_PARAM);\n        this.forkTasksInputsParameter = forkPrepareTask.taskOutput.get(FORK_TASK_INPUT_PARAM);\n        super.input(FORK_TASK_PARAM, forkTasksParameter);\n        super.input(FORK_TASK_INPUT_PARAM, forkTasksInputsParameter);\n    }\n\n    DynamicFork(WorkflowTask workflowTask) {\n        super(workflowTask);\n        String nameOfParamForForkTask = workflowTask.getDynamicForkTasksParam();\n        String nameOfParamForForkTaskInput = workflowTask.getDynamicForkTasksInputParamName();\n        this.forkTasksParameter =\n                (String) workflowTask.getInputParameters().get(nameOfParamForForkTask);\n        this.forkTasksInputsParameter =\n                (String) workflowTask.getInputParameters().get(nameOfParamForForkTaskInput);\n    }\n\n    public Join getJoin() {\n        return join;\n    }\n\n    public String getForkTasksParameter() {\n        return forkTasksParameter;\n    }\n\n    public String getForkTasksInputsParameter() {\n        return forkTasksInputsParameter;\n    }\n\n    @Override\n    public void updateWorkflowTask(WorkflowTask task) {\n        task.setDynamicForkTasksParam(\"forkedTasks\");\n        task.setDynamicForkTasksInputParamName(\"forkedTasksInputs\");\n    }\n\n    @Override\n    protected List<WorkflowTask> getChildrenTasks() {\n        List<WorkflowTask> tasks = new ArrayList<>();\n        tasks.addAll(join.getWorkflowDefTasks());\n        return tasks;\n    }\n\n    @Override\n    protected List<WorkflowTask> getParentTasks() {\n        if (forkPrepareTask != null) {\n            return List.of(forkPrepareTask.toWorkflowTask());\n        }\n        return List.of();\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/DynamicForkInput.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class DynamicForkInput {\n\n    /** List of tasks to execute in parallel */\n    private List<Task<?>> tasks;\n\n    /**\n     * Input to the tasks. Key is the reference name of the task and value is an Object that is sent\n     * as input to the task\n     */\n    private Map<String, Object> inputs;\n\n    public DynamicForkInput(List<Task<?>> tasks, Map<String, Object> inputs) {\n        this.tasks = tasks;\n        this.inputs = inputs;\n    }\n\n    public DynamicForkInput() {}\n\n    public List<Task<?>> getTasks() {\n        return tasks;\n    }\n\n    public void setTasks(List<Task<?>> tasks) {\n        this.tasks = tasks;\n    }\n\n    public Map<String, Object> getInputs() {\n        return inputs;\n    }\n\n    public void setInputs(Map<String, Object> inputs) {\n        this.inputs = inputs;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Event.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.google.common.base.Strings;\n\n/** Task to publish Events to external queuing systems like SQS, NATS, AMQP etc. */\npublic class Event extends Task<Event> {\n\n    private static final String SINK_PARAMETER = \"sink\";\n\n    /**\n     * @param taskReferenceName Unique reference name within the workflow\n     * @param eventSink qualified name of the event sink where the message is published. Using the\n     *     format sink_type:location e.g. sqs:sqs_queue_name, amqp_queue:queue_name,\n     *     amqp_exchange:queue_name, nats:queue_name\n     */\n    public Event(String taskReferenceName, String eventSink) {\n        super(taskReferenceName, TaskType.EVENT);\n        if (Strings.isNullOrEmpty(eventSink)) {\n            throw new AssertionError(\"Null/Empty eventSink\");\n        }\n        super.input(SINK_PARAMETER, eventSink);\n    }\n\n    Event(WorkflowTask workflowTask) {\n        super(workflowTask);\n    }\n\n    public String getSink() {\n        return (String) getInput().get(SINK_PARAMETER);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/ForkJoin.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\n/** ForkJoin task */\npublic class ForkJoin extends Task<ForkJoin> {\n\n    private Join join;\n\n    private Task[][] forkedTasks;\n\n    /**\n     * execute task specified in the forkedTasks parameter in parallel.\n     *\n     * <p>forkedTask is a two-dimensional list that executes the outermost list in parallel and list\n     * within that is executed sequentially.\n     *\n     * <p>e.g. [[task1, task2],[task3, task4],[task5]] are executed as:\n     *\n     * <pre>\n     *                    ---------------\n     *                    |     fork    |\n     *                    ---------------\n     *                    |       |     |\n     *                    |       |     |\n     *                  task1  task3  task5\n     *                  task2  task4    |\n     *                    |      |      |\n     *                 ---------------------\n     *                 |       join        |\n     *                 ---------------------\n     * </pre>\n     *\n     * <p>This method automatically adds a join that waits for all the *last* tasks in the fork\n     * (e.g. task2, task4 and task5 in the above example) to be completed.*\n     *\n     * <p>Use join method @see {@link ForkJoin#joinOn(String...)} to override this behavior (note:\n     * not a common scenario)\n     *\n     * @param taskReferenceName unique task reference name\n     * @param forkedTasks List of tasks to be executed in parallel\n     */\n    public ForkJoin(String taskReferenceName, Task<?>[]... forkedTasks) {\n        super(taskReferenceName, TaskType.FORK_JOIN);\n        this.forkedTasks = forkedTasks;\n    }\n\n    ForkJoin(WorkflowTask workflowTask) {\n        super(workflowTask);\n        int size = workflowTask.getForkTasks().size();\n        this.forkedTasks = new Task[size][];\n        int i = 0;\n        for (List<WorkflowTask> forkTasks : workflowTask.getForkTasks()) {\n            Task[] tasks = new Task[forkTasks.size()];\n            for (int j = 0; j < forkTasks.size(); j++) {\n                WorkflowTask forkWorkflowTask = forkTasks.get(j);\n                Task task = TaskRegistry.getTask(forkWorkflowTask);\n                tasks[j] = task;\n            }\n            this.forkedTasks[i++] = tasks;\n        }\n    }\n\n    public ForkJoin joinOn(String... joinOn) {\n        this.join = new Join(getTaskReferenceName() + \"_join\", joinOn);\n        return this;\n    }\n\n    @Override\n    protected List<WorkflowTask> getChildrenTasks() {\n        WorkflowTask fork = toWorkflowTask();\n\n        WorkflowTask joinWorkflowTask = null;\n        if (this.join != null) {\n            List<WorkflowTask> joinTasks = this.join.getWorkflowDefTasks();\n            joinWorkflowTask = joinTasks.get(0);\n        } else {\n            joinWorkflowTask = new WorkflowTask();\n            joinWorkflowTask.setWorkflowTaskType(TaskType.JOIN);\n            joinWorkflowTask.setTaskReferenceName(getTaskReferenceName() + \"_join\");\n            joinWorkflowTask.setName(joinWorkflowTask.getTaskReferenceName());\n            joinWorkflowTask.setJoinOn(fork.getJoinOn());\n        }\n        return Arrays.asList(joinWorkflowTask);\n    }\n\n    @Override\n    public void updateWorkflowTask(WorkflowTask fork) {\n        List<String> joinOnTaskRefNames = new ArrayList<>();\n        List<List<WorkflowTask>> forkTasks = new ArrayList<>();\n\n        for (Task<?>[] forkedTaskList : forkedTasks) {\n            List<WorkflowTask> forkedWorkflowTasks = new ArrayList<>();\n            for (Task<?> baseWorkflowTask : forkedTaskList) {\n                forkedWorkflowTasks.addAll(baseWorkflowTask.getWorkflowDefTasks());\n            }\n            forkTasks.add(forkedWorkflowTasks);\n            joinOnTaskRefNames.add(\n                    forkedWorkflowTasks.get(forkedWorkflowTasks.size() - 1).getTaskReferenceName());\n        }\n        if (this.join != null) {\n            fork.setJoinOn(List.of(this.join.getJoinOn()));\n        } else {\n            fork.setJoinOn(joinOnTaskRefNames);\n        }\n\n        fork.setForkTasks(forkTasks);\n    }\n\n    public Task[][] getForkedTasks() {\n        return forkedTasks;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Http.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.*;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.utils.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n/** Wait task */\npublic class Http extends Task<Http> {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(Http.class);\n\n    private static final String INPUT_PARAM = \"http_request\";\n\n    private ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    private Input httpRequest;\n\n    public Http(String taskReferenceName) {\n        super(taskReferenceName, TaskType.HTTP);\n        this.httpRequest = new Input();\n        this.httpRequest.method = Input.HttpMethod.GET;\n        super.input(INPUT_PARAM, httpRequest);\n    }\n\n    Http(WorkflowTask workflowTask) {\n        super(workflowTask);\n\n        Object inputRequest = workflowTask.getInputParameters().get(INPUT_PARAM);\n        if (inputRequest != null) {\n            try {\n                this.httpRequest = objectMapper.convertValue(inputRequest, Input.class);\n            } catch (Exception e) {\n                LOGGER.error(\"Error while trying to convert input request \" + e.getMessage(), e);\n            }\n        }\n    }\n\n    public Http input(Input httpRequest) {\n        this.httpRequest = httpRequest;\n        return this;\n    }\n\n    public Http url(String url) {\n        this.httpRequest.setUri(url);\n        return this;\n    }\n\n    public Http method(Input.HttpMethod method) {\n        this.httpRequest.setMethod(method);\n        return this;\n    }\n\n    public Http headers(Map<String, Object> headers) {\n        this.httpRequest.setHeaders(headers);\n        return this;\n    }\n\n    public Http body(Object body) {\n        this.httpRequest.setBody(body);\n        return this;\n    }\n\n    public Http readTimeout(int readTimeout) {\n        this.httpRequest.setReadTimeOut(readTimeout);\n        return this;\n    }\n\n    public Input getHttpRequest() {\n        return httpRequest;\n    }\n\n    @Override\n    protected void updateWorkflowTask(WorkflowTask workflowTask) {\n        workflowTask.getInputParameters().put(INPUT_PARAM, httpRequest);\n    }\n\n    public static class Input {\n        public enum HttpMethod {\n            PUT,\n            POST,\n            GET,\n            DELETE,\n            OPTIONS,\n            HEAD\n        }\n\n        private HttpMethod method; // PUT, POST, GET, DELETE, OPTIONS, HEAD\n        private String vipAddress;\n        private String appName;\n        private Map<String, Object> headers = new HashMap<>();\n        private String uri;\n        private Object body;\n        private String accept = \"application/json\";\n        private String contentType = \"application/json\";\n        private Integer connectionTimeOut;\n        private Integer readTimeOut;\n\n        /**\n         * @return the method\n         */\n        public HttpMethod getMethod() {\n            return method;\n        }\n\n        /**\n         * @param method the method to set\n         */\n        public void setMethod(HttpMethod method) {\n            this.method = method;\n        }\n\n        /**\n         * @return the headers\n         */\n        public Map<String, Object> getHeaders() {\n            return headers;\n        }\n\n        /**\n         * @param headers the headers to set\n         */\n        public void setHeaders(Map<String, Object> headers) {\n            this.headers = headers;\n        }\n\n        /**\n         * @return the body\n         */\n        public Object getBody() {\n            return body;\n        }\n\n        /**\n         * @param body the body to set\n         */\n        public void setBody(Object body) {\n            this.body = body;\n        }\n\n        /**\n         * @return the uri\n         */\n        public String getUri() {\n            return uri;\n        }\n\n        /**\n         * @param uri the uri to set\n         */\n        public void setUri(String uri) {\n            this.uri = uri;\n        }\n\n        /**\n         * @return the vipAddress\n         */\n        public String getVipAddress() {\n            return vipAddress;\n        }\n\n        /**\n         * @param vipAddress the vipAddress to set\n         */\n        public void setVipAddress(String vipAddress) {\n            this.vipAddress = vipAddress;\n        }\n\n        /**\n         * @return the accept\n         */\n        public String getAccept() {\n            return accept;\n        }\n\n        /**\n         * @param accept the accept to set\n         */\n        public void setAccept(String accept) {\n            this.accept = accept;\n        }\n\n        /**\n         * @return the MIME content type to use for the request\n         */\n        public String getContentType() {\n            return contentType;\n        }\n\n        /**\n         * @param contentType the MIME content type to set\n         */\n        public void setContentType(String contentType) {\n            this.contentType = contentType;\n        }\n\n        public String getAppName() {\n            return appName;\n        }\n\n        public void setAppName(String appName) {\n            this.appName = appName;\n        }\n\n        /**\n         * @return the connectionTimeOut\n         */\n        public Integer getConnectionTimeOut() {\n            return connectionTimeOut;\n        }\n\n        /**\n         * @return the readTimeOut\n         */\n        public Integer getReadTimeOut() {\n            return readTimeOut;\n        }\n\n        public void setConnectionTimeOut(Integer connectionTimeOut) {\n            this.connectionTimeOut = connectionTimeOut;\n        }\n\n        public void setReadTimeOut(Integer readTimeOut) {\n            this.readTimeOut = readTimeOut;\n        }\n\n        @Override\n        public String toString() {\n            return \"Input{\"\n                    + \"method=\"\n                    + method\n                    + \", vipAddress='\"\n                    + vipAddress\n                    + '\\''\n                    + \", appName='\"\n                    + appName\n                    + '\\''\n                    + \", headers=\"\n                    + headers\n                    + \", uri='\"\n                    + uri\n                    + '\\''\n                    + \", body=\"\n                    + body\n                    + \", accept='\"\n                    + accept\n                    + '\\''\n                    + \", contentType='\"\n                    + contentType\n                    + '\\''\n                    + \", connectionTimeOut=\"\n                    + connectionTimeOut\n                    + \", readTimeOut=\"\n                    + readTimeOut\n                    + '}';\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            Input input = (Input) o;\n            return method == input.method\n                    && Objects.equals(vipAddress, input.vipAddress)\n                    && Objects.equals(appName, input.appName)\n                    && Objects.equals(headers, input.headers)\n                    && Objects.equals(uri, input.uri)\n                    && Objects.equals(body, input.body)\n                    && Objects.equals(accept, input.accept)\n                    && Objects.equals(contentType, input.contentType)\n                    && Objects.equals(connectionTimeOut, input.connectionTimeOut)\n                    && Objects.equals(readTimeOut, input.readTimeOut);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(\n                    method,\n                    vipAddress,\n                    appName,\n                    headers,\n                    uri,\n                    body,\n                    accept,\n                    contentType,\n                    connectionTimeOut,\n                    readTimeOut);\n        }\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/JQ.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\nimport com.google.common.base.Strings;\n\n/**\n * JQ Transformation task See https://stedolan.github.io/jq/ for how to form the queries to parse\n * JSON payloads\n */\npublic class JQ extends Task<JQ> {\n\n    private static final String QUERY_EXPRESSION_PARAMETER = \"queryExpression\";\n\n    public JQ(String taskReferenceName, String queryExpression) {\n        super(taskReferenceName, TaskType.JSON_JQ_TRANSFORM);\n        if (Strings.isNullOrEmpty(queryExpression)) {\n            throw new AssertionError(\"Null/Empty queryExpression\");\n        }\n        super.input(QUERY_EXPRESSION_PARAMETER, queryExpression);\n    }\n\n    JQ(WorkflowTask workflowTask) {\n        super(workflowTask);\n    }\n\n    public String getQueryExpression() {\n        return (String) getInput().get(QUERY_EXPRESSION_PARAMETER);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Javascript.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.script.Bindings;\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptEngineManager;\nimport javax.script.ScriptException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.def.ValidationError;\n\nimport com.google.common.base.Strings;\n\n/**\n * JQ Transformation task See https://stedolan.github.io/jq/ for how to form the queries to parse\n * JSON payloads\n */\npublic class Javascript extends Task<Javascript> {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(Javascript.class);\n\n    private static final String EXPRESSION_PARAMETER = \"expression\";\n\n    private static final String EVALUATOR_TYPE_PARAMETER = \"evaluatorType\";\n\n    private static final String ENGINE = \"nashorn\";\n\n    /**\n     * Javascript tasks are executed on the Conductor server without having to write worker code\n     *\n     * <p>Use {@link Javascript#validate()} method to validate the javascript to ensure the script\n     * is valid.\n     *\n     * @param taskReferenceName\n     * @param script script to execute\n     */\n    public Javascript(String taskReferenceName, String script) {\n        super(taskReferenceName, TaskType.INLINE);\n        if (Strings.isNullOrEmpty(script)) {\n            throw new AssertionError(\"Null/Empty script\");\n        }\n        super.input(EVALUATOR_TYPE_PARAMETER, \"javascript\");\n        super.input(EXPRESSION_PARAMETER, script);\n    }\n\n    /**\n     * Javascript tasks are executed on the Conductor server without having to write worker code\n     *\n     * <p>Use {@link Javascript#validate()} method to validate the javascript to ensure the script\n     * is valid.\n     *\n     * @param taskReferenceName\n     * @param stream stream to load the script file from\n     */\n    public Javascript(String taskReferenceName, InputStream stream) {\n        super(taskReferenceName, TaskType.INLINE);\n        if (stream == null) {\n            throw new AssertionError(\"Stream is empty\");\n        }\n        super.input(EVALUATOR_TYPE_PARAMETER, \"javascript\");\n        try {\n            String script = new String(stream.readAllBytes());\n            super.input(EXPRESSION_PARAMETER, script);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    Javascript(WorkflowTask workflowTask) {\n        super(workflowTask);\n    }\n\n    public String getExpression() {\n        return (String) getInput().get(EXPRESSION_PARAMETER);\n    }\n\n    /**\n     * Validates the script.\n     *\n     * @return\n     */\n    public Javascript validate() {\n        ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(\"Nashorn\");\n        if (scriptEngine == null) {\n            LOGGER.error(\"missing \" + ENGINE + \" engine.  Ensure you are running supported JVM\");\n            return this;\n        }\n\n        try {\n\n            Bindings bindings = scriptEngine.createBindings();\n            bindings.put(\"$\", new HashMap<>());\n            scriptEngine.eval(getExpression(), bindings);\n\n        } catch (ScriptException e) {\n            String message = e.getMessage();\n            throw new ValidationError(message);\n        }\n        return this;\n    }\n\n    /**\n     * Helper method to unit test your javascript. The method is not used for creating or executing\n     * workflow but is meant for testing only.\n     *\n     * @param input Input that against which the script will be executed\n     * @return Output of the script\n     */\n    public Object test(Map<String, Object> input) {\n\n        ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(\"Nashorn\");\n        if (scriptEngine == null) {\n            LOGGER.error(\"missing \" + ENGINE + \" engine.  Ensure you are running supported JVM\");\n            return this;\n        }\n\n        try {\n\n            Bindings bindings = scriptEngine.createBindings();\n            bindings.put(\"$\", input);\n            return scriptEngine.eval(getExpression(), bindings);\n\n        } catch (ScriptException e) {\n            String message = e.getMessage();\n            throw new ValidationError(message);\n        }\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Join.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.Arrays;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\npublic class Join extends Task<Join> {\n\n    private String[] joinOn;\n\n    /**\n     * @param taskReferenceName\n     * @param joinOn List of task reference names to join on\n     */\n    public Join(String taskReferenceName, String... joinOn) {\n        super(taskReferenceName, TaskType.JOIN);\n        this.joinOn = joinOn;\n    }\n\n    Join(WorkflowTask workflowTask) {\n        super(workflowTask);\n        this.joinOn = workflowTask.getJoinOn().toArray(new String[0]);\n    }\n\n    @Override\n    protected void updateWorkflowTask(WorkflowTask workflowTask) {\n        workflowTask.setJoinOn(Arrays.asList(joinOn));\n    }\n\n    public String[] getJoinOn() {\n        return joinOn;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/SetVariable.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.def.WorkflowBuilder;\n\npublic class SetVariable extends Task<SetVariable> {\n    /**\n     * Sets the value of the variable in workflow. Used for workflow state management. Workflow\n     * state is a Map that is initialized using @see {@link WorkflowBuilder#variables(Object)}\n     *\n     * @param taskReferenceName Use input methods to set the variable values\n     */\n    public SetVariable(String taskReferenceName) {\n        super(taskReferenceName, TaskType.SET_VARIABLE);\n    }\n\n    SetVariable(WorkflowTask workflowTask) {\n        super(workflowTask);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/SimpleTask.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\n/** Workflow task executed by a worker */\npublic class SimpleTask extends Task<SimpleTask> {\n\n    private TaskDef taskDef;\n\n    public SimpleTask(String taskDefName, String taskReferenceName) {\n        super(taskReferenceName, TaskType.SIMPLE);\n        super.name(taskDefName);\n    }\n\n    SimpleTask(WorkflowTask workflowTask) {\n        super(workflowTask);\n        this.taskDef = workflowTask.getTaskDefinition();\n    }\n\n    public TaskDef getTaskDef() {\n        return taskDef;\n    }\n\n    public SimpleTask setTaskDef(TaskDef taskDef) {\n        this.taskDef = taskDef;\n        return this;\n    }\n\n    @Override\n    protected void updateWorkflowTask(WorkflowTask workflowTask) {\n        workflowTask.setTaskDefinition(taskDef);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/SubWorkflow.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.def.ConductorWorkflow;\n\npublic class SubWorkflow extends Task<SubWorkflow> {\n\n    private ConductorWorkflow conductorWorkflow;\n\n    private String workflowName;\n\n    private Integer workflowVersion;\n\n    /**\n     * Start a workflow as a sub-workflow\n     *\n     * @param taskReferenceName\n     * @param workflowName\n     * @param workflowVersion\n     */\n    public SubWorkflow(String taskReferenceName, String workflowName, Integer workflowVersion) {\n        super(taskReferenceName, TaskType.SUB_WORKFLOW);\n        this.workflowName = workflowName;\n        this.workflowVersion = workflowVersion;\n    }\n\n    /**\n     * Start a workflow as a sub-workflow\n     *\n     * @param taskReferenceName\n     * @param conductorWorkflow\n     */\n    public SubWorkflow(String taskReferenceName, ConductorWorkflow conductorWorkflow) {\n        super(taskReferenceName, TaskType.SUB_WORKFLOW);\n        this.conductorWorkflow = conductorWorkflow;\n    }\n\n    SubWorkflow(WorkflowTask workflowTask) {\n        super(workflowTask);\n        SubWorkflowParams subworkflowParam = workflowTask.getSubWorkflowParam();\n        this.workflowName = subworkflowParam.getName();\n        this.workflowVersion = subworkflowParam.getVersion();\n        if (subworkflowParam.getWorkflowDef() != null) {\n            this.conductorWorkflow =\n                    ConductorWorkflow.fromWorkflowDef(subworkflowParam.getWorkflowDef());\n        }\n    }\n\n    public ConductorWorkflow getConductorWorkflow() {\n        return conductorWorkflow;\n    }\n\n    public String getWorkflowName() {\n        return workflowName;\n    }\n\n    public int getWorkflowVersion() {\n        return workflowVersion;\n    }\n\n    @Override\n    protected void updateWorkflowTask(WorkflowTask workflowTask) {\n        SubWorkflowParams subWorkflowParam = new SubWorkflowParams();\n\n        if (conductorWorkflow != null) {\n            subWorkflowParam.setWorkflowDef(conductorWorkflow.toWorkflowDef());\n        } else {\n            subWorkflowParam.setName(workflowName);\n            subWorkflowParam.setVersion(workflowVersion);\n        }\n        workflowTask.setSubWorkflowParam(subWorkflowParam);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Switch.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.*;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\n/** Switch Task */\npublic class Switch extends Task<Switch> {\n\n    public static final String VALUE_PARAM_NAME = \"value-param\";\n\n    public static final String JAVASCRIPT_NAME = \"javascript\";\n\n    private String caseExpression;\n\n    private boolean useJavascript;\n\n    private List<Task<?>> defaultTasks = new ArrayList<>();\n\n    private Map<String, List<Task<?>>> branches = new HashMap<>();\n\n    /**\n     * Switch case (similar to if...then...else or switch in java language)\n     *\n     * @param taskReferenceName\n     * @param caseExpression An expression that outputs a string value to be used as case branches.\n     *     Case expression can be a support value parameter e.g. ${workflow.input.key} or\n     *     ${task.output.key} or a Javascript statement.\n     * @param useJavascript set to true if the caseExpression is a javascript statement\n     */\n    public Switch(String taskReferenceName, String caseExpression, boolean useJavascript) {\n        super(taskReferenceName, TaskType.SWITCH);\n        this.caseExpression = caseExpression;\n        this.useJavascript = useJavascript;\n    }\n\n    /**\n     * Switch case (similar to if...then...else or switch in java language)\n     *\n     * @param taskReferenceName\n     * @param caseExpression\n     */\n    public Switch(String taskReferenceName, String caseExpression) {\n        super(taskReferenceName, TaskType.SWITCH);\n        this.caseExpression = caseExpression;\n        this.useJavascript = false;\n    }\n\n    Switch(WorkflowTask workflowTask) {\n        super(workflowTask);\n        Map<String, List<WorkflowTask>> decisions = workflowTask.getDecisionCases();\n\n        decisions.entrySet().stream()\n                .forEach(\n                        branch -> {\n                            String branchName = branch.getKey();\n                            List<WorkflowTask> branchWorkflowTasks = branch.getValue();\n                            List<Task<?>> branchTasks = new ArrayList<>();\n                            for (WorkflowTask branchWorkflowTask : branchWorkflowTasks) {\n                                branchTasks.add(TaskRegistry.getTask(branchWorkflowTask));\n                            }\n                            this.branches.put(branchName, branchTasks);\n                        });\n\n        List<WorkflowTask> defaultCases = workflowTask.getDefaultCase();\n        for (WorkflowTask defaultCase : defaultCases) {\n            this.defaultTasks.add(TaskRegistry.getTask(defaultCase));\n        }\n    }\n\n    public Switch defaultCase(Task<?>... tasks) {\n        defaultTasks = Arrays.asList(tasks);\n        return this;\n    }\n\n    public Switch defaultCase(List<Task<?>> defaultTasks) {\n        this.defaultTasks = defaultTasks;\n        return this;\n    }\n\n    public Switch decisionCases(Map<String, List<Task<?>>> branches) {\n        this.branches = branches;\n        return this;\n    }\n\n    public Switch defaultCase(String... workerTasks) {\n        for (String workerTask : workerTasks) {\n            this.defaultTasks.add(new SimpleTask(workerTask, workerTask));\n        }\n        return this;\n    }\n\n    public Switch switchCase(String caseValue, Task... tasks) {\n        branches.put(caseValue, Arrays.asList(tasks));\n        return this;\n    }\n\n    public Switch switchCase(String caseValue, String... workerTasks) {\n        List<Task<?>> tasks = new ArrayList<>(workerTasks.length);\n        int i = 0;\n        for (String workerTask : workerTasks) {\n            tasks.add(new SimpleTask(workerTask, workerTask));\n        }\n        branches.put(caseValue, tasks);\n        return this;\n    }\n\n    public List<Task<?>> getDefaultTasks() {\n        return defaultTasks;\n    }\n\n    public Map<String, List<Task<?>>> getBranches() {\n        return branches;\n    }\n\n    @Override\n    public void updateWorkflowTask(WorkflowTask workflowTask) {\n\n        if (useJavascript) {\n            workflowTask.setEvaluatorType(JAVASCRIPT_NAME);\n            workflowTask.setExpression(caseExpression);\n\n        } else {\n            workflowTask.setEvaluatorType(VALUE_PARAM_NAME);\n            workflowTask.getInputParameters().put(\"switchCaseValue\", caseExpression);\n            workflowTask.setExpression(\"switchCaseValue\");\n        }\n\n        Map<String, List<WorkflowTask>> decisionCases = new HashMap<>();\n        branches.entrySet()\n                .forEach(\n                        entry -> {\n                            String decisionCase = entry.getKey();\n                            List<Task<?>> decisionTasks = entry.getValue();\n                            List<WorkflowTask> decionTaskDefs =\n                                    new ArrayList<>(decisionTasks.size());\n                            for (Task<?> decisionTask : decisionTasks) {\n                                decionTaskDefs.addAll(decisionTask.getWorkflowDefTasks());\n                            }\n                            decisionCases.put(decisionCase, decionTaskDefs);\n                        });\n\n        workflowTask.setDecisionCases(decisionCases);\n        List<WorkflowTask> defaultCaseTaskDefs = new ArrayList<>(defaultTasks.size());\n        for (Task<?> defaultTask : defaultTasks) {\n            defaultCaseTaskDefs.addAll(defaultTask.getWorkflowDefTasks());\n        }\n        workflowTask.setDefaultCase(defaultCaseTaskDefs);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Task.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.*;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.utils.InputOutputGetter;\nimport com.netflix.conductor.sdk.workflow.utils.MapBuilder;\nimport com.netflix.conductor.sdk.workflow.utils.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.base.Strings;\n\n/** Workflow Task */\npublic abstract class Task<T> {\n\n    private String name;\n\n    private String description;\n\n    private String taskReferenceName;\n\n    private boolean optional;\n\n    private int startDelay;\n\n    private TaskType type;\n\n    private Map<String, Object> input = new HashMap<>();\n\n    protected final ObjectMapper om = new ObjectMapperProvider().getObjectMapper();\n\n    public final InputOutputGetter taskInput;\n\n    public final InputOutputGetter taskOutput;\n\n    public Task(String taskReferenceName, TaskType type) {\n        if (Strings.isNullOrEmpty(taskReferenceName)) {\n            throw new AssertionError(\"taskReferenceName cannot be null\");\n        }\n        if (type == null) {\n            throw new AssertionError(\"type cannot be null\");\n        }\n\n        this.name = taskReferenceName;\n        this.taskReferenceName = taskReferenceName;\n        this.type = type;\n        this.taskInput = new InputOutputGetter(taskReferenceName, InputOutputGetter.Field.input);\n        this.taskOutput = new InputOutputGetter(taskReferenceName, InputOutputGetter.Field.output);\n    }\n\n    Task(WorkflowTask workflowTask) {\n        this(workflowTask.getTaskReferenceName(), TaskType.valueOf(workflowTask.getType()));\n        this.input = workflowTask.getInputParameters();\n        this.description = workflowTask.getDescription();\n        this.name = workflowTask.getName();\n    }\n\n    public T name(String name) {\n        this.name = name;\n        return (T) this;\n    }\n\n    public T description(String description) {\n        this.description = description;\n        return (T) this;\n    }\n\n    public T input(String key, boolean value) {\n        input.put(key, value);\n        return (T) this;\n    }\n\n    public T input(String key, Object value) {\n        input.put(key, value);\n        return (T) this;\n    }\n\n    public T input(String key, char value) {\n        input.put(key, value);\n        return (T) this;\n    }\n\n    public T input(String key, InputOutputGetter value) {\n        input.put(key, value.getParent());\n        return (T) this;\n    }\n\n    public T input(InputOutputGetter value) {\n        return input(\"input\", value);\n    }\n\n    public T input(String key, String value) {\n        input.put(key, value);\n        return (T) this;\n    }\n\n    public T input(String key, Number value) {\n        input.put(key, value);\n        return (T) this;\n    }\n\n    public T input(String key, Map<String, Object> value) {\n        input.put(key, value);\n        return (T) this;\n    }\n\n    public T input(Map<String, Object> map) {\n        input.putAll(map);\n        return (T) this;\n    }\n\n    public T input(MapBuilder builder) {\n        input.putAll(builder.build());\n        return (T) this;\n    }\n\n    public T input(Object... keyValues) {\n        if (keyValues.length == 1) {\n            Object kv = keyValues[0];\n            Map objectMap = om.convertValue(kv, Map.class);\n            input.putAll(objectMap);\n            return (T) this;\n        }\n        if (keyValues.length % 2 == 1) {\n            throw new IllegalArgumentException(\"Not all keys have value specified\");\n        }\n        for (int i = 0; i < keyValues.length; ) {\n            String key = keyValues[i].toString();\n            Object value = keyValues[i + 1];\n            input.put(key, value);\n            i += 2;\n        }\n        return (T) this;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getTaskReferenceName() {\n        return taskReferenceName;\n    }\n\n    public void setTaskReferenceName(String taskReferenceName) {\n        this.taskReferenceName = taskReferenceName;\n    }\n\n    public boolean isOptional() {\n        return optional;\n    }\n\n    public void setOptional(boolean optional) {\n        this.optional = optional;\n    }\n\n    public int getStartDelay() {\n        return startDelay;\n    }\n\n    public void setStartDelay(int startDelay) {\n        this.startDelay = startDelay;\n    }\n\n    public TaskType getType() {\n        return type;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public Map<String, Object> getInput() {\n        return input;\n    }\n\n    public final List<WorkflowTask> getWorkflowDefTasks() {\n        List<WorkflowTask> workflowTasks = new ArrayList<>();\n        workflowTasks.addAll(getParentTasks());\n        workflowTasks.add(toWorkflowTask());\n        workflowTasks.addAll(getChildrenTasks());\n        return workflowTasks;\n    }\n\n    protected final WorkflowTask toWorkflowTask() {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(name);\n        workflowTask.setTaskReferenceName(taskReferenceName);\n        workflowTask.setWorkflowTaskType(type);\n        workflowTask.setDescription(description);\n        workflowTask.setInputParameters(input);\n        workflowTask.setStartDelay(startDelay);\n        workflowTask.setOptional(optional);\n\n        // Let the sub-classes enrich the workflow task before returning back\n        updateWorkflowTask(workflowTask);\n\n        return workflowTask;\n    }\n\n    /**\n     * Override this method when the sub-class should update the default WorkflowTask generated\n     * using {@link #toWorkflowTask()}\n     *\n     * @param workflowTask\n     */\n    protected void updateWorkflowTask(WorkflowTask workflowTask) {}\n\n    /**\n     * Override this method when sub-classes will generate multiple workflow tasks. Used by tasks\n     * which have children tasks such as do_while, fork, etc.\n     *\n     * @return\n     */\n    protected List<WorkflowTask> getChildrenTasks() {\n        return List.of();\n    }\n\n    protected List<WorkflowTask> getParentTasks() {\n        return List.of();\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/TaskRegistry.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\npublic class TaskRegistry {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(TaskRegistry.class);\n\n    private static Map<String, Class<? extends Task>> taskTypeMap = new HashMap<>();\n\n    public static void register(String taskType, Class<? extends Task> taskImplementation) {\n        taskTypeMap.put(taskType, taskImplementation);\n    }\n\n    public static Task<?> getTask(WorkflowTask workflowTask) {\n        Class<? extends Task> clazz = taskTypeMap.get(workflowTask.getType());\n        if (clazz == null) {\n            throw new UnsupportedOperationException(\n                    \"No support to convert \" + workflowTask.getType());\n        }\n        Task<?> task = null;\n        try {\n            task = clazz.getDeclaredConstructor(WorkflowTask.class).newInstance(workflowTask);\n        } catch (Exception e) {\n            LOGGER.error(e.getMessage(), e);\n            return task;\n        }\n        return task;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Terminate.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.util.HashMap;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.Workflow;\n\npublic class Terminate extends Task<Terminate> {\n\n    private static final String TERMINATION_STATUS_PARAMETER = \"terminationStatus\";\n\n    private static final String TERMINATION_WORKFLOW_OUTPUT = \"workflowOutput\";\n\n    private static final String TERMINATION_REASON_PARAMETER = \"terminationReason\";\n\n    /**\n     * Terminate the workflow and mark it as FAILED\n     *\n     * @param taskReferenceName\n     * @param reason\n     */\n    public Terminate(String taskReferenceName, String reason) {\n        this(taskReferenceName, Workflow.WorkflowStatus.FAILED, reason, new HashMap<>());\n    }\n\n    /**\n     * Terminate the workflow with a specific terminate status\n     *\n     * @param taskReferenceName\n     * @param terminationStatus\n     * @param reason\n     */\n    public Terminate(\n            String taskReferenceName, Workflow.WorkflowStatus terminationStatus, String reason) {\n        this(taskReferenceName, terminationStatus, reason, new HashMap<>());\n    }\n\n    public Terminate(\n            String taskReferenceName,\n            Workflow.WorkflowStatus terminationStatus,\n            String reason,\n            Object workflowOutput) {\n        super(taskReferenceName, TaskType.TERMINATE);\n\n        input(TERMINATION_STATUS_PARAMETER, terminationStatus.name());\n        input(TERMINATION_WORKFLOW_OUTPUT, workflowOutput);\n        input(TERMINATION_REASON_PARAMETER, reason);\n    }\n\n    Terminate(WorkflowTask workflowTask) {\n        super(workflowTask);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Wait.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def.tasks;\n\nimport java.time.Duration;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\n\nimport javax.swing.*;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\n\n/** Wait task */\npublic class Wait extends Task<Wait> {\n\n    public static final String DURATION_INPUT = \"duration\";\n    public static final String UNTIL_INPUT = \"until\";\n\n    public static final DateTimeFormatter dateTimeFormatter =\n            DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm z\");\n\n    /**\n     * Wait until and external signal completes the task. The external signal can be either an API\n     * call (POST /api/task) to update the task status or an event coming from a supported external\n     * queue integration like SQS, Kafka, NATS, AMQP etc.\n     *\n     * <p><br>\n     * see <a href=https://netflix.github.io/conductor/reference-docs/wait-task/>\n     * https://netflix.github.io/conductor/reference-docs/wait-task</a> for more details\n     *\n     * @param taskReferenceName\n     */\n    public Wait(String taskReferenceName) {\n        super(taskReferenceName, TaskType.WAIT);\n    }\n\n    public Wait(String taskReferenceName, Duration waitFor) {\n        super(taskReferenceName, TaskType.WAIT);\n        long seconds = waitFor.getSeconds();\n        input(DURATION_INPUT, seconds + \"s\");\n    }\n\n    public Wait(String taskReferenceName, ZonedDateTime waitUntil) {\n        super(taskReferenceName, TaskType.WAIT);\n        String formattedDateTime = waitUntil.format(dateTimeFormatter);\n        input(UNTIL_INPUT, formattedDateTime);\n    }\n\n    Wait(WorkflowTask workflowTask) {\n        super(workflowTask);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/WorkflowExecutor.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.*;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.http.MetadataClient;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.http.WorkflowClient;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.sdk.workflow.def.ConductorWorkflow;\nimport com.netflix.conductor.sdk.workflow.def.tasks.*;\nimport com.netflix.conductor.sdk.workflow.executor.task.AnnotatedWorkerExecutor;\nimport com.netflix.conductor.sdk.workflow.utils.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.sun.jersey.api.client.ClientHandler;\nimport com.sun.jersey.api.client.config.DefaultClientConfig;\nimport com.sun.jersey.api.client.filter.ClientFilter;\n\npublic class WorkflowExecutor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowExecutor.class);\n\n    private final TypeReference<List<TaskDef>> listOfTaskDefs = new TypeReference<>() {};\n\n    private Map<String, CompletableFuture<Workflow>> runningWorkflowFutures =\n            new ConcurrentHashMap<>();\n\n    private final ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    private final TaskClient taskClient;\n\n    private final WorkflowClient workflowClient;\n\n    private final MetadataClient metadataClient;\n\n    private final AnnotatedWorkerExecutor annotatedWorkerExecutor;\n\n    private final ScheduledExecutorService scheduledWorkflowMonitor =\n            Executors.newSingleThreadScheduledExecutor();\n\n    static {\n        initTaskImplementations();\n    }\n\n    public static void initTaskImplementations() {\n        TaskRegistry.register(TaskType.DO_WHILE.name(), DoWhile.class);\n        TaskRegistry.register(TaskType.DYNAMIC.name(), Dynamic.class);\n        TaskRegistry.register(TaskType.FORK_JOIN_DYNAMIC.name(), DynamicFork.class);\n        TaskRegistry.register(TaskType.FORK_JOIN.name(), ForkJoin.class);\n        TaskRegistry.register(TaskType.HTTP.name(), Http.class);\n        TaskRegistry.register(TaskType.INLINE.name(), Javascript.class);\n        TaskRegistry.register(TaskType.JOIN.name(), Join.class);\n        TaskRegistry.register(TaskType.JSON_JQ_TRANSFORM.name(), JQ.class);\n        TaskRegistry.register(TaskType.SET_VARIABLE.name(), SetVariable.class);\n        TaskRegistry.register(TaskType.SIMPLE.name(), SimpleTask.class);\n        TaskRegistry.register(TaskType.SUB_WORKFLOW.name(), SubWorkflow.class);\n        TaskRegistry.register(TaskType.SWITCH.name(), Switch.class);\n        TaskRegistry.register(TaskType.TERMINATE.name(), Terminate.class);\n        TaskRegistry.register(TaskType.WAIT.name(), Wait.class);\n        TaskRegistry.register(TaskType.EVENT.name(), Event.class);\n    }\n\n    public WorkflowExecutor(String apiServerURL) {\n        this(apiServerURL, 100);\n    }\n\n    public WorkflowExecutor(\n            String apiServerURL, int pollingInterval, ClientFilter... clientFilter) {\n\n        taskClient = new TaskClient(new DefaultClientConfig(), (ClientHandler) null, clientFilter);\n        taskClient.setRootURI(apiServerURL);\n\n        workflowClient =\n                new WorkflowClient(new DefaultClientConfig(), (ClientHandler) null, clientFilter);\n        workflowClient.setRootURI(apiServerURL);\n\n        metadataClient =\n                new MetadataClient(new DefaultClientConfig(), (ClientHandler) null, clientFilter);\n        metadataClient.setRootURI(apiServerURL);\n\n        annotatedWorkerExecutor = new AnnotatedWorkerExecutor(taskClient, pollingInterval);\n        scheduledWorkflowMonitor.scheduleAtFixedRate(\n                () -> {\n                    for (Map.Entry<String, CompletableFuture<Workflow>> entry :\n                            runningWorkflowFutures.entrySet()) {\n                        String workflowId = entry.getKey();\n                        CompletableFuture<Workflow> future = entry.getValue();\n                        Workflow workflow = workflowClient.getWorkflow(workflowId, true);\n                        if (workflow.getStatus().isTerminal()) {\n                            future.complete(workflow);\n                            runningWorkflowFutures.remove(workflowId);\n                        }\n                    }\n                },\n                100,\n                100,\n                TimeUnit.MILLISECONDS);\n    }\n\n    public WorkflowExecutor(\n            TaskClient taskClient,\n            WorkflowClient workflowClient,\n            MetadataClient metadataClient,\n            int pollingInterval) {\n\n        this.taskClient = taskClient;\n        this.workflowClient = workflowClient;\n        this.metadataClient = metadataClient;\n        annotatedWorkerExecutor = new AnnotatedWorkerExecutor(taskClient, pollingInterval);\n        scheduledWorkflowMonitor.scheduleAtFixedRate(\n                () -> {\n                    for (Map.Entry<String, CompletableFuture<Workflow>> entry :\n                            runningWorkflowFutures.entrySet()) {\n                        String workflowId = entry.getKey();\n                        CompletableFuture<Workflow> future = entry.getValue();\n                        Workflow workflow = workflowClient.getWorkflow(workflowId, true);\n                        if (workflow.getStatus().isTerminal()) {\n                            future.complete(workflow);\n                            runningWorkflowFutures.remove(workflowId);\n                        }\n                    }\n                },\n                100,\n                100,\n                TimeUnit.MILLISECONDS);\n    }\n\n    public void initWorkers(String packagesToScan) {\n        annotatedWorkerExecutor.initWorkers(packagesToScan);\n    }\n\n    public CompletableFuture<Workflow> executeWorkflow(String name, Integer version, Object input) {\n        CompletableFuture<Workflow> future = new CompletableFuture<>();\n        Map<String, Object> inputMap = objectMapper.convertValue(input, Map.class);\n\n        StartWorkflowRequest request = new StartWorkflowRequest();\n        request.setInput(inputMap);\n        request.setName(name);\n        request.setVersion(version);\n\n        String workflowId = workflowClient.startWorkflow(request);\n        runningWorkflowFutures.put(workflowId, future);\n        return future;\n    }\n\n    public CompletableFuture<Workflow> executeWorkflow(\n            ConductorWorkflow conductorWorkflow, Object input) {\n\n        CompletableFuture<Workflow> future = new CompletableFuture<>();\n\n        Map<String, Object> inputMap = objectMapper.convertValue(input, Map.class);\n\n        StartWorkflowRequest request = new StartWorkflowRequest();\n        request.setInput(inputMap);\n        request.setName(conductorWorkflow.getName());\n        request.setVersion(conductorWorkflow.getVersion());\n        request.setWorkflowDef(conductorWorkflow.toWorkflowDef());\n\n        String workflowId = workflowClient.startWorkflow(request);\n        runningWorkflowFutures.put(workflowId, future);\n\n        return future;\n    }\n\n    public void loadTaskDefs(String resourcePath) throws IOException {\n        InputStream resource = WorkflowExecutor.class.getResourceAsStream(resourcePath);\n        if (resource != null) {\n            List<TaskDef> taskDefs = objectMapper.readValue(resource, listOfTaskDefs);\n            loadMetadata(taskDefs);\n        }\n    }\n\n    public void loadWorkflowDefs(String resourcePath) throws IOException {\n        InputStream resource = WorkflowExecutor.class.getResourceAsStream(resourcePath);\n        if (resource != null) {\n            WorkflowDef workflowDef = objectMapper.readValue(resource, WorkflowDef.class);\n            loadMetadata(workflowDef);\n        }\n    }\n\n    public void loadMetadata(WorkflowDef workflowDef) {\n        metadataClient.registerWorkflowDef(workflowDef);\n    }\n\n    public void loadMetadata(List<TaskDef> taskDefs) {\n        metadataClient.registerTaskDefs(taskDefs);\n    }\n\n    public void shutdown() {\n        scheduledWorkflowMonitor.shutdown();\n        annotatedWorkerExecutor.shutdown();\n    }\n\n    public boolean registerWorkflow(WorkflowDef workflowDef, boolean overwrite) {\n        try {\n            if (overwrite) {\n                metadataClient.updateWorkflowDefs(Arrays.asList(workflowDef));\n            } else {\n                metadataClient.registerWorkflowDef(workflowDef);\n            }\n            return true;\n        } catch (Exception e) {\n            LOGGER.error(e.getMessage(), e);\n            return false;\n        }\n    }\n\n    public MetadataClient getMetadataClient() {\n        return metadataClient;\n    }\n\n    public TaskClient getTaskClient() {\n        return taskClient;\n    }\n\n    public WorkflowClient getWorkflowClient() {\n        return workflowClient;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/task/AnnotatedWorker.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.*;\nimport java.util.*;\n\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.def.tasks.DynamicFork;\nimport com.netflix.conductor.sdk.workflow.def.tasks.DynamicForkInput;\nimport com.netflix.conductor.sdk.workflow.task.InputParam;\nimport com.netflix.conductor.sdk.workflow.task.OutputParam;\nimport com.netflix.conductor.sdk.workflow.utils.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic class AnnotatedWorker implements Worker {\n\n    private String name;\n\n    private Method workerMethod;\n\n    private Object obj;\n\n    private ObjectMapper om = new ObjectMapperProvider().getObjectMapper();\n\n    private int pollingInterval = 100;\n\n    private Set<TaskResult.Status> failedStatuses =\n            Set.of(TaskResult.Status.FAILED, TaskResult.Status.FAILED_WITH_TERMINAL_ERROR);\n\n    public AnnotatedWorker(String name, Method workerMethod, Object obj) {\n        this.name = name;\n        this.workerMethod = workerMethod;\n        this.obj = obj;\n        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\n    }\n\n    @Override\n    public String getTaskDefName() {\n        return name;\n    }\n\n    @Override\n    public TaskResult execute(Task task) {\n        TaskResult result = null;\n        try {\n            TaskContext context = TaskContext.set(task);\n            Object[] parameters = getInvocationParameters(task);\n            Object invocationResult = workerMethod.invoke(obj, parameters);\n            result = setValue(invocationResult, context.getTaskResult());\n            if (!failedStatuses.contains(result.getStatus())\n                    && result.getCallbackAfterSeconds() > 0) {\n                result.setStatus(TaskResult.Status.IN_PROGRESS);\n            }\n        } catch (InvocationTargetException invocationTargetException) {\n            if (result == null) {\n                result = new TaskResult(task);\n            }\n            Throwable e = invocationTargetException.getCause();\n            e.printStackTrace();\n            if (e instanceof NonRetryableException) {\n                result.setStatus(TaskResult.Status.FAILED_WITH_TERMINAL_ERROR);\n            } else {\n                result.setStatus(TaskResult.Status.FAILED);\n            }\n\n            result.setReasonForIncompletion(e.getMessage());\n            StringBuilder stackTrace = new StringBuilder();\n            for (StackTraceElement stackTraceElement : e.getStackTrace()) {\n                String className = stackTraceElement.getClassName();\n                if (className.startsWith(\"jdk.\")\n                        || className.startsWith(AnnotatedWorker.class.getName())) {\n                    break;\n                }\n                stackTrace.append(stackTraceElement);\n                stackTrace.append(\"\\n\");\n            }\n            result.log(stackTrace.toString());\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n\n        return result;\n    }\n\n    private Object[] getInvocationParameters(Task task) {\n        Class<?>[] parameterTypes = workerMethod.getParameterTypes();\n        Parameter[] parameters = workerMethod.getParameters();\n\n        if (parameterTypes.length == 1 && parameterTypes[0].equals(Task.class)) {\n            return new Object[] {task};\n        } else if (parameterTypes.length == 1 && parameterTypes[0].equals(Map.class)) {\n            return new Object[] {task.getInputData()};\n        }\n\n        return getParameters(task, parameterTypes, parameters);\n    }\n\n    private Object[] getParameters(Task task, Class<?>[] parameterTypes, Parameter[] parameters) {\n        Annotation[][] parameterAnnotations = workerMethod.getParameterAnnotations();\n        Object[] values = new Object[parameterTypes.length];\n        for (int i = 0; i < parameterTypes.length; i++) {\n            Annotation[] paramAnnotation = parameterAnnotations[i];\n            if (paramAnnotation != null && paramAnnotation.length > 0) {\n                Type type = parameters[i].getParameterizedType();\n                Class<?> parameterType = parameterTypes[i];\n                values[i] = getInputValue(task, parameterType, type, paramAnnotation);\n            } else {\n                values[i] = om.convertValue(task.getInputData(), parameterTypes[i]);\n            }\n        }\n\n        return values;\n    }\n\n    private Object getInputValue(\n            Task task, Class<?> parameterType, Type type, Annotation[] paramAnnotation) {\n        InputParam ip = findInputParamAnnotation(paramAnnotation);\n\n        if (ip == null) {\n            return om.convertValue(task.getInputData(), parameterType);\n        }\n\n        final String name = ip.value();\n        final Object value = task.getInputData().get(name);\n        if (value == null) {\n            return null;\n        }\n\n        if (List.class.isAssignableFrom(parameterType)) {\n            List<?> list = om.convertValue(value, List.class);\n            if (type instanceof ParameterizedType) {\n                ParameterizedType parameterizedType = (ParameterizedType) type;\n                Class<?> typeOfParameter = (Class<?>) parameterizedType.getActualTypeArguments()[0];\n                List<Object> parameterizedList = new ArrayList<>();\n                for (Object item : list) {\n                    parameterizedList.add(om.convertValue(item, typeOfParameter));\n                }\n\n                return parameterizedList;\n            } else {\n                return list;\n            }\n        } else {\n            return om.convertValue(value, parameterType);\n        }\n    }\n\n    private static InputParam findInputParamAnnotation(Annotation[] paramAnnotation) {\n        return (InputParam)\n                Arrays.stream(paramAnnotation)\n                        .filter(ann -> ann.annotationType().equals(InputParam.class))\n                        .findFirst()\n                        .orElse(null);\n    }\n\n    private TaskResult setValue(Object invocationResult, TaskResult result) {\n\n        if (invocationResult == null) {\n            result.setStatus(TaskResult.Status.COMPLETED);\n            return result;\n        }\n\n        OutputParam opAnnotation =\n                workerMethod.getAnnotatedReturnType().getAnnotation(OutputParam.class);\n        if (opAnnotation != null) {\n\n            String name = opAnnotation.value();\n            result.getOutputData().put(name, invocationResult);\n            result.setStatus(TaskResult.Status.COMPLETED);\n            return result;\n\n        } else if (invocationResult instanceof TaskResult) {\n\n            return (TaskResult) invocationResult;\n\n        } else if (invocationResult instanceof Map) {\n            Map resultAsMap = (Map) invocationResult;\n            result.getOutputData().putAll(resultAsMap);\n            result.setStatus(TaskResult.Status.COMPLETED);\n            return result;\n        } else if (invocationResult instanceof String\n                || invocationResult instanceof Number\n                || invocationResult instanceof Boolean) {\n            result.getOutputData().put(\"result\", invocationResult);\n            result.setStatus(TaskResult.Status.COMPLETED);\n            return result;\n        } else if (invocationResult instanceof List) {\n\n            List resultAsList = om.convertValue(invocationResult, List.class);\n            result.getOutputData().put(\"result\", resultAsList);\n            result.setStatus(TaskResult.Status.COMPLETED);\n            return result;\n\n        } else if (invocationResult instanceof DynamicForkInput) {\n            DynamicForkInput forkInput = (DynamicForkInput) invocationResult;\n            List<com.netflix.conductor.sdk.workflow.def.tasks.Task<?>> tasks = forkInput.getTasks();\n            List<WorkflowTask> workflowTasks = new ArrayList<>();\n            for (com.netflix.conductor.sdk.workflow.def.tasks.Task<?> sdkTask : tasks) {\n                workflowTasks.addAll(sdkTask.getWorkflowDefTasks());\n            }\n            result.getOutputData().put(DynamicFork.FORK_TASK_PARAM, workflowTasks);\n            result.getOutputData().put(DynamicFork.FORK_TASK_INPUT_PARAM, forkInput.getInputs());\n            result.setStatus(TaskResult.Status.COMPLETED);\n            return result;\n\n        } else {\n            Map resultAsMap = om.convertValue(invocationResult, Map.class);\n            result.getOutputData().putAll(resultAsMap);\n            result.setStatus(TaskResult.Status.COMPLETED);\n            return result;\n        }\n    }\n\n    public void setPollingInterval(int pollingInterval) {\n        System.out.println(\n                \"Setting the polling interval for \" + getTaskDefName() + \", to \" + pollingInterval);\n        this.pollingInterval = pollingInterval;\n    }\n\n    @Override\n    public int getPollingInterval() {\n        System.out.println(\"Sending the polling interval to \" + pollingInterval);\n        return pollingInterval;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/task/AnnotatedWorkerExecutor.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\nimport java.lang.reflect.Method;\nimport java.util.*;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.client.automator.TaskRunnerConfigurer;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.sdk.workflow.task.WorkerTask;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.google.common.reflect.ClassPath;\n\npublic class AnnotatedWorkerExecutor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(AnnotatedWorkerExecutor.class);\n\n    private TaskClient taskClient;\n\n    private TaskRunnerConfigurer taskRunner;\n\n    private List<Worker> executors = new ArrayList<>();\n\n    private Map<String, Method> workerExecutors = new HashMap<>();\n\n    private Map<String, Integer> workerToThreadCount = new HashMap<>();\n\n    private Map<String, Integer> workerToPollingInterval = new HashMap<>();\n\n    private Map<String, String> workerDomains = new HashMap<>();\n\n    private Map<String, Object> workerClassObjs = new HashMap<>();\n\n    private static Set<String> scannedPackages = new HashSet<>();\n\n    private WorkerConfiguration workerConfiguration;\n\n    public AnnotatedWorkerExecutor(TaskClient taskClient) {\n        this.taskClient = taskClient;\n        this.workerConfiguration = new WorkerConfiguration();\n    }\n\n    public AnnotatedWorkerExecutor(TaskClient taskClient, int pollingIntervalInMillis) {\n        this.taskClient = taskClient;\n        this.workerConfiguration = new WorkerConfiguration(pollingIntervalInMillis);\n    }\n\n    public AnnotatedWorkerExecutor(TaskClient taskClient, WorkerConfiguration workerConfiguration) {\n        this.taskClient = taskClient;\n        this.workerConfiguration = workerConfiguration;\n    }\n\n    /**\n     * Finds any worker implementation and starts polling for tasks\n     *\n     * @param basePackage list of packages - comma separated - to scan for annotated worker\n     *     implementation\n     */\n    public synchronized void initWorkers(String basePackage) {\n        scanWorkers(basePackage);\n        startPolling();\n    }\n\n    /** Shuts down the workers */\n    public void shutdown() {\n        if (taskRunner != null) {\n            taskRunner.shutdown();\n        }\n    }\n\n    private void scanWorkers(String basePackage) {\n        try {\n            if (scannedPackages.contains(basePackage)) {\n                // skip\n                LOGGER.info(\"Package {} already scanned and will skip\", basePackage);\n                return;\n            }\n            // Add here so to avoid infinite recursion where a class in the package contains the\n            // code to init workers\n            scannedPackages.add(basePackage);\n            List<String> packagesToScan = new ArrayList<>();\n            if (basePackage != null) {\n                String[] packages = basePackage.split(\",\");\n                Collections.addAll(packagesToScan, packages);\n            }\n\n            LOGGER.info(\"packages to scan {}\", packagesToScan);\n\n            long s = System.currentTimeMillis();\n            ClassPath.from(AnnotatedWorkerExecutor.class.getClassLoader())\n                    .getAllClasses()\n                    .forEach(\n                            classMeta -> {\n                                String name = classMeta.getName();\n                                if (!includePackage(packagesToScan, name)) {\n                                    return;\n                                }\n                                try {\n                                    Class<?> clazz = classMeta.load();\n                                    Object obj = clazz.getConstructor().newInstance();\n                                    addBean(obj);\n                                } catch (Throwable t) {\n                                    // trace because many classes won't have a default no-args\n                                    // constructor and will fail\n                                    LOGGER.trace(\n                                            \"Caught exception while loading and scanning class {}\",\n                                            t.getMessage());\n                                }\n                            });\n            LOGGER.info(\n                    \"Took {} ms to scan all the classes, loading {} tasks\",\n                    (System.currentTimeMillis() - s),\n                    workerExecutors.size());\n\n        } catch (Exception e) {\n            LOGGER.error(\"Error while scanning for workers: \", e);\n        }\n    }\n\n    private boolean includePackage(List<String> packagesToScan, String name) {\n        for (String scanPkg : packagesToScan) {\n            if (name.startsWith(scanPkg)) return true;\n        }\n        return false;\n    }\n\n    public void addBean(Object bean) {\n        Class<?> clazz = bean.getClass();\n        for (Method method : clazz.getMethods()) {\n            WorkerTask annotation = method.getAnnotation(WorkerTask.class);\n            if (annotation == null) {\n                continue;\n            }\n            addMethod(annotation, method, bean);\n        }\n    }\n\n    private void addMethod(WorkerTask annotation, Method method, Object bean) {\n        String name = annotation.value();\n\n        int threadCount = workerConfiguration.getThreadCount(name);\n        if (threadCount == 0) {\n            threadCount = annotation.threadCount();\n        }\n        workerToThreadCount.put(name, threadCount);\n\n        int pollingInterval = workerConfiguration.getPollingInterval(name);\n        if (pollingInterval == 0) {\n            pollingInterval = annotation.pollingInterval();\n        }\n        workerToPollingInterval.put(name, pollingInterval);\n\n        String domain = workerConfiguration.getDomain(name);\n        if (Strings.isNullOrEmpty(domain)) {\n            domain = annotation.domain();\n        }\n        if (!Strings.isNullOrEmpty(domain)) {\n            workerDomains.put(name, domain);\n        }\n\n        workerClassObjs.put(name, bean);\n        workerExecutors.put(name, method);\n        LOGGER.info(\n                \"Adding worker for task {}, method {} with threadCount {} and polling interval set to {} ms\",\n                name,\n                method,\n                threadCount,\n                pollingInterval);\n    }\n\n    public void startPolling() {\n        workerExecutors.forEach(\n                (taskName, method) -> {\n                    Object obj = workerClassObjs.get(taskName);\n                    AnnotatedWorker executor = new AnnotatedWorker(taskName, method, obj);\n                    executor.setPollingInterval(workerToPollingInterval.get(taskName));\n                    executors.add(executor);\n                });\n\n        if (executors.isEmpty()) {\n            return;\n        }\n\n        LOGGER.info(\"Starting workers with threadCount {}\", workerToThreadCount);\n        LOGGER.info(\"Worker domains {}\", workerDomains);\n\n        taskRunner =\n                new TaskRunnerConfigurer.Builder(taskClient, executors)\n                        .withTaskThreadCount(workerToThreadCount)\n                        .withTaskToDomain(workerDomains)\n                        .build();\n\n        taskRunner.init();\n    }\n\n    @VisibleForTesting\n    List<Worker> getExecutors() {\n        return executors;\n    }\n\n    @VisibleForTesting\n    TaskRunnerConfigurer getTaskRunner() {\n        return taskRunner;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/task/DynamicForkWorker.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\nimport java.lang.reflect.Method;\nimport java.util.Map;\nimport java.util.function.Function;\n\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.sdk.workflow.def.tasks.DynamicFork;\nimport com.netflix.conductor.sdk.workflow.def.tasks.DynamicForkInput;\nimport com.netflix.conductor.sdk.workflow.task.InputParam;\nimport com.netflix.conductor.sdk.workflow.utils.ObjectMapperProvider;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic class DynamicForkWorker implements Worker {\n\n    private final int pollingInterval;\n\n    private final Function<Object, DynamicForkInput> workerMethod;\n\n    private final String name;\n\n    private ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    public DynamicForkWorker(\n            String name, Function<Object, DynamicForkInput> workerMethod, int pollingInterval) {\n        this.name = name;\n        this.workerMethod = workerMethod;\n        this.pollingInterval = pollingInterval;\n    }\n\n    @Override\n    public String getTaskDefName() {\n        return name;\n    }\n\n    @Override\n    public TaskResult execute(Task task) {\n        TaskResult result = new TaskResult(task);\n        try {\n\n            Object parameter = getInvocationParameters(this.workerMethod, task);\n            DynamicForkInput output = this.workerMethod.apply(parameter);\n            result.getOutputData().put(DynamicFork.FORK_TASK_PARAM, output.getTasks());\n            result.getOutputData().put(DynamicFork.FORK_TASK_INPUT_PARAM, output.getInputs());\n            result.setStatus(TaskResult.Status.COMPLETED);\n\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        return result;\n    }\n\n    @Override\n    public int getPollingInterval() {\n        return pollingInterval;\n    }\n\n    private Object getInvocationParameters(Function<?, DynamicForkInput> function, Task task) {\n        InputParam annotation = null;\n        Class<?> parameterType = null;\n        for (Method method : function.getClass().getDeclaredMethods()) {\n            if (method.getReturnType().equals(DynamicForkInput.class)) {\n                annotation = method.getParameters()[0].getAnnotation(InputParam.class);\n                parameterType = method.getParameters()[0].getType();\n            }\n        }\n\n        if (parameterType.equals(Task.class)) {\n            return task;\n        } else if (parameterType.equals(Map.class)) {\n            return task.getInputData();\n        }\n        if (annotation != null) {\n            String name = annotation.value();\n            Object value = task.getInputData().get(name);\n            return objectMapper.convertValue(value, parameterType);\n        }\n        return objectMapper.convertValue(task.getInputData(), parameterType);\n    }\n\n    public static void main(String[] args) {\n        Function<?, DynamicForkInput> fn =\n                new Function<TaskDef, DynamicForkInput>() {\n                    @Override\n                    public DynamicForkInput apply(@InputParam(\"a\") TaskDef s) {\n                        return null;\n                    }\n                };\n\n        for (Method method : fn.getClass().getDeclaredMethods()) {\n            if (method.getReturnType().equals(DynamicForkInput.class)) {\n                System.out.println(\n                        \"\\n\\n-->method: \"\n                                + method\n                                + \", input: \"\n                                + method.getParameters()[0].getType());\n                System.out.println(\"I take input as \" + method.getParameters()[0].getType());\n                InputParam annotation = method.getParameters()[0].getAnnotation(InputParam.class);\n                System.out.println(\"I have annotation \" + annotation);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/task/NonRetryableException.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\n/**\n * Runtime exception indicating the non-retriable error with the task execution. If thrown, the task\n * will fail with FAILED_WITH_TERMINAL_ERROR and will not kick off retries.\n */\npublic class NonRetryableException extends RuntimeException {\n\n    public NonRetryableException(String message) {\n        super(message);\n    }\n\n    public NonRetryableException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public NonRetryableException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/task/TaskContext.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\n\n/** Context for the task */\npublic class TaskContext {\n\n    public static final ThreadLocal<TaskContext> TASK_CONTEXT_INHERITABLE_THREAD_LOCAL =\n            InheritableThreadLocal.withInitial(() -> null);\n\n    public TaskContext(Task task, TaskResult taskResult) {\n        this.task = task;\n        this.taskResult = taskResult;\n    }\n\n    public static TaskContext get() {\n        return TASK_CONTEXT_INHERITABLE_THREAD_LOCAL.get();\n    }\n\n    public static TaskContext set(Task task) {\n        TaskResult result = new TaskResult(task);\n        TaskContext context = new TaskContext(task, result);\n        TASK_CONTEXT_INHERITABLE_THREAD_LOCAL.set(context);\n        return context;\n    }\n\n    private final Task task;\n\n    private final TaskResult taskResult;\n\n    public String getWorkflowInstanceId() {\n        return task.getWorkflowInstanceId();\n    }\n\n    public String getTaskId() {\n        return task.getTaskId();\n    }\n\n    public int getRetryCount() {\n        return task.getRetryCount();\n    }\n\n    public int getPollCount() {\n        return task.getPollCount();\n    }\n\n    public long getCallbackAfterSeconds() {\n        return task.getCallbackAfterSeconds();\n    }\n\n    public void addLog(String log) {\n        this.taskResult.log(log);\n    }\n\n    public Task getTask() {\n        return task;\n    }\n\n    public TaskResult getTaskResult() {\n        return taskResult;\n    }\n\n    public void setCallbackAfter(int seconds) {\n        this.taskResult.setCallbackAfterSeconds(seconds);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/task/WorkerConfiguration.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\npublic class WorkerConfiguration {\n\n    private int defaultPollingInterval = 0;\n\n    public WorkerConfiguration(int defaultPollingInterval) {\n        this.defaultPollingInterval = defaultPollingInterval;\n    }\n\n    public WorkerConfiguration() {}\n\n    public int getPollingInterval(String taskName) {\n        return defaultPollingInterval;\n    }\n\n    public int getThreadCount(String taskName) {\n        return 0;\n    }\n\n    public String getDomain(String taskName) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/task/InputParam.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.task;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.PARAMETER)\npublic @interface InputParam {\n    String value();\n\n    boolean required() default false;\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/task/OutputParam.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.task;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE_USE)\npublic @interface OutputParam {\n    String value();\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/task/WorkerTask.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.task;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/** Identifies a simple worker task. */\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface WorkerTask {\n    String value();\n\n    // No. of threads to use for executing the task\n    int threadCount() default 1;\n\n    int pollingInterval() default 100;\n\n    String domain() default \"\";\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/utils/InputOutputGetter.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.utils;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\n\npublic class InputOutputGetter {\n\n    public enum Field {\n        input,\n        output\n    }\n\n    public static final class Map {\n        private final String parent;\n\n        public Map(String parent) {\n            this.parent = parent;\n        }\n\n        public String get(String key) {\n            return parent + \".\" + key + \"}\";\n        }\n\n        public Map map(String key) {\n            return new Map(parent + \".\" + key);\n        }\n\n        public List list(String key) {\n            return new List(parent + \".\" + key);\n        }\n\n        @Override\n        public String toString() {\n            return parent + \"}\";\n        }\n    }\n\n    public static final class List {\n\n        private final String parent;\n\n        public List(String parent) {\n            this.parent = parent;\n        }\n\n        public List list(String key) {\n            return new List(parent + \".\" + key);\n        }\n\n        public Map map(String key) {\n            return new Map(parent + \".\" + key);\n        }\n\n        public String get(String key, int index) {\n            return parent + \".\" + key + \"[\" + index + \"]}\";\n        }\n\n        public String get(int index) {\n            return parent + \"[\" + index + \"]}\";\n        }\n\n        @Override\n        public String toString() {\n            return parent + \"}\";\n        }\n    }\n\n    private final String name;\n\n    private final Field field;\n\n    public InputOutputGetter(String name, Field field) {\n        this.name = name;\n        this.field = field;\n    }\n\n    public String get(String key) {\n        return \"${\" + name + \".\" + field + \".\" + key + \"}\";\n    }\n\n    public String getParent() {\n        return \"${\" + name + \".\" + field + \"}\";\n    }\n\n    @JsonIgnore\n    public Map map(String key) {\n        return new Map(\"${\" + name + \".\" + field + \".\" + key);\n    }\n\n    @JsonIgnore\n    public List list(String key) {\n        return new List(\"${\" + name + \".\" + field + \".\" + key);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/utils/MapBuilder.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.utils;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MapBuilder {\n    private Map<String, Object> map = new HashMap<>();\n\n    public MapBuilder add(String key, String value) {\n        map.put(key, value);\n        return this;\n    }\n\n    public MapBuilder add(String key, Number value) {\n        map.put(key, value);\n        return this;\n    }\n\n    public MapBuilder add(String key, MapBuilder value) {\n        map.put(key, value.build());\n        return this;\n    }\n\n    public Map<String, Object> build() {\n        return map;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/utils/ObjectMapperProvider.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.utils;\n\nimport com.netflix.conductor.common.jackson.JsonProtoModule;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.SerializationFeature;\n\npublic class ObjectMapperProvider {\n\n    public ObjectMapper getObjectMapper() {\n        final ObjectMapper objectMapper = new ObjectMapper();\n        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\n        objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);\n        objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);\n        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);\n\n        objectMapper.setDefaultPropertyInclusion(\n                JsonInclude.Value.construct(\n                        JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_EMPTY));\n        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);\n        // objectMapper.setSerializationInclusion(JsonInclude.Include.);\n\n        objectMapper.registerModule(new JsonProtoModule());\n        return objectMapper;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/main/resources/test-server.properties",
    "content": "conductor.db.type=memory\nconductor.indexing.enabled=false\nconductor.workflow-repair-service.enabled=false\nloadSample=true\nconductor.system-task-workers.enabled=false"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/def/TaskConversionsTests.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def;\n\nimport java.time.Duration;\nimport java.time.ZonedDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.Test;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.sdk.workflow.def.tasks.*;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class TaskConversionsTests {\n\n    static {\n        WorkflowExecutor.initTaskImplementations();\n    }\n\n    @Test\n    public void testSimpleTaskConversion() {\n        SimpleTask simpleTask = new SimpleTask(\"task_name\", \"task_ref_name\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"key11\", \"value11\");\n        map.put(\"key12\", 100);\n\n        simpleTask.input(\"key1\", \"value\");\n        simpleTask.input(\"key2\", 42);\n        simpleTask.input(\"key3\", true);\n        simpleTask.input(\"key4\", map);\n\n        WorkflowTask workflowTask = simpleTask.getWorkflowDefTasks().get(0);\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(fromWorkflowTask instanceof SimpleTask);\n        SimpleTask simpleTaskFromWorkflowTask = (SimpleTask) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(simpleTask.getName(), fromWorkflowTask.getName());\n        assertEquals(simpleTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(simpleTask.getTaskDef(), simpleTaskFromWorkflowTask.getTaskDef());\n        assertEquals(simpleTask.getType(), simpleTaskFromWorkflowTask.getType());\n        assertEquals(simpleTask.getStartDelay(), simpleTaskFromWorkflowTask.getStartDelay());\n        assertEquals(simpleTask.getInput(), simpleTaskFromWorkflowTask.getInput());\n    }\n\n    @Test\n    public void testDynamicTaskCoversion() {\n        Dynamic dynamicTask = new Dynamic(\"task_name\", \"task_ref_name\");\n\n        WorkflowTask workflowTask = dynamicTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters().get(Dynamic.TASK_NAME_INPUT_PARAM));\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(fromWorkflowTask instanceof Dynamic);\n        Dynamic taskFromWorkflowTask = (Dynamic) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(dynamicTask.getName(), fromWorkflowTask.getName());\n        assertEquals(dynamicTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(dynamicTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(dynamicTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(dynamicTask.getInput(), taskFromWorkflowTask.getInput());\n    }\n\n    @Test\n    public void testForkTaskConversion() {\n        SimpleTask task1 = new SimpleTask(\"task1\", \"task1\");\n        SimpleTask task2 = new SimpleTask(\"task2\", \"task2\");\n        SimpleTask task3 = new SimpleTask(\"task3\", \"task3\");\n\n        ForkJoin forkTask =\n                new ForkJoin(\"task_ref_name\", new Task[] {task1}, new Task[] {task2, task3});\n\n        WorkflowTask workflowTask = forkTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getForkTasks());\n        assertFalse(workflowTask.getForkTasks().isEmpty());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(fromWorkflowTask instanceof ForkJoin);\n        ForkJoin taskFromWorkflowTask = (ForkJoin) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(forkTask.getName(), fromWorkflowTask.getName());\n        assertEquals(forkTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(forkTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(forkTask.getInput(), taskFromWorkflowTask.getInput());\n\n        assertEquals(\n                forkTask.getForkedTasks().length, taskFromWorkflowTask.getForkedTasks().length);\n        for (int i = 0; i < forkTask.getForkedTasks().length; i++) {\n            assertEquals(\n                    forkTask.getForkedTasks()[i].length,\n                    taskFromWorkflowTask.getForkedTasks()[i].length);\n            for (int j = 0; j < forkTask.getForkedTasks()[i].length; j++) {\n                assertEquals(\n                        forkTask.getForkedTasks()[i][j].getTaskReferenceName(),\n                        taskFromWorkflowTask.getForkedTasks()[i][j].getTaskReferenceName());\n\n                assertEquals(\n                        forkTask.getForkedTasks()[i][j].getName(),\n                        taskFromWorkflowTask.getForkedTasks()[i][j].getName());\n\n                assertEquals(\n                        forkTask.getForkedTasks()[i][j].getType(),\n                        taskFromWorkflowTask.getForkedTasks()[i][j].getType());\n            }\n        }\n    }\n\n    @Test\n    public void testDynamicForkTaskCoversion() {\n        DynamicFork dynamicTask = new DynamicFork(\"task_ref_name\", \"forkTasks\", \"forkTaskInputs\");\n\n        WorkflowTask workflowTask = dynamicTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(fromWorkflowTask instanceof DynamicFork);\n        DynamicFork taskFromWorkflowTask = (DynamicFork) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(dynamicTask.getName(), fromWorkflowTask.getName());\n        assertEquals(dynamicTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(dynamicTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(dynamicTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(dynamicTask.getInput(), taskFromWorkflowTask.getInput());\n        assertEquals(\n                dynamicTask.getForkTasksParameter(), taskFromWorkflowTask.getForkTasksParameter());\n        assertEquals(\n                dynamicTask.getForkTasksInputsParameter(),\n                taskFromWorkflowTask.getForkTasksInputsParameter());\n    }\n\n    @Test\n    public void testDoWhileConversion() {\n        SimpleTask task1 = new SimpleTask(\"task_name\", \"task_ref_name\");\n        SimpleTask task2 = new SimpleTask(\"task_name\", \"task_ref_name\");\n\n        DoWhile doWhileTask = new DoWhile(\"task_ref_name\", 2, task1, task2);\n\n        WorkflowTask workflowTask = doWhileTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(fromWorkflowTask instanceof DoWhile);\n        DoWhile taskFromWorkflowTask = (DoWhile) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(doWhileTask.getName(), fromWorkflowTask.getName());\n        assertEquals(doWhileTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(doWhileTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(doWhileTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(doWhileTask.getInput(), taskFromWorkflowTask.getInput());\n\n        assertEquals(doWhileTask.getLoopCondition(), taskFromWorkflowTask.getLoopCondition());\n        assertEquals(\n                doWhileTask.getLoopTasks().stream()\n                        .map(task -> task.getTaskReferenceName())\n                        .sorted()\n                        .collect(Collectors.toSet()),\n                taskFromWorkflowTask.getLoopTasks().stream()\n                        .map(task -> task.getTaskReferenceName())\n                        .sorted()\n                        .collect(Collectors.toSet()));\n    }\n\n    @Test\n    public void testJoin() {\n\n        Join joinTask = new Join(\"task_ref_name\", \"task1\", \"task2\");\n\n        WorkflowTask workflowTask = joinTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n        assertNotNull(workflowTask.getJoinOn());\n        assertTrue(!workflowTask.getJoinOn().isEmpty());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof Join,\n                \"task is not of type Join, but of type \" + fromWorkflowTask.getClass().getName());\n        Join taskFromWorkflowTask = (Join) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(joinTask.getName(), fromWorkflowTask.getName());\n        assertEquals(joinTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(joinTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(joinTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(joinTask.getInput(), taskFromWorkflowTask.getInput());\n\n        assertEquals(joinTask.getJoinOn().length, taskFromWorkflowTask.getJoinOn().length);\n        assertEquals(\n                Arrays.asList(joinTask.getJoinOn()).stream().sorted().collect(Collectors.toSet()),\n                Arrays.asList(taskFromWorkflowTask.getJoinOn()).stream()\n                        .sorted()\n                        .collect(Collectors.toSet()));\n    }\n\n    @Test\n    public void testEvent() {\n\n        Event eventTask = new Event(\"task_ref_name\", \"sqs:queue11\");\n\n        WorkflowTask workflowTask = eventTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof Event,\n                \"task is not of type Event, but of type \" + fromWorkflowTask.getClass().getName());\n        Event taskFromWorkflowTask = (Event) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(eventTask.getName(), fromWorkflowTask.getName());\n        assertEquals(eventTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(eventTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(eventTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(eventTask.getInput(), taskFromWorkflowTask.getInput());\n        assertEquals(eventTask.getSink(), taskFromWorkflowTask.getSink());\n    }\n\n    @Test\n    public void testSetVariableConversion() {\n\n        SetVariable setVariableTask = new SetVariable(\"task_ref_name\");\n\n        WorkflowTask workflowTask = setVariableTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof SetVariable,\n                \"task is not of type SetVariable, but of type \"\n                        + fromWorkflowTask.getClass().getName());\n        SetVariable taskFromWorkflowTask = (SetVariable) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(setVariableTask.getName(), fromWorkflowTask.getName());\n        assertEquals(\n                setVariableTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(setVariableTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(setVariableTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(setVariableTask.getInput(), taskFromWorkflowTask.getInput());\n    }\n\n    @Test\n    public void testSubWorkflowConversion() {\n\n        SubWorkflow subWorkflowTask = new SubWorkflow(\"task_ref_name\", \"sub_flow\", 2);\n\n        WorkflowTask workflowTask = subWorkflowTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof SubWorkflow,\n                \"task is not of type SubWorkflow, but of type \"\n                        + fromWorkflowTask.getClass().getName());\n        SubWorkflow taskFromWorkflowTask = (SubWorkflow) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(subWorkflowTask.getName(), fromWorkflowTask.getName());\n        assertEquals(\n                subWorkflowTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(subWorkflowTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(subWorkflowTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(subWorkflowTask.getInput(), taskFromWorkflowTask.getInput());\n        assertEquals(subWorkflowTask.getWorkflowName(), taskFromWorkflowTask.getWorkflowName());\n        assertEquals(\n                subWorkflowTask.getWorkflowVersion(), taskFromWorkflowTask.getWorkflowVersion());\n    }\n\n    @Test\n    public void testSwitchConversion() {\n\n        SimpleTask task1 = new SimpleTask(\"task_name\", \"task_ref_name1\");\n        SimpleTask task2 = new SimpleTask(\"task_name\", \"task_ref_name2\");\n        SimpleTask task3 = new SimpleTask(\"task_name\", \"task_ref_name3\");\n\n        Switch decision = new Switch(\"switch\", \"${workflow.input.zip\");\n        decision.switchCase(\"caseA\", task1);\n        decision.switchCase(\"caseB\", task2, task3);\n\n        decision.defaultCase(\n                new Terminate(\"terminate\", Workflow.WorkflowStatus.FAILED, \"\", new HashMap<>()));\n\n        WorkflowTask workflowTask = decision.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof Switch,\n                \"task is not of type Switch, but of type \" + fromWorkflowTask.getClass().getName());\n        Switch taskFromWorkflowTask = (Switch) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(decision.getName(), fromWorkflowTask.getName());\n        assertEquals(decision.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(decision.getType(), taskFromWorkflowTask.getType());\n        assertEquals(decision.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(decision.getInput(), taskFromWorkflowTask.getInput());\n        // TODO: ADD CASES FOR DEFAULT CASE\n        assertEquals(decision.getBranches().keySet(), taskFromWorkflowTask.getBranches().keySet());\n        assertEquals(\n                decision.getBranches().values().stream()\n                        .map(\n                                tasks ->\n                                        tasks.stream()\n                                                .map(Task::getTaskReferenceName)\n                                                .collect(Collectors.toSet()))\n                        .collect(Collectors.toSet()),\n                taskFromWorkflowTask.getBranches().values().stream()\n                        .map(\n                                tasks ->\n                                        tasks.stream()\n                                                .map(Task::getTaskReferenceName)\n                                                .collect(Collectors.toSet()))\n                        .collect(Collectors.toSet()));\n        assertEquals(decision.getBranches().size(), taskFromWorkflowTask.getBranches().size());\n    }\n\n    @Test\n    public void testTerminateConversion() {\n\n        Terminate terminateTask =\n                new Terminate(\"terminate\", Workflow.WorkflowStatus.FAILED, \"\", new HashMap<>());\n\n        WorkflowTask workflowTask = terminateTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof Terminate,\n                \"task is not of type Terminate, but of type \"\n                        + fromWorkflowTask.getClass().getName());\n        Terminate taskFromWorkflowTask = (Terminate) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(terminateTask.getName(), fromWorkflowTask.getName());\n        assertEquals(terminateTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(terminateTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(terminateTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(terminateTask.getInput(), taskFromWorkflowTask.getInput());\n    }\n\n    @Test\n    public void testWaitConversion() {\n\n        Wait waitTask = new Wait(\"terminate\");\n\n        WorkflowTask workflowTask = waitTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof Wait,\n                \"task is not of type Wait, but of type \" + fromWorkflowTask.getClass().getName());\n        Wait taskFromWorkflowTask = (Wait) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(waitTask.getName(), fromWorkflowTask.getName());\n        assertEquals(waitTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(waitTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(waitTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(waitTask.getInput(), taskFromWorkflowTask.getInput());\n\n        // Wait for 10 seconds\n        waitTask = new Wait(\"wait_for_10_seconds\", Duration.of(10, ChronoUnit.SECONDS));\n        workflowTask = waitTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n        assertEquals(\"10s\", workflowTask.getInputParameters().get(Wait.DURATION_INPUT));\n\n        // Wait for 10 minutes\n        waitTask = new Wait(\"wait_for_10_seconds\", Duration.of(10, ChronoUnit.MINUTES));\n        workflowTask = waitTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n        assertEquals(\"600s\", workflowTask.getInputParameters().get(Wait.DURATION_INPUT));\n\n        // Wait till next week some time\n        ZonedDateTime nextWeek = ZonedDateTime.now().plusDays(7);\n        String formattedDateTime = Wait.dateTimeFormatter.format(nextWeek);\n        waitTask = new Wait(\"wait_till_next_week\", nextWeek);\n        workflowTask = waitTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n        assertEquals(formattedDateTime, workflowTask.getInputParameters().get(Wait.UNTIL_INPUT));\n    }\n\n    @Test\n    public void testHttpConverter() {\n\n        Http httpTask = new Http(\"terminate\");\n        Http.Input input = new Http.Input();\n        input.setUri(\"http://example.com\");\n        input.setMethod(Http.Input.HttpMethod.POST);\n        input.setBody(\"Hello World\");\n        input.setReadTimeOut(100);\n        Map<String, Object> headers = new HashMap<>();\n        headers.put(\"X-AUTHORIZATION\", \"my_api_key\");\n        input.setHeaders(headers);\n\n        httpTask.input(input);\n\n        WorkflowTask workflowTask = httpTask.getWorkflowDefTasks().get(0);\n        assertNotNull(workflowTask.getInputParameters());\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof Http,\n                \"task is not of type Http, but of type \" + fromWorkflowTask.getClass().getName());\n        Http taskFromWorkflowTask = (Http) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(httpTask.getName(), fromWorkflowTask.getName());\n        assertEquals(httpTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(httpTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(httpTask.getStartDelay(), taskFromWorkflowTask.getStartDelay());\n        assertEquals(httpTask.getInput(), taskFromWorkflowTask.getInput());\n        assertEquals(httpTask.getHttpRequest(), taskFromWorkflowTask.getHttpRequest());\n\n        System.out.println(httpTask.getInput());\n        System.out.println(taskFromWorkflowTask.getInput());\n    }\n\n    @Test\n    public void testJQTaskConversion() {\n        JQ jqTask = new JQ(\"task_name\", \"{ key3: (.key1.value1 + .key2.value2) }\");\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"key11\", \"value11\");\n        map.put(\"key12\", 100);\n\n        jqTask.input(\"key1\", \"value\");\n        jqTask.input(\"key2\", 42);\n        jqTask.input(\"key3\", true);\n        jqTask.input(\"key4\", map);\n\n        WorkflowTask workflowTask = jqTask.getWorkflowDefTasks().get(0);\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(fromWorkflowTask instanceof JQ, \"Found the instance \" + fromWorkflowTask);\n        JQ taskFromWorkflowTask = (JQ) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(jqTask.getName(), fromWorkflowTask.getName());\n        assertEquals(jqTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(jqTask.getQueryExpression(), taskFromWorkflowTask.getQueryExpression());\n        assertEquals(jqTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(jqTask.getInput(), taskFromWorkflowTask.getInput());\n    }\n\n    @Test\n    public void testInlineTaskConversion() {\n\n        Javascript inlineTask =\n                new Javascript(\n                        \"task_name\",\n                        \"function e() { if ($.value == 1){return {\\\"result\\\": true}} else { return {\\\"result\\\": false}}} e();\");\n        inlineTask.validate();\n\n        Map<String, Object> map = new HashMap<>();\n        map.put(\"key11\", \"value11\");\n        map.put(\"key12\", 100);\n\n        inlineTask.input(\"key1\", \"value\");\n        inlineTask.input(\"key2\", 42);\n        inlineTask.input(\"key3\", true);\n        inlineTask.input(\"key4\", map);\n\n        WorkflowTask workflowTask = inlineTask.getWorkflowDefTasks().get(0);\n\n        Task fromWorkflowTask = TaskRegistry.getTask(workflowTask);\n        assertTrue(\n                fromWorkflowTask instanceof Javascript, \"Found the instance \" + fromWorkflowTask);\n        Javascript taskFromWorkflowTask = (Javascript) fromWorkflowTask;\n\n        assertNotNull(fromWorkflowTask);\n        assertEquals(inlineTask.getName(), fromWorkflowTask.getName());\n        assertEquals(inlineTask.getTaskReferenceName(), fromWorkflowTask.getTaskReferenceName());\n        assertEquals(inlineTask.getExpression(), taskFromWorkflowTask.getExpression());\n        assertEquals(inlineTask.getType(), taskFromWorkflowTask.getType());\n        assertEquals(inlineTask.getInput(), taskFromWorkflowTask.getInput());\n    }\n\n    @Test\n    public void testJavascriptValidation() {\n        // This script has errors\n        Javascript inlineTask =\n                new Javascript(\n                        \"task_name\",\n                        \"function e() { if ($.value ==> 1){return {\\\"result\\\": true}} else { return {\\\"result\\\": false}}} e();\");\n        boolean failed = false;\n        try {\n            inlineTask.validate();\n        } catch (ValidationError ve) {\n            failed = true;\n        }\n\n        assertTrue(failed);\n\n        // This script does NOT have errors\n        inlineTask =\n                new Javascript(\n                        \"task_name\",\n                        \"function e() { if ($.value == 1){return {\\\"result\\\": true}} else { return {\\\"result\\\": false}}} e();\");\n        failed = false;\n        try {\n            inlineTask.validate();\n        } catch (ValidationError ve) {\n            failed = true;\n        }\n\n        assertFalse(failed);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/def/WorkflowCreationTests.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.sdk.testing.WorkflowTestRunner;\nimport com.netflix.conductor.sdk.workflow.def.tasks.*;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\nimport com.netflix.conductor.sdk.workflow.task.InputParam;\nimport com.netflix.conductor.sdk.workflow.task.OutputParam;\nimport com.netflix.conductor.sdk.workflow.task.WorkerTask;\nimport com.netflix.conductor.sdk.workflow.testing.TestWorkflowInput;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n@Disabled\npublic class WorkflowCreationTests {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowCreationTests.class);\n\n    private static WorkflowExecutor executor;\n\n    private static WorkflowTestRunner runner;\n\n    @BeforeAll\n    public static void init() throws IOException {\n        runner = new WorkflowTestRunner(8080, \"3.7.3\");\n        runner.init(\"com.netflix.conductor.sdk\");\n        executor = runner.getWorkflowExecutor();\n    }\n\n    @AfterAll\n    public static void cleanUp() {\n        runner.shutdown();\n    }\n\n    @WorkerTask(\"get_user_info\")\n    public @OutputParam(\"zipCode\") String getZipCode(@InputParam(\"name\") String userName) {\n        return \"95014\";\n    }\n\n    @WorkerTask(\"task2\")\n    public @OutputParam(\"greetings\") String task2() {\n        return \"Hello World\";\n    }\n\n    @WorkerTask(\"task3\")\n    public @OutputParam(\"greetings\") String task3() {\n        return \"Hello World-3\";\n    }\n\n    @WorkerTask(\"fork_gen\")\n    public DynamicForkInput generateDynamicFork() {\n        DynamicForkInput forks = new DynamicForkInput();\n        Map<String, Object> inputs = new HashMap<>();\n        forks.setInputs(inputs);\n        List<Task<?>> tasks = new ArrayList<>();\n        forks.setTasks(tasks);\n\n        for (int i = 0; i < 3; i++) {\n            SimpleTask task = new SimpleTask(\"task2\", \"fork_task_\" + i);\n            tasks.add(task);\n            HashMap<String, Object> taskInput = new HashMap<>();\n            taskInput.put(\"key\", \"value\");\n            taskInput.put(\"key2\", 101);\n            inputs.put(task.getTaskReferenceName(), taskInput);\n        }\n        return forks;\n    }\n\n    private ConductorWorkflow<TestWorkflowInput> registerTestWorkflow()\n            throws InterruptedException {\n        InputStream script = getClass().getResourceAsStream(\"/script.js\");\n        SimpleTask getUserInfo = new SimpleTask(\"get_user_info\", \"get_user_info\");\n        getUserInfo.input(\"name\", ConductorWorkflow.input.get(\"name\"));\n\n        SimpleTask sendToCupertino = new SimpleTask(\"task2\", \"cupertino\");\n        SimpleTask sendToNYC = new SimpleTask(\"task2\", \"nyc\");\n\n        int len = 4;\n        Task<?>[][] parallelTasks = new Task[len][1];\n        for (int i = 0; i < len; i++) {\n            parallelTasks[i][0] = new SimpleTask(\"task2\", \"task_parallel_\" + i);\n        }\n\n        WorkflowBuilder<TestWorkflowInput> builder = new WorkflowBuilder<>(executor);\n        TestWorkflowInput defaultInput = new TestWorkflowInput();\n        defaultInput.setName(\"defaultName\");\n\n        builder.name(\"sdk_workflow_example\")\n                .version(1)\n                .ownerEmail(\"hello@example.com\")\n                .description(\"Example Workflow\")\n                .restartable(true)\n                .variables(new WorkflowState())\n                .timeoutPolicy(WorkflowDef.TimeoutPolicy.TIME_OUT_WF, 100)\n                .defaultInput(defaultInput)\n                .add(new Javascript(\"js\", script))\n                .add(new ForkJoin(\"parallel\", parallelTasks))\n                .add(getUserInfo)\n                .add(\n                        new Switch(\"decide2\", \"${workflow.input.zipCode}\")\n                                .switchCase(\"95014\", sendToCupertino)\n                                .switchCase(\"10121\", sendToNYC))\n                // .add(new SubWorkflow(\"subflow\", \"sub_workflow_example\", 5))\n                .add(new SimpleTask(\"task2\", \"task222\"))\n                .add(new DynamicFork(\"dynamic_fork\", new SimpleTask(\"fork_gen\", \"fork_gen\")));\n\n        ConductorWorkflow<TestWorkflowInput> workflow = builder.build();\n        boolean registered = workflow.registerWorkflow(true, true);\n        assertTrue(registered);\n\n        return workflow;\n    }\n\n    @Test\n    public void verifyCreatedWorkflow() throws Exception {\n        ConductorWorkflow<TestWorkflowInput> conductorWorkflow = registerTestWorkflow();\n        WorkflowDef def = conductorWorkflow.toWorkflowDef();\n        assertNotNull(def);\n        assertTrue(\n                def.getTasks()\n                        .get(def.getTasks().size() - 2)\n                        .getType()\n                        .equals(TaskType.TASK_TYPE_FORK_JOIN_DYNAMIC));\n        assertTrue(\n                def.getTasks()\n                        .get(def.getTasks().size() - 1)\n                        .getType()\n                        .equals(TaskType.TASK_TYPE_JOIN));\n    }\n\n    @Test\n    public void verifyInlineWorkflowExecution() throws ValidationError {\n        TestWorkflowInput workflowInput = new TestWorkflowInput(\"username\", \"10121\", \"US\");\n        try {\n            Workflow run = registerTestWorkflow().execute(workflowInput).get(10, TimeUnit.SECONDS);\n            assertEquals(\n                    Workflow.WorkflowStatus.COMPLETED,\n                    run.getStatus(),\n                    run.getReasonForIncompletion());\n        } catch (Exception e) {\n            e.printStackTrace();\n            fail(e.getMessage());\n        }\n    }\n\n    @Test\n    public void testWorkflowExecutionByName() throws ExecutionException, InterruptedException {\n\n        // Register the workflow first\n        registerTestWorkflow();\n\n        TestWorkflowInput input = new TestWorkflowInput(\"username\", \"10121\", \"US\");\n\n        ConductorWorkflow<TestWorkflowInput> conductorWorkflow =\n                new ConductorWorkflow<TestWorkflowInput>(executor)\n                        .from(\"sdk_workflow_example\", null);\n\n        CompletableFuture<Workflow> execution = conductorWorkflow.execute(input);\n        try {\n            execution.get(10, TimeUnit.SECONDS);\n        } catch (Exception e) {\n            e.printStackTrace();\n            fail(e.getMessage());\n        }\n    }\n\n    @Test\n    public void verifyWorkflowExecutionFailsIfNotExists()\n            throws ExecutionException, InterruptedException {\n\n        // Register the workflow first\n        registerTestWorkflow();\n\n        TestWorkflowInput input = new TestWorkflowInput(\"username\", \"10121\", \"US\");\n\n        try {\n            ConductorWorkflow<TestWorkflowInput> conductorWorkflow =\n                    new ConductorWorkflow<TestWorkflowInput>(executor)\n                            .from(\"non_existent_workflow\", null);\n            conductorWorkflow.execute(input);\n            fail(\"execution should have failed\");\n        } catch (Exception e) {\n        }\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/def/WorkflowDefTaskTests.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def;\n\nimport org.junit.jupiter.api.Test;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.sdk.workflow.def.tasks.*;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class WorkflowDefTaskTests {\n\n    static {\n        WorkflowExecutor.initTaskImplementations();\n    }\n\n    @Test\n    public void testWorkflowDefTaskWithStartDelay() {\n        SimpleTask simpleTask = new SimpleTask(\"task_name\", \"task_ref_name\");\n        int startDelay = 5;\n\n        simpleTask.setStartDelay(startDelay);\n\n        WorkflowTask workflowTask = simpleTask.getWorkflowDefTasks().get(0);\n\n        assertEquals(simpleTask.getStartDelay(), workflowTask.getStartDelay());\n        assertEquals(startDelay, simpleTask.getStartDelay());\n        assertEquals(startDelay, workflowTask.getStartDelay());\n    }\n\n    @Test\n    public void testWorkflowDefTaskWithOptionalEnabled() {\n        SimpleTask simpleTask = new SimpleTask(\"task_name\", \"task_ref_name\");\n\n        simpleTask.setOptional(true);\n\n        WorkflowTask workflowTask = simpleTask.getWorkflowDefTasks().get(0);\n\n        assertEquals(simpleTask.getStartDelay(), workflowTask.getStartDelay());\n        assertEquals(true, simpleTask.isOptional());\n        assertEquals(true, workflowTask.isOptional());\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/def/WorkflowState.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.def;\n\npublic class WorkflowState {\n\n    private boolean paymentCompleted;\n\n    private int timeTaken;\n\n    public boolean isPaymentCompleted() {\n        return paymentCompleted;\n    }\n\n    public void setPaymentCompleted(boolean paymentCompleted) {\n        this.paymentCompleted = paymentCompleted;\n    }\n\n    public int getTimeTaken() {\n        return timeTaken;\n    }\n\n    public void setTimeTaken(int timeTaken) {\n        this.timeTaken = timeTaken;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/executor/task/AnnotatedWorkerTests.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\n\nimport com.netflix.conductor.client.automator.TaskRunnerConfigurer;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.worker.Worker;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.sdk.workflow.task.InputParam;\nimport com.netflix.conductor.sdk.workflow.task.OutputParam;\nimport com.netflix.conductor.sdk.workflow.task.WorkerTask;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.mock;\n\npublic class AnnotatedWorkerTests {\n\n    static class Car {\n        String brand;\n\n        String getBrand() {\n            return brand;\n        }\n\n        void setBrand(String brand) {\n            this.brand = brand;\n        }\n    }\n\n    static class Bike {\n        String brand;\n\n        String getBrand() {\n            return brand;\n        }\n\n        void setBrand(String brand) {\n            this.brand = brand;\n        }\n    }\n\n    static class CarWorker {\n        @WorkerTask(\"test_1\")\n        public @OutputParam(\"result\") List<Car> doWork(@InputParam(\"input\") List<Car> input) {\n            return input;\n        }\n    }\n\n    @Test\n    @DisplayName(\"it should handle null values when InputParam is a List\")\n    void nullListAsInputParam() throws NoSuchMethodException {\n        var worker = new CarWorker();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\", worker.getClass().getMethod(\"doWork\", List.class), worker);\n\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n        assertNull(outputData.get(\"result\"));\n    }\n\n    @Test\n    @DisplayName(\"it should handle an empty List as InputParam\")\n    void emptyListAsInputParam() throws NoSuchMethodException {\n        var worker = new CarWorker();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\", worker.getClass().getMethod(\"doWork\", List.class), worker);\n\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setInputData(Map.of(\"input\", List.of()));\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n\n        @SuppressWarnings(\"unchecked\")\n        List<Car> result = (List<Car>) outputData.get(\"result\");\n        assertTrue(result.isEmpty());\n    }\n\n    @Test\n    @DisplayName(\"it should handle a non empty List as InputParam\")\n    void nonEmptyListAsInputParam() throws NoSuchMethodException {\n        var worker = new CarWorker();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\", worker.getClass().getMethod(\"doWork\", List.class), worker);\n\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setInputData(Map.of(\"input\", List.of(Map.of(\"brand\", \"BMW\"))));\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n\n        @SuppressWarnings(\"unchecked\")\n        List<Car> result = (List<Car>) outputData.get(\"result\");\n        assertEquals(1, result.size());\n\n        Car car = result.get(0);\n        assertEquals(\"BMW\", car.getBrand());\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    static class RawListInput {\n        @WorkerTask(\"test_1\")\n        public @OutputParam(\"result\") List doWork(@InputParam(\"input\") List input) {\n            return input;\n        }\n    }\n\n    @Test\n    @DisplayName(\"it should handle a Raw List Type as InputParam\")\n    void rawListAsInputParam() throws NoSuchMethodException {\n        var worker = new RawListInput();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\", worker.getClass().getMethod(\"doWork\", List.class), worker);\n\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setInputData(Map.of(\"input\", List.of(Map.of(\"brand\", \"BMW\"))));\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n        assertEquals(task.getInputData().get(\"input\"), outputData.get(\"result\"));\n    }\n\n    static class MapInput {\n        @WorkerTask(\"test_1\")\n        public @OutputParam(\"result\") Map<String, Object> doWork(Map<String, Object> input) {\n            return input;\n        }\n    }\n\n    @Test\n    @DisplayName(\"it should accept a not annotated Map as input\")\n    void mapAsInputParam() throws NoSuchMethodException {\n        var worker = new MapInput();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\", worker.getClass().getMethod(\"doWork\", Map.class), worker);\n\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setInputData(Map.of(\"input\", List.of(Map.of(\"brand\", \"BMW\"))));\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n        assertEquals(task.getInputData(), outputData.get(\"result\"));\n    }\n\n    static class TaskInput {\n        @WorkerTask(\"test_1\")\n        public @OutputParam(\"result\") Task doWork(Task input) {\n            return input;\n        }\n    }\n\n    @Test\n    @DisplayName(\"it should accept a Task as input\")\n    void taskAsInputParam() throws NoSuchMethodException {\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setInputData(Map.of(\"input\", List.of(Map.of(\"brand\", \"BMW\"))));\n\n        var worker = new TaskInput();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\", worker.getClass().getMethod(\"doWork\", Task.class), worker);\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n        var result = (Task) outputData.get(\"result\");\n        assertEquals(result.getTaskId(), task.getTaskId());\n    }\n\n    @Retention(RetentionPolicy.RUNTIME)\n    @Target(ElementType.PARAMETER)\n    public @interface AnotherAnnotation {}\n\n    static class AnotherAnnotationInput {\n        @WorkerTask(\"test_2\")\n        public @OutputParam(\"result\") Bike doWork(@AnotherAnnotation Bike input) {\n            return input;\n        }\n    }\n\n    @Test\n    @DisplayName(\n            \"it should convert to the correct type even if there's no @InputParam and parameters are annotated with other annotations\")\n    void annotatedWithAnotherAnnotation() throws NoSuchMethodException {\n        var worker = new AnotherAnnotationInput();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\", worker.getClass().getMethod(\"doWork\", Bike.class), worker);\n\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setInputData(Map.of(\"brand\", \"Trek\"));\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n        var bike = (Bike) outputData.get(\"result\");\n        assertEquals(\"Trek\", bike.getBrand());\n    }\n\n    static class MultipleInputParams {\n        @WorkerTask(value = \"test_1\", threadCount = 3, pollingInterval = 333)\n        public Map<String, Object> doWork(\n                @InputParam(\"bike\") Bike bike, @InputParam(\"car\") Car car) {\n            return Map.of(\"bike\", bike, \"car\", car);\n        }\n    }\n\n    @Test\n    @DisplayName(\"it should handle multiple input params\")\n    void multipleInputParams() throws NoSuchMethodException {\n        var worker = new MultipleInputParams();\n        var annotatedWorker =\n                new AnnotatedWorker(\n                        \"test_1\",\n                        worker.getClass().getMethod(\"doWork\", Bike.class, Car.class),\n                        worker);\n\n        var task = new Task();\n        task.setStatus(Task.Status.IN_PROGRESS);\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setInputData(Map.of(\"bike\", Map.of(\"brand\", \"Trek\"), \"car\", Map.of(\"brand\", \"BMW\")));\n\n        var result0 = annotatedWorker.execute(task);\n        var outputData = result0.getOutputData();\n\n        var bike = (Bike) outputData.get(\"bike\");\n        assertEquals(\"Trek\", bike.getBrand());\n\n        var car = (Car) outputData.get(\"car\");\n        assertEquals(\"BMW\", car.getBrand());\n    }\n\n    @Test\n    @DisplayName(\"it should honor the polling interval from annotations and config\")\n    void pollingIntervalTest() throws NoSuchMethodException {\n        var config = new TestWorkerConfig();\n\n        var worker = new MultipleInputParams();\n\n        AnnotatedWorkerExecutor annotatedWorkerExecutor =\n                new AnnotatedWorkerExecutor(mock(TaskClient.class));\n        annotatedWorkerExecutor.addBean(worker);\n        annotatedWorkerExecutor.startPolling();\n        List<Worker> workers = annotatedWorkerExecutor.getExecutors();\n        assertNotNull(workers);\n        assertEquals(1, workers.size());\n        Worker taskWorker = workers.get(0);\n        assertEquals(333, taskWorker.getPollingInterval());\n\n        var worker2 = new AnotherAnnotationInput();\n        annotatedWorkerExecutor = new AnnotatedWorkerExecutor(mock(TaskClient.class));\n        annotatedWorkerExecutor.addBean(worker2);\n        annotatedWorkerExecutor.startPolling();\n        workers = annotatedWorkerExecutor.getExecutors();\n        assertNotNull(workers);\n        assertEquals(1, workers.size());\n        taskWorker = workers.get(0);\n        assertEquals(100, taskWorker.getPollingInterval());\n\n        config.setPollingInterval(\"test_2\", 123);\n        annotatedWorkerExecutor = new AnnotatedWorkerExecutor(mock(TaskClient.class), config);\n        annotatedWorkerExecutor.addBean(worker2);\n        annotatedWorkerExecutor.startPolling();\n        workers = annotatedWorkerExecutor.getExecutors();\n        assertNotNull(workers);\n        assertEquals(1, workers.size());\n        taskWorker = workers.get(0);\n        assertEquals(123, taskWorker.getPollingInterval());\n    }\n\n    @Test\n    @DisplayName(\"it should honor the polling interval from annotations and config\")\n    void threadCountTest() throws NoSuchMethodException {\n        var config = new TestWorkerConfig();\n\n        var worker = new MultipleInputParams();\n        var worker2 = new AnotherAnnotationInput();\n\n        AnnotatedWorkerExecutor annotatedWorkerExecutor =\n                new AnnotatedWorkerExecutor(mock(TaskClient.class), config);\n        annotatedWorkerExecutor.addBean(worker);\n        annotatedWorkerExecutor.addBean(worker2);\n\n        annotatedWorkerExecutor.startPolling();\n        TaskRunnerConfigurer runner = annotatedWorkerExecutor.getTaskRunner();\n        assertNotNull(runner);\n        Map<String, Integer> taskThreadCount = runner.getTaskThreadCount();\n\n        assertNotNull(taskThreadCount);\n        assertEquals(3, taskThreadCount.get(\"test_1\"));\n        assertEquals(1, taskThreadCount.get(\"test_2\"));\n\n        annotatedWorkerExecutor.shutdown();\n        config.setThreadCount(\"test_2\", 2);\n        annotatedWorkerExecutor = new AnnotatedWorkerExecutor(mock(TaskClient.class), config);\n        annotatedWorkerExecutor.addBean(worker);\n        annotatedWorkerExecutor.addBean(worker2);\n\n        annotatedWorkerExecutor.startPolling();\n        runner = annotatedWorkerExecutor.getTaskRunner();\n\n        taskThreadCount = runner.getTaskThreadCount();\n\n        assertNotNull(taskThreadCount);\n        assertEquals(3, taskThreadCount.get(\"test_1\"));\n        assertEquals(2, taskThreadCount.get(\"test_2\"));\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/executor/task/TestWorkerConfig.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.executor.task;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class TestWorkerConfig extends WorkerConfiguration {\n\n    private Map<String, Integer> pollingIntervals = new HashMap<>();\n\n    private Map<String, Integer> threadCounts = new HashMap<>();\n\n    @Override\n    public int getPollingInterval(String taskName) {\n        return pollingIntervals.getOrDefault(taskName, 0);\n    }\n\n    public void setPollingInterval(String taskName, int interval) {\n        pollingIntervals.put(taskName, interval);\n    }\n\n    public void setThreadCount(String taskName, int threadCount) {\n        threadCounts.put(taskName, threadCount);\n    }\n\n    @Override\n    public int getThreadCount(String taskName) {\n        return threadCounts.getOrDefault(taskName, 0);\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/testing/Task1Input.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.testing;\n\npublic class Task1Input {\n\n    private int mod;\n\n    private int oddEven;\n\n    public int getMod() {\n        return mod;\n    }\n\n    public void setMod(int mod) {\n        this.mod = mod;\n    }\n\n    public int getOddEven() {\n        return oddEven;\n    }\n\n    public void setOddEven(int oddEven) {\n        this.oddEven = oddEven;\n    }\n\n    @Override\n    public String toString() {\n        return \"Task1Input{\" + \"mod=\" + mod + \", oddEven=\" + oddEven + '}';\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/testing/TestWorkflowInput.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.testing;\n\npublic class TestWorkflowInput {\n\n    private String name;\n\n    private String zipCode;\n\n    private String countryCode;\n\n    public TestWorkflowInput(String name, String zipCode, String countryCode) {\n        this.name = name;\n        this.zipCode = zipCode;\n        this.countryCode = countryCode;\n    }\n\n    public TestWorkflowInput() {}\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getZipCode() {\n        return zipCode;\n    }\n\n    public void setZipCode(String zipCode) {\n        this.zipCode = zipCode;\n    }\n\n    public String getCountryCode() {\n        return countryCode;\n    }\n\n    public void setCountryCode(String countryCode) {\n        this.countryCode = countryCode;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/java/com/netflix/conductor/sdk/workflow/testing/WorkflowTestFrameworkTests.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.sdk.workflow.testing;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.sdk.testing.WorkflowTestRunner;\nimport com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;\nimport com.netflix.conductor.sdk.workflow.task.InputParam;\nimport com.netflix.conductor.sdk.workflow.task.OutputParam;\nimport com.netflix.conductor.sdk.workflow.task.WorkerTask;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class WorkflowTestFrameworkTests {\n\n    private static WorkflowTestRunner testRunner;\n\n    private static WorkflowExecutor executor;\n\n    @BeforeAll\n    public static void init() throws IOException {\n        testRunner = new WorkflowTestRunner(8080, \"3.7.3\");\n        testRunner.init(\"com.netflix.conductor.sdk.workflow.testing\");\n\n        executor = testRunner.getWorkflowExecutor();\n        executor.loadTaskDefs(\"/tasks.json\");\n        executor.loadWorkflowDefs(\"/simple_workflow.json\");\n    }\n\n    @AfterAll\n    public static void cleanUp() {\n        testRunner.shutdown();\n    }\n\n    @Test\n    public void testDynamicTaskExecuted() throws Exception {\n\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"task2Name\", \"task_2\");\n        input.put(\"mod\", \"1\");\n        input.put(\"oddEven\", \"12\");\n        input.put(\"number\", 0);\n\n        // Start the workflow and wait for it to complete\n        Workflow workflow = executor.executeWorkflow(\"Decision_TaskExample\", 1, input).get();\n\n        assertNotNull(workflow);\n        assertEquals(Workflow.WorkflowStatus.COMPLETED, workflow.getStatus());\n        assertNotNull(workflow.getOutput());\n        assertNotNull(workflow.getTasks());\n        assertFalse(workflow.getTasks().isEmpty());\n        assertTrue(\n                workflow.getTasks().stream()\n                        .anyMatch(task -> task.getTaskDefName().equals(\"task_6\")));\n\n        // task_2's implementation fails at the first try, so we should have to instances of task_2\n        // execution\n        // 2 executions of task_2 should be present\n        assertEquals(\n                2,\n                workflow.getTasks().stream()\n                        .filter(task -> task.getTaskDefName().equals(\"task_2\"))\n                        .count());\n        List<Task> task2Executions =\n                workflow.getTasks().stream()\n                        .filter(task -> task.getTaskDefName().equals(\"task_2\"))\n                        .collect(Collectors.toList());\n        assertNotNull(task2Executions);\n        assertEquals(2, task2Executions.size());\n\n        // First instance would have failed and second succeeded.\n        assertEquals(Task.Status.FAILED, task2Executions.get(0).getStatus());\n        assertEquals(Task.Status.COMPLETED, task2Executions.get(1).getStatus());\n\n        // task10's output\n        assertEquals(100, workflow.getOutput().get(\"c\"));\n    }\n\n    @Test\n    public void testWorkflowFailure() throws Exception {\n\n        Map<String, Object> input = new HashMap<>();\n        // task2Name is missing which will cause workflow to fail\n        input.put(\"mod\", \"1\");\n        input.put(\"oddEven\", \"12\");\n        input.put(\"number\", 0);\n\n        // we are missing task2Name parameter which is required to wire up dynamictask\n        // The workflow should fail as we are not passing it as input\n        Workflow workflow = executor.executeWorkflow(\"Decision_TaskExample\", 1, input).get();\n        assertNotNull(workflow);\n        assertEquals(Workflow.WorkflowStatus.FAILED, workflow.getStatus());\n        assertNotNull(workflow.getReasonForIncompletion());\n    }\n\n    @WorkerTask(\"task_1\")\n    public Map<String, Object> task1(Task1Input input) {\n        Map<String, Object> result = new HashMap<>();\n        result.put(\"input\", input);\n        return result;\n    }\n\n    @WorkerTask(\"task_2\")\n    public TaskResult task2(Task task) {\n        if (task.getRetryCount() < 1) {\n            task.setStatus(Task.Status.FAILED);\n            task.setReasonForIncompletion(\"try again\");\n            return new TaskResult(task);\n        }\n\n        task.setStatus(Task.Status.COMPLETED);\n        return new TaskResult(task);\n    }\n\n    @WorkerTask(\"task_6\")\n    public TaskResult task6(Task task) {\n        task.setStatus(Task.Status.COMPLETED);\n        return new TaskResult(task);\n    }\n\n    @WorkerTask(\"task_10\")\n    public TaskResult task10(Task task) {\n        task.setStatus(Task.Status.COMPLETED);\n        task.getOutputData().put(\"a\", \"b\");\n        task.getOutputData().put(\"c\", 100);\n        task.getOutputData().put(\"x\", false);\n        return new TaskResult(task);\n    }\n\n    @WorkerTask(\"task_8\")\n    public TaskResult task8(Task task) {\n        task.setStatus(Task.Status.COMPLETED);\n        return new TaskResult(task);\n    }\n\n    @WorkerTask(\"task_5\")\n    public TaskResult task5(Task task) {\n        task.setStatus(Task.Status.COMPLETED);\n        return new TaskResult(task);\n    }\n\n    @WorkerTask(\"task_3\")\n    public @OutputParam(\"z1\") String task3(@InputParam(\"taskToExecute\") String p1) {\n        return \"output of task3, p1=\" + p1;\n    }\n\n    @WorkerTask(\"task_30\")\n    public Map<String, Object> task30(Task task) {\n        Map<String, Object> output = new HashMap<>();\n        output.put(\"v1\", \"b\");\n        output.put(\"v2\", Arrays.asList(\"one\", \"two\", 3));\n        output.put(\"v3\", 5);\n        return output;\n    }\n\n    @WorkerTask(\"task_31\")\n    public Map<String, Object> task31(Task task) {\n        Map<String, Object> output = new HashMap<>();\n        output.put(\"a1\", \"b\");\n        output.put(\"a2\", Arrays.asList(\"one\", \"two\", 3));\n        output.put(\"a3\", 5);\n        return output;\n    }\n\n    @WorkerTask(\"HTTP\")\n    public Map<String, Object> http(Task task) {\n        Map<String, Object> output = new HashMap<>();\n        output.put(\"a1\", \"b\");\n        output.put(\"a2\", Arrays.asList(\"one\", \"two\", 3));\n        output.put(\"a3\", 5);\n        return output;\n    }\n\n    @WorkerTask(\"EVENT\")\n    public Map<String, Object> event(Task task) {\n        Map<String, Object> output = new HashMap<>();\n        output.put(\"a1\", \"b\");\n        output.put(\"a2\", Arrays.asList(\"one\", \"two\", 3));\n        output.put(\"a3\", 5);\n        return output;\n    }\n}\n"
  },
  {
    "path": "java-sdk/src/test/resources/application-integrationtest.properties",
    "content": "#\n# /*\n#  * Copyright 2021 Netflix, Inc.\n#  * <p>\n#  * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n#  * the License. You may obtain a copy of the License at\n#  * <p>\n#  * http://www.apache.org/licenses/LICENSE-2.0\n#  * <p>\n#  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n#  * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n#  * specific language governing permissions and limitations under the License.\n#  */\n#\n\nconductor.db.type=memory\nconductor.workflow-execution-lock.type=local_only\nconductor.external-payload-storage.type=mock\nconductor.indexing.enabled=false\n\nconductor.app.stack=test\nconductor.app.appId=conductor\n\nconductor.app.workflow-offset-timeout=30s\n\nconductor.system-task-workers.enabled=false\nconductor.app.system-task-worker-callback-duration=0\n\nconductor.app.event-message-indexing-enabled=true\nconductor.app.event-execution-indexing-enabled=true\n\nconductor.workflow-reconciler.enabled=true\nconductor.workflow-repair-service.enabled=false\n\nconductor.app.workflow-execution-lock-enabled=false\n\nconductor.app.workflow-input-payload-size-threshold=10KB\nconductor.app.max-workflow-input-payload-size-threshold=10240KB\nconductor.app.workflow-output-payload-size-threshold=10KB\nconductor.app.max-workflow-output-payload-size-threshold=10240KB\nconductor.app.task-input-payload-size-threshold=10KB\nconductor.app.max-task-input-payload-size-threshold=10240KB\nconductor.app.task-output-payload-size-threshold=10KB\nconductor.app.max-task-output-payload-size-threshold=10240KB\nconductor.app.max-workflow-variables-payload-size-threshold=2KB\n\nconductor.redis.availability-zone=us-east-1c\nconductor.redis.data-center-region=us-east-1\nconductor.redis.workflow-namespace-prefix=integration-test\nconductor.redis.queue-namespace-prefix=integtest\n\nconductor.elasticsearch.index-prefix=conductor\nconductor.elasticsearch.cluster-health-color=yellow\n"
  },
  {
    "path": "java-sdk/src/test/resources/log4j2.xml",
    "content": "<!--\n\n    Copyright 2020 Netflix, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n-->\n<!-- default log configuration -->\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"CONSOLE\">\n            <PatternLayout pattern=\"%-4r [%t] %-5p %c %x - %m%n\"/>\n        </Console>\n    </Appenders>\n\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"CONSOLE\" />\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "java-sdk/src/test/resources/script.js",
    "content": "function e() {\n    if ($.value > 1){\n        return {\n            \"key\": \"value\",\n            \"key2\": 42\n        };\n    } else {\n        return {};\n    }\n}\ne();"
  },
  {
    "path": "java-sdk/src/test/resources/simple_workflow.json",
    "content": "{\n  \"createTime\": 1635491472393,\n  \"updateTime\": 1635356450472,\n  \"name\": \"Decision_TaskExample\",\n  \"description\": \"Decision_TaskExample\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"decision_task\",\n      \"taskReferenceName\": \"decision_task\",\n      \"inputParameters\": {\n        \"case_value_param\": \"${workflow.input.number}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"case_value_param\",\n      \"decisionCases\": {\n        \"0\": [\n          {\n            \"name\": \"task_5\",\n            \"taskReferenceName\": \"task_5\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"dyntask\",\n            \"taskReferenceName\": \"task_2\",\n            \"inputParameters\": {\n              \"taskToExecute\":\"${workflow.input.task2Name}\"\n            },\n            \"type\": \"DYNAMIC\",\n            \"dynamicTaskNameParam\":\"taskToExecute\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"task_6\",\n            \"taskReferenceName\": \"task_6\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"1\": [\n          {\n            \"name\": \"task_8\",\n            \"taskReferenceName\": \"task_8\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"task_10\",\n            \"taskReferenceName\": \"task_10\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [\n        {\n          \"name\": \"task_8\",\n          \"taskReferenceName\": \"task_8_default\",\n          \"inputParameters\": {},\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"task_10\",\n      \"taskReferenceName\": \"task_10_last\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": true,\n  \"ownerEmail\": \"abc@example.com\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"variables\": {},\n  \"inputTemplate\": {}\n}"
  },
  {
    "path": "java-sdk/src/test/resources/tasks.json",
    "content": "[\n  {\n    \"createTime\": 1635656118884,\n    \"createdBy\": \"\",\n    \"name\": \"task_38\",\n    \"description\": \"task_38\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 1,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638846956,\n    \"createdBy\": \"\",\n    \"name\": \"encode\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 1200,\n    \"inputKeys\": [\n      \"fileLocation\"\n    ],\n    \"outputKeys\": [\n      \"encodeLocation\"\n    ],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 1,\n    \"responseTimeoutSeconds\": 1200,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 50,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"ownerEmail\": \"encode_admin@test.com\",\n    \"pollTimeoutSeconds\": 1200\n  },\n  {\n    \"createTime\": 1635656118436,\n    \"createdBy\": \"\",\n    \"name\": \"task_8\",\n    \"description\": \"task_8\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 1,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118873,\n    \"createdBy\": \"\",\n    \"name\": \"task_37\",\n    \"description\": \"task_37\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 1,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118460,\n    \"createdBy\": \"\",\n    \"name\": \"task_9\",\n    \"description\": \"task_9\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 1,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118390,\n    \"createdBy\": \"\",\n    \"name\": \"task_6\",\n    \"description\": \"task_6\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 1,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118861,\n    \"createdBy\": \"\",\n    \"name\": \"task_36\",\n    \"description\": \"task_36\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638847017,\n    \"createdBy\": \"\",\n    \"name\": \"collect_payment_task\",\n    \"description\": \"collect_payment_task\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118847,\n    \"createdBy\": \"\",\n    \"name\": \"task_35\",\n    \"description\": \"task_35\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118422,\n    \"createdBy\": \"\",\n    \"name\": \"task_7\",\n    \"description\": \"task_7\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118835,\n    \"createdBy\": \"\",\n    \"name\": \"task_34\",\n    \"description\": \"task_34\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118349,\n    \"createdBy\": \"\",\n    \"name\": \"task_4\",\n    \"description\": \"task_4\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118819,\n    \"createdBy\": \"\",\n    \"name\": \"task_33\",\n    \"description\": \"task_33\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118371,\n    \"createdBy\": \"\",\n    \"name\": \"task_5\",\n    \"description\": \"task_5\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118808,\n    \"createdBy\": \"\",\n    \"name\": \"task_32\",\n    \"description\": \"task_32\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118302,\n    \"createdBy\": \"\",\n    \"name\": \"task_2\",\n    \"description\": \"task_2\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 1,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118797,\n    \"createdBy\": \"\",\n    \"name\": \"task_31\",\n    \"description\": \"task_31\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118323,\n    \"createdBy\": \"\",\n    \"name\": \"task_3\",\n    \"description\": \"task_3\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118228,\n    \"createdBy\": \"\",\n    \"name\": \"task_0\",\n    \"description\": \"task_0\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118775,\n    \"createdBy\": \"\",\n    \"name\": \"task_30\",\n    \"description\": \"task_30\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118267,\n    \"createdBy\": \"\",\n    \"name\": \"task_1\",\n    \"description\": \"task_1\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638847161,\n    \"createdBy\": \"\",\n    \"name\": \"BookHotels\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"ui@example.com\"\n  },\n  {\n    \"createTime\": 1635638847170,\n    \"createdBy\": \"\",\n    \"name\": \"deploy\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 1200,\n    \"inputKeys\": [\n      \"fileLocation\"\n    ],\n    \"outputKeys\": [\n      \"deployLocation\"\n    ],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 600,\n    \"responseTimeoutSeconds\": 1200,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 50,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"ownerEmail\": \"encode_admin@test.com\",\n    \"pollTimeoutSeconds\": 1200\n  },\n  {\n    \"createTime\": 1635763310960,\n    \"createdBy\": \"\",\n    \"name\": \"ship_via_dhl\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 600,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 300,\n    \"responseTimeoutSeconds\": 300,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 2,\n    \"ownerEmail\": \"abc@example.com\",\n    \"pollTimeoutSeconds\": 1200\n  },\n  {\n    \"createTime\": 1635638847180,\n    \"createdBy\": \"\",\n    \"name\": \"StartBooking\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"ui@example.com\"\n  },\n  {\n    \"createTime\": 1635434088645,\n    \"createdBy\": \"\",\n    \"name\": \"Read_Name\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 600,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 300,\n    \"responseTimeoutSeconds\": 300,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 1,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"ownerEmail\": \"abc@example.com\",\n    \"pollTimeoutSeconds\": 1200\n  },\n  {\n    \"createTime\": 1635638847189,\n    \"createdBy\": \"\",\n    \"name\": \"book_flight_task\",\n    \"description\": \"book_flight_task\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638847198,\n    \"createdBy\": \"\",\n    \"name\": \"book_car_task\",\n    \"description\": \"book_car_task\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118758,\n    \"createdBy\": \"\",\n    \"name\": \"task_29\",\n    \"description\": \"task_29\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118747,\n    \"createdBy\": \"\",\n    \"name\": \"task_28\",\n    \"description\": \"task_28\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635278952348,\n    \"createdBy\": \"\",\n    \"name\": \"ship_via_ups\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 600,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 300,\n    \"responseTimeoutSeconds\": 300,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 2,\n    \"ownerEmail\": \"abc@example.com\",\n    \"pollTimeoutSeconds\": 1200\n  },\n  {\n    \"createTime\": 1635638847226,\n    \"createdBy\": \"\",\n    \"name\": \"image_convert_resize\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 1200,\n    \"inputKeys\": [\n      \"fileLocation\",\n      \"outputFormat\",\n      \"outputWidth\",\n      \"outputHeight\",\n      \"maintainAspectRatio\"\n    ],\n    \"outputKeys\": [\n      \"fileLocation\"\n    ],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 600,\n    \"responseTimeoutSeconds\": 1200,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 50,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"ownerEmail\": \"test@example.com\",\n    \"pollTimeoutSeconds\": 3600\n  },\n  {\n    \"createTime\": 1635638847238,\n    \"createdBy\": \"\",\n    \"name\": \"deposit_money\",\n    \"description\": \"deposit_money\",\n    \"retryCount\": 5,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 10,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118912,\n    \"createdBy\": \"\",\n    \"name\": \"search_elasticsearch\",\n    \"description\": \"search_elasticsearch\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118900,\n    \"createdBy\": \"\",\n    \"name\": \"task_39\",\n    \"description\": \"task_39\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118586,\n    \"createdBy\": \"\",\n    \"name\": \"task_16\",\n    \"description\": \"task_16\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635278952330,\n    \"createdBy\": \"\",\n    \"name\": \"shipping_info\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 600,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 300,\n    \"responseTimeoutSeconds\": 300,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 2,\n    \"ownerEmail\": \"abc@example.com\",\n    \"pollTimeoutSeconds\": 1200\n  },\n  {\n    \"createTime\": 1635656118567,\n    \"createdBy\": \"\",\n    \"name\": \"task_15\",\n    \"description\": \"task_15\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118545,\n    \"createdBy\": \"\",\n    \"name\": \"task_14\",\n    \"description\": \"task_14\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118528,\n    \"createdBy\": \"\",\n    \"name\": \"task_13\",\n    \"description\": \"task_13\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118513,\n    \"createdBy\": \"\",\n    \"name\": \"task_12\",\n    \"description\": \"task_12\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638847312,\n    \"createdBy\": \"\",\n    \"name\": \"withdraw_money\",\n    \"description\": \"withdraw_money\",\n    \"retryCount\": 5,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 10,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118495,\n    \"createdBy\": \"\",\n    \"name\": \"task_11\",\n    \"description\": \"task_11\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118480,\n    \"createdBy\": \"\",\n    \"name\": \"task_10\",\n    \"description\": \"task_10\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"updateTime\": 1636574526469,\n    \"createdBy\": \"user\",\n    \"updatedBy\": \"\",\n    \"name\": \"sample_task_name_1\",\n    \"description\": \"This is a sample task for demo\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 30,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 5,\n    \"responseTimeoutSeconds\": 10,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1636051623273,\n    \"createdBy\": \"\",\n    \"name\": \"order_details\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"ownerEmail\": \"abc@example.com\"\n  },\n  {\n    \"createTime\": 1635638847343,\n    \"createdBy\": \"\",\n    \"name\": \"CompleteFlightBooking\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"ui@example.com\"\n  },\n  {\n    \"createTime\": 1635278952339,\n    \"createdBy\": \"\",\n    \"name\": \"ship_via_fedex\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 600,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 300,\n    \"responseTimeoutSeconds\": 300,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 2,\n    \"ownerEmail\": \"abc@example.com\",\n    \"pollTimeoutSeconds\": 1200\n  },\n  {\n    \"createTime\": 1635638847353,\n    \"createdBy\": \"\",\n    \"name\": \"map_state_codes\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 300,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 10,\n    \"responseTimeoutSeconds\": 180,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@gmail.com\"\n  },\n  {\n    \"createTime\": 1635638847362,\n    \"createdBy\": \"\",\n    \"name\": \"compute_median_top_states\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 300,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 10,\n    \"responseTimeoutSeconds\": 180,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@gmail.com\"\n  },\n  {\n    \"createTime\": 1635638847374,\n    \"createdBy\": \"\",\n    \"name\": \"scaleS3Image\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 300,\n    \"inputKeys\": [\n      \"inputBucketName\",\n      \"inputKeyName\",\n      \"scalingFactor\",\n      \"outputBucketName\",\n      \"outputKeyName\"\n    ],\n    \"outputKeys\": [\n      \"response\"\n    ],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 10,\n    \"responseTimeoutSeconds\": 180,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"conductor@example.com\"\n  },\n  {\n    \"createTime\": 1635656118736,\n    \"createdBy\": \"\",\n    \"name\": \"task_27\",\n    \"description\": \"task_27\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118725,\n    \"createdBy\": \"\",\n    \"name\": \"task_26\",\n    \"description\": \"task_26\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118713,\n    \"createdBy\": \"\",\n    \"name\": \"task_25\",\n    \"description\": \"task_25\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118700,\n    \"createdBy\": \"\",\n    \"name\": \"task_24\",\n    \"description\": \"task_24\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635878631466,\n    \"createdBy\": \"\",\n    \"name\": \"task_23\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 600,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 600,\n    \"responseTimeoutSeconds\": 300,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 50,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"pollTimeoutSeconds\": 600,\n    \"ownerEmail\": \"test@example.com\"\n  },\n  {\n    \"createTime\": 1635878631456,\n    \"createdBy\": \"\",\n    \"name\": \"task_22\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 600,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 600,\n    \"responseTimeoutSeconds\": 300,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 50,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"pollTimeoutSeconds\": 600,\n    \"ownerEmail\": \"test@example.com\"\n  },\n  {\n    \"createTime\": 1635638847436,\n    \"createdBy\": \"\",\n    \"name\": \"send_email_task\",\n    \"description\": \"send_email_task\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118658,\n    \"createdBy\": \"\",\n    \"name\": \"task_21\",\n    \"description\": \"task_21\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638847459,\n    \"createdBy\": \"\",\n    \"name\": \"image_multiple_convert_resize\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 1200,\n    \"inputKeys\": [\n      \"fileLocation\",\n      \"outputFormats\",\n      \"outputSizes\",\n      \"maintainAspectRatio\"\n    ],\n    \"outputKeys\": [\n      \"dynamicTasks\",\n      \"dynamicTasksInput\"\n    ],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 600,\n    \"responseTimeoutSeconds\": 1200,\n    \"concurrentExecLimit\": 100,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 50,\n    \"rateLimitFrequencyInSeconds\": 60,\n    \"ownerEmail\": \"exampl@example.com\",\n    \"pollTimeoutSeconds\": 3600\n  },\n  {\n    \"createTime\": 1635656118644,\n    \"createdBy\": \"\",\n    \"name\": \"task_20\",\n    \"description\": \"task_20\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638847477,\n    \"createdBy\": \"\",\n    \"name\": \"simple_worker\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 300,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 10,\n    \"responseTimeoutSeconds\": 180,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@gmail.com\"\n  },\n  {\n    \"createTime\": 1635638847486,\n    \"createdBy\": \"\",\n    \"name\": \"book_hotel_task\",\n    \"description\": \"book_hotel_task\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 1200,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635638847495,\n    \"createdBy\": \"\",\n    \"name\": \"watermarkS3Image\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 300,\n    \"inputKeys\": [\n      \"inputBucketName\",\n      \"inputKeyName\",\n      \"watermarkBucketName\",\n      \"watermarkKeyName\",\n      \"outputBucketName\",\n      \"outputKeyName\"\n    ],\n    \"outputKeys\": [\n      \"response\"\n    ],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 10,\n    \"responseTimeoutSeconds\": 180,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"conductor@example.com\"\n  },\n  {\n    \"createTime\": 1635656118631,\n    \"createdBy\": \"\",\n    \"name\": \"task_19\",\n    \"description\": \"task_19\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118616,\n    \"createdBy\": \"\",\n    \"name\": \"task_18\",\n    \"description\": \"task_18\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  {\n    \"createTime\": 1635656118601,\n    \"createdBy\": \"\",\n    \"name\": \"task_17\",\n    \"description\": \"task_17\",\n    \"retryCount\": 1,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 60,\n    \"responseTimeoutSeconds\": 3600,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"example@email.com\"\n  }\n]"
  },
  {
    "path": "java-sdk/testing_framework.md",
    "content": "# Unit Testing Framework for Workflows\n\nThe framework allows you to test the workflow definitions against a specific version of Conductor server.\n\nThe unit tests allow the following:\n1. **Input/Output Wiring**: Ensure the tasks are wired up correctly.\n2. **Parameter check**: Workflow behavior with missing mandatory parameters is expected (fail if required).\n3. **Task Failure behavior**: Ensure the task definitions have the right number of retries etc.  \n   For example, if the task is not idempotent, it does not get retried.\n4. **Branch Testing**: Given a specific input, ensure the workflow executes a specific branch of the fork/decision.\n\nThe local test server is self-contained with no additional dependencies required and stores all the data\nin memory.  Once the test completes, the server is terminated and all the data is wiped out.\n\n## Unit Testing Frameworks\nThe unit testing framework is agnostic to the framework you use for testing and can be easily integrated into \nJUnit, Spock and other testing frameworks being used.\n\n## Setting Up Local Server for Testing​\n\n```java\n//Setup method  code - should be called once per the test lifecycle\n//e.g. @BeforeClass in JUnit\n\n//Download the published conductor server version 3.5.2 \n//Start the local server at port 8096\ntestRunner = new WorkflowTestRunner(8096, \"3.5.2\");\n\n//Scan the packages for task workers\ntestRunner.init(\"com.netflix.conductor.testing.workflows\");\n\n//Get the executor instance used for  loading workflows \nexecutor = testRunner.getWorkflowExecutor();\n```\n\nClean up method:\n```java\n//Clean up method code -- place in a clean up method e.g. @AfterClass in Junit\n\n//Shutdown local workers and servers and clean up any local resources in use.\ntestRunner.shutdown();\n```\n\nLoading workflows from JSON files for testing:\n```java\nexecutor.loadTaskDefs(\"/tasks.json\");\nexecutor.loadWorkflowDefs(\"/simple_workflow.json\");\n```\n\n## Sample test code that starts a workflow and verifies its execution\n\n```java\nGetInsuranceQuote getQuote = new GetInsuranceQuote();\ngetQuote.setName(\"personA\");\ngetQuote.setAmount(1000000.0);\ngetQuote.setZipCode(\"10121\");\n\n// Start the workflow and wait for it to complete\nCompletableFuture<Workflow> workflowFuture = executor.executeWorkflow(\"InsuranceQuoteWorkflow\", 1, getQuote);\n\n//Wait for the workflow execution to complete\nWorkflow workflow = workflowFuture.get();\n\n//Assertions\nassertNotNull(workflow);\nassertEquals(Workflow.WorkflowStatus.COMPLETED, workflow.getStatus());\nassertNotNull(workflow.getOutput());\nassertNotNull(workflow.getTasks());\nassertFalse(workflow.getTasks().isEmpty());\nassertTrue(workflow.getTasks().stream().anyMatch(task -> task.getTaskDefName().equals(\"task_6\")));\n```\n\n\n\n"
  },
  {
    "path": "java-sdk/worker_sdk.md",
    "content": "# Worker SDK\nWorker SDK makes it easy to write Conductor workers which are strongly typed with specific inputs and outputs.\n\nAnnotations for the worker methods:\n\n* `@WorkerTask` - When annotated, convert a method to a Conductor worker.\n* `@InputParam` - Name of the input parameter to bind to from the task's input.\n* `@OutputParam` - Name of the output key of the task's output.\n\nPlease note inputs and outputs to a task in Conductor are JSON documents.\n\n\n**Examples**\n\nCreate a worker named `task1` that gets Task as input and produces TaskResult as output.\n```java\n@WorkerTask(\"task1\")\n    public TaskResult task1(Task task) {\n        task.setStatus(Task.Status.COMPLETED);\n        return new TaskResult(task);\n    }\n```\n\nCreate a worker named `task2` that takes the `name` as a String input and produces an output `return \"Hello, \" + name`\n\n```java\n@WorkerTask(\"task2\")\npublic @OutputParam(\"greetings\") String task2(@InputParam(\"name\") String name) {\n    return \"Hello, \" + name;\n}\n```\nExample Task Input/Output\n\nInput:\n```json\n{\n   \"name\": \"conductor\"\n}\n```\n\nOutput:\n```json\n{\n   \"greetings\": \"Hello, conductor\"\n}\n```\nA worker that takes complex java type as input and produces the complex output:\n```java\n@WorkerTask(\"get_insurance_quote\")\n public InsuranceQuote getInsuranceQuote(GetInsuranceQuote quoteInput) {\n     InsuranceQuote quote = new InsuranceQuote();\n     //Implementation\n     return quote;\n }\n```\n\nExample Task Input/Output\n\nInput:\n```json\n{\n   \"name\": \"personA\",\n   \"zipCode\": \"10121\",\n   \"amount\": 1000000\n}\n```\n\nOutput:\n```json\n{\n   \"name\": \"personA\",\n   \"quotedPremium\": 123.50,\n   \"quotedAmount\": 1000000\n}\n```\n\n## Managing Task Workers\nAnnotated Workers are managed by [WorkflowExecutor](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/executor/WorkflowExecutor.java)\n\n### Start Workers\n```java\nWorkflowExecutor executor = new WorkflowExecutor(\"http://server/api/\");\n//List of packages  (comma separated) to scan for annotated workers.  \n// Please note, the worker method MUST be public and the class in which they are defined\n//MUST have a no-args constructor        \nexecutor.initWorkers(\"com.company.package1,com.company.package2\");\n```\n\n### Stop Workers\nThe code fragment to stop workers at shutdown of the application.\n```java\nexecutor.shutdown();\n```\n\n### Unit Testing Workers\nWorkers implemented with the annotations are regular Java methods that can be unit tested with any testing framework.\n\n#### Mock Workers for Workflow Testing​\nCreate a mock worker in a different package (e.g., test) and scan for these packages when loading up the workers for integration testing.\n\nSee [Unit Testing Framework](testing_framework.md) for more details on testing.\n\n## Best Practices\nIn a typical production environment, you will have multiple workers across different machines/VMs/pods polling for the same task.\nAs with all Conductor workers, the following best practices apply:\n\n1. Workers should be stateless and should not maintain any state on the process they are running.\n2. Ideally, workers should be idempotent.\n3. The worker should follow the Single Responsibility Principle and do exactly one thing they are responsible for.\n4. The worker should not embed any workflow logic - i.e., scheduling another worker, sending a message, etc. The Conductor has features to do this, making it possible to decouple your workflow logic from worker implementation.\n\n\n\n\n\n\n\n"
  },
  {
    "path": "java-sdk/workflow_sdk.md",
    "content": "# Workflow SDK\nWorkflow SDK provides fluent API to create workflows with strongly typed interfaces.\n\n## APIs\n### ConductorWorkflow\n[ConductorWorkflow](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/ConductorWorkflow.java) is the SDK representation of a Conductor workflow.\n\n#### Create a `ConductorWorkflow` Instance\n```java\nConductorWorkflow<GetInsuranceQuote> conductorWorkflow = new WorkflowBuilder<GetInsuranceQuote>(executor)\n    .name(\"sdk_workflow_example\")\n    .version(1)\n    .ownerEmail(\"hello@example.com\")\n    .description(\"Example Workflow\")\n    .timeoutPolicy(WorkflowDef.TimeoutPolicy.TIME_OUT_WF, 100)\n    .add(new SimpleTask(\"calculate_insurance_premium\", \"calculate_insurance_premium\"))\n    .add(new SimpleTask(\"send_email\", \"send_email\"))\n    .build();\n```\n### Working with Simple Worker Tasks\nUse [SimpleTask](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/SimpleTask.java) to add a simple task to a workflow.\n\nExample:\n```java\n...\nbuilder.add(new SimpleTask(\"send_email\", \"send_email\"))\n...\n```\n### Wiring Inputs to Task\nUse `input` methods to configure the inputs to the task.\n\nSee our doc on [task inputs](https://conductor.netflix.com/how-tos/Tasks/task-inputs.html) for more details.\n\nExample\n```java\nbuilder.add(\n        new SimpleTask(\"send_email\", \"send_email\")\n                .input(\"email\", \"${workflow.input.email}\")\n                .input(\"subject\", \"Your insurance quote for the amount ${generate_quote.output.amount}\")\n);\n```\n\n### Working with Operators\nEach operator has its own class that can be added to the workflow builder.\n\n* [ForkJoin](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/ForkJoin.java) \n* [Wait](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Wait.java)\n* [Switch](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Switch.java)\n* [DynamicFork](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/DynamicFork.java)\n* [DoWhile](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/DoWhile.java)\n* [Join](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Join.java)\n* [Dynamic](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Dynamic.java)\n* [Terminate](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/Terminate.java)\n* [SubWorkflow](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/SubWorkflow.java)\n* [SetVariable](https://github.com/Netflix/conductor/blob/main/java-sdk/src/main/java/com/netflix/conductor/sdk/workflow/def/tasks/SetVariable.java)\n\n\n#### Register Workflow with Conductor Server\n```java\n//Returns true if the workflow is successfully created\n//Reasons why this method will return false\n//1. Network connectivity issue\n//2. Workflow already exists with the specified name and version \n//3. There are missing task definitions\nboolean registered = workflow.registerWorkflow();\n```\n#### Overwrite Existing Workflow Definition​\n```java\nboolean registered = workflow.registerWorkflow(true);\n```\n\n#### Overwrite existing workflow definitions & registering any missing task definitions\n```java\nboolean registered = workflow.registerWorkflow(true, true);\n```\n\n#### Create `ConductorWorkflow` based on the definition registered on the server\n\n```java\nConductorWorkflow<GetInsuranceQuote> conductorWorkflow = \n                        new ConductorWorkflow<GetInsuranceQuote>(executor)\n                        .from(\"sdk_workflow_example\", 1);\n```\n\n#### Start Workflow Execution\nStart the execution of the workflow based on the definition registered on the server. Use the register method to register a workflow on the server before executing.\n\n```java\n\n//Returns a completable future\nCompletableFuture<Workflow> execution = conductorWorkflow.execute(input);\n\n//Wait for the workflow to complete -- useful if workflow completes within a reasonable amount of time\nWorkflow workflowRun = execution.get();\n\n//Get the workflowId\nString workflowId = workflowRun.getWorkflowId();\n\n//Get the status of workflow execution\nWorkflowStatus status = workflowRun.getStatus();\n```\nSee [Workflow](https://github.com/Netflix/conductor/blob/main/common/src/main/java/com/netflix/conductor/common/run/Workflow.java) for more details on the Workflow object.\n\n#### Start Dynamic Workflow Execution\nDynamic workflows are executed by specifying the workflow definition along with the execution and do not require registering the workflow on the server before executing.\n\n##### Use cases for dynamic workflows\n1. Each workflow run has a unique workflow definition \n2. Workflows are defined based on the user data and cannot be modeled ahead of time statically \n\n```java\n//1. Use WorkflowBuilder to create ConductorWorkflow.\n//2. Execute using the definition created by SDK.\nCompletableFuture<Workflow> execution = conductorWorkflow.executeDynamic(input);\n\n```\n\n\n\n\n\n\n"
  },
  {
    "path": "json-jq-task/build.gradle",
    "content": "/*\n *  Copyright 2022 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\ndependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n\n    implementation \"net.thisptr:jackson-jq:${revJq}\"\n    implementation \"com.github.ben-manes.caffeine:caffeine\"\n}\n"
  },
  {
    "path": "json-jq-task/src/main/java/com/netflix/conductor/tasks/json/JsonJqTransform.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.tasks.json;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.benmanes.caffeine.cache.CacheLoader;\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport com.github.benmanes.caffeine.cache.LoadingCache;\nimport net.thisptr.jackson.jq.JsonQuery;\nimport net.thisptr.jackson.jq.Scope;\n\n@Component(JsonJqTransform.NAME)\npublic class JsonJqTransform extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(JsonJqTransform.class);\n    public static final String NAME = \"JSON_JQ_TRANSFORM\";\n    private static final String QUERY_EXPRESSION_PARAMETER = \"queryExpression\";\n    private static final String OUTPUT_RESULT = \"result\";\n    private static final String OUTPUT_RESULT_LIST = \"resultList\";\n    private static final String OUTPUT_ERROR = \"error\";\n    private static final TypeReference<Map<String, Object>> mapType = new TypeReference<>() {};\n    private final TypeReference<List<Object>> listType = new TypeReference<>() {};\n    private final Scope rootScope;\n    private final ObjectMapper objectMapper;\n    private final LoadingCache<String, JsonQuery> queryCache = createQueryCache();\n\n    @Autowired\n    public JsonJqTransform(ObjectMapper objectMapper) {\n        super(NAME);\n        this.objectMapper = objectMapper;\n        this.rootScope = Scope.newEmptyScope();\n        this.rootScope.loadFunctions(Scope.class.getClassLoader());\n    }\n\n    @Override\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n        final Map<String, Object> taskInput = task.getInputData();\n\n        final String queryExpression = (String) taskInput.get(QUERY_EXPRESSION_PARAMETER);\n\n        if (queryExpression == null) {\n            task.setReasonForIncompletion(\n                    \"Missing '\" + QUERY_EXPRESSION_PARAMETER + \"' in input parameters\");\n            task.setStatus(TaskModel.Status.FAILED);\n            return;\n        }\n\n        try {\n            final JsonNode input = objectMapper.valueToTree(taskInput);\n            final JsonQuery query = queryCache.get(queryExpression);\n\n            final Scope childScope = Scope.newChildScope(rootScope);\n\n            final List<JsonNode> result = query.apply(childScope, input);\n\n            task.setStatus(TaskModel.Status.COMPLETED);\n            if (result == null) {\n                task.addOutput(OUTPUT_RESULT, null);\n                task.addOutput(OUTPUT_RESULT_LIST, null);\n            } else if (result.isEmpty()) {\n                task.addOutput(OUTPUT_RESULT, null);\n                task.addOutput(OUTPUT_RESULT_LIST, result);\n            } else {\n                task.addOutput(OUTPUT_RESULT, extractBody(result.get(0)));\n                task.addOutput(OUTPUT_RESULT_LIST, result);\n            }\n        } catch (final Exception e) {\n            LOGGER.error(\n                    \"Error executing task: {} in workflow: {}\",\n                    task.getTaskId(),\n                    workflow.getWorkflowId(),\n                    e);\n            task.setStatus(TaskModel.Status.FAILED);\n            final String message = extractFirstValidMessage(e);\n            task.setReasonForIncompletion(message);\n            task.addOutput(OUTPUT_ERROR, message);\n        }\n    }\n\n    private LoadingCache<String, JsonQuery> createQueryCache() {\n        final CacheLoader<String, JsonQuery> loader = JsonQuery::compile;\n        return Caffeine.newBuilder()\n                .expireAfterWrite(1, TimeUnit.HOURS)\n                .maximumSize(1000)\n                .build(loader);\n    }\n\n    @Override\n    public boolean execute(\n            WorkflowModel workflow, TaskModel task, WorkflowExecutor workflowExecutor) {\n        this.start(workflow, task, workflowExecutor);\n        return true;\n    }\n\n    private String extractFirstValidMessage(final Exception e) {\n        Throwable currentStack = e;\n        final List<String> messages = new ArrayList<>();\n        messages.add(currentStack.getMessage());\n        while (currentStack.getCause() != null) {\n            currentStack = currentStack.getCause();\n            messages.add(currentStack.getMessage());\n        }\n        return messages.stream().filter(it -> !it.contains(\"N/A\")).findFirst().orElse(\"\");\n    }\n\n    private Object extractBody(JsonNode node) {\n        if (node.isNull()) {\n            return null;\n        } else if (node.isObject()) {\n            return objectMapper.convertValue(node, mapType);\n        } else if (node.isArray()) {\n            return objectMapper.convertValue(node, listType);\n        } else if (node.isBoolean()) {\n            return node.asBoolean();\n        } else if (node.isNumber()) {\n            if (node.isIntegralNumber()) {\n                return node.asLong();\n            } else {\n                return node.asDouble();\n            }\n        } else {\n            return node.asText();\n        }\n    }\n}\n"
  },
  {
    "path": "json-jq-task/src/test/java/com/netflix/conductor/tasks/json/JsonJqTransformTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.tasks.json;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.config.ObjectMapperProvider;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.*;\n\npublic class JsonJqTransformTest {\n\n    private final ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();\n\n    @Test\n    public void dataShouldBeCorrectlySelected() {\n        final JsonJqTransform jsonJqTransform = new JsonJqTransform(objectMapper);\n        final WorkflowModel workflow = new WorkflowModel();\n        final TaskModel task = new TaskModel();\n        final Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"queryExpression\", \".inputJson.key[0]\");\n        final Map<String, Object> inputJson = new HashMap<>();\n        inputJson.put(\"key\", Collections.singletonList(\"VALUE\"));\n        inputData.put(\"inputJson\", inputJson);\n        task.setInputData(inputData);\n        task.setOutputData(new HashMap<>());\n\n        jsonJqTransform.start(workflow, task, null);\n\n        assertNull(task.getOutputData().get(\"error\"));\n        assertEquals(\"VALUE\", task.getOutputData().get(\"result\").toString());\n        assertEquals(\"[\\\"VALUE\\\"]\", task.getOutputData().get(\"resultList\").toString());\n    }\n\n    @Test\n    public void simpleErrorShouldBeDisplayed() {\n        final JsonJqTransform jsonJqTransform = new JsonJqTransform(objectMapper);\n        final WorkflowModel workflow = new WorkflowModel();\n        final TaskModel task = new TaskModel();\n        final Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"queryExpression\", \"{\");\n        task.setInputData(inputData);\n        task.setOutputData(new HashMap<>());\n\n        jsonJqTransform.start(workflow, task, null);\n\n        assertTrue(\n                ((String) task.getOutputData().get(\"error\"))\n                        .startsWith(\"Encountered \\\"<EOF>\\\" at line 1, column 1.\"));\n    }\n\n    @Test\n    public void nestedExceptionsWithNACausesShouldBeDisregarded() {\n        final JsonJqTransform jsonJqTransform = new JsonJqTransform(objectMapper);\n        final WorkflowModel workflow = new WorkflowModel();\n        final TaskModel task = new TaskModel();\n        final Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\n                \"queryExpression\",\n                \"{officeID: (.inputJson.OIDs | unique)[], requestedIndicatorList: .inputJson.requestedindicatorList}\");\n        final Map<String, Object> inputJson = new HashMap<>();\n        inputJson.put(\"OIDs\", Collections.singletonList(\"VALUE\"));\n        final Map<String, Object> indicatorList = new HashMap<>();\n        indicatorList.put(\"indicator\", \"AFA\");\n        indicatorList.put(\"value\", false);\n        inputJson.put(\"requestedindicatorList\", Collections.singletonList(indicatorList));\n        inputData.put(\"inputJson\", inputJson);\n        task.setInputData(inputData);\n        task.setOutputData(new HashMap<>());\n\n        jsonJqTransform.start(workflow, task, null);\n\n        assertTrue(\n                ((String) task.getOutputData().get(\"error\"))\n                        .startsWith(\"Encountered \\\" \\\"[\\\" \\\"[ \\\"\\\" at line 1\"));\n    }\n\n    @Test\n    public void mapResultShouldBeCorrectlyExtracted() {\n        final JsonJqTransform jsonJqTransform = new JsonJqTransform(objectMapper);\n        final WorkflowModel workflow = new WorkflowModel();\n        final TaskModel task = new TaskModel();\n        final Map<String, Object> taskInput = new HashMap<>();\n        Map<String, Object> inputData = new HashMap<>();\n        inputData.put(\"method\", \"POST\");\n        inputData.put(\"successExpression\", null);\n        inputData.put(\"requestTransform\", \"{name: (.body.name + \\\" you are a \\\" + .body.title) }\");\n        inputData.put(\"responseTransform\", \"{result: \\\"reply: \\\" + .response.body.message}\");\n        taskInput.put(\"input\", inputData);\n        taskInput.put(\n                \"queryExpression\",\n                \"{ requestTransform: .input.requestTransform // \\\".body\\\"  , responseTransform: .input.responseTransform // \\\".response.body\\\", method: .input.method // \\\"GET\\\", document: .input.document // \\\"rgt_results\\\", successExpression: .input.successExpression // \\\"true\\\"   }\");\n        task.setInputData(taskInput);\n        task.setOutputData(new HashMap<>());\n\n        jsonJqTransform.start(workflow, task, null);\n\n        assertNull(task.getOutputData().get(\"error\"));\n        assertTrue(task.getOutputData().get(\"result\") instanceof Map);\n        HashMap<String, Object> result =\n                (HashMap<String, Object>) task.getOutputData().get(\"result\");\n        assertEquals(\"POST\", result.get(\"method\"));\n        assertEquals(\n                \"{name: (.body.name + \\\" you are a \\\" + .body.title) }\",\n                result.get(\"requestTransform\"));\n        assertEquals(\n                \"{result: \\\"reply: \\\" + .response.body.message}\", result.get(\"responseTransform\"));\n    }\n\n    @Test\n    public void stringResultShouldBeCorrectlyExtracted() {\n        final JsonJqTransform jsonJqTransform = new JsonJqTransform(objectMapper);\n        final WorkflowModel workflow = new WorkflowModel();\n        final TaskModel task = new TaskModel();\n        final Map<String, Object> taskInput = new HashMap<>();\n        taskInput.put(\"data\", new ArrayList<>());\n        taskInput.put(\n                \"queryExpression\", \"if(.data | length >0) then \\\"EXISTS\\\" else \\\"CREATE\\\" end\");\n\n        task.setInputData(taskInput);\n\n        jsonJqTransform.start(workflow, task, null);\n\n        assertNull(task.getOutputData().get(\"error\"));\n        assertTrue(task.getOutputData().get(\"result\") instanceof String);\n        String result = (String) task.getOutputData().get(\"result\");\n        assertEquals(\"CREATE\", result);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void listResultShouldBeCorrectlyExtracted() throws JsonProcessingException {\n        final JsonJqTransform jsonJqTransform = new JsonJqTransform(objectMapper);\n        final WorkflowModel workflow = new WorkflowModel();\n        final TaskModel task = new TaskModel();\n        String json =\n                \"{ \\\"request\\\": { \\\"transitions\\\": [ { \\\"name\\\": \\\"redeliver\\\" }, { \\\"name\\\": \\\"redeliver_from_validation_error\\\" }, { \\\"name\\\": \\\"redelivery\\\" } ] } }\";\n        Map<String, Object> inputData = objectMapper.readValue(json, Map.class);\n\n        final Map<String, Object> taskInput = new HashMap<>();\n        taskInput.put(\"inputData\", inputData);\n        taskInput.put(\"queryExpression\", \".inputData.request.transitions | map(.name)\");\n\n        task.setInputData(taskInput);\n\n        jsonJqTransform.start(workflow, task, null);\n\n        assertNull(task.getOutputData().get(\"error\"));\n        assertTrue(task.getOutputData().get(\"result\") instanceof List);\n        List<Object> result = (List<Object>) task.getOutputData().get(\"result\");\n        assertEquals(3, result.size());\n    }\n\n    @Test\n    public void nullResultShouldBeCorrectlyExtracted() throws JsonProcessingException {\n        final JsonJqTransform jsonJqTransform = new JsonJqTransform(objectMapper);\n        final WorkflowModel workflow = new WorkflowModel();\n        final TaskModel task = new TaskModel();\n        final Map<String, Object> taskInput = new HashMap<>();\n        taskInput.put(\"queryExpression\", \"null\");\n        task.setInputData(taskInput);\n\n        jsonJqTransform.start(workflow, task, null);\n\n        assertNull(task.getOutputData().get(\"error\"));\n        assertNull(task.getOutputData().get(\"result\"));\n    }\n}\n"
  },
  {
    "path": "licenseheader.txt",
    "content": "/*\n * Copyright $YEAR Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */"
  },
  {
    "path": "polyglot-clients/README.md",
    "content": "# SDKs for other languages\n\nLanguage specific client SDKs are maintained at a dedicated [conductor-sdk](https://github.com/conductor-sdk) repository.\n\n\nCheck the repository for the latest list, but there are SDK clients for:\n\n## SDK List\n* [Clojure](https://github.com/conductor-sdk/conductor-clojure)\n* [C#](https://github.com/conductor-sdk/conductor-csharp)\n* [Go](https://github.com/conductor-sdk/conductor-go)\n* [Python](https://github.com/conductor-sdk/conductor-python)\n\n\n### In progress (PRs encouraged!)\n* [JavaScript](https://github.com/conductor-sdk/conductor-javascript)"
  },
  {
    "path": "redis-concurrency-limit/build.gradle",
    "content": "plugins {\n    id 'groovy'\n}\n\ndependencies {\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n    compileOnly 'org.springframework.data:spring-data-redis'\n\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    implementation \"redis.clients:jedis:3.6.0\" // Jedis version \"revJedis=3.3.0\" does not play well with Spring Data Redis\n    implementation \"org.apache.commons:commons-lang3\"\n\n    testImplementation \"org.codehaus.groovy:groovy-all:${revGroovy}\"\n    testImplementation \"org.spockframework:spock-core:${revSpock}\"\n    testImplementation \"org.spockframework:spock-spring:${revSpock}\"\n    testImplementation \"org.testcontainers:spock:${revTestContainer}\"\n    testImplementation \"org.testcontainers:testcontainers:${revTestContainer}\"\n    testImplementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n    testImplementation 'org.springframework.data:spring-data-redis'\n}\n"
  },
  {
    "path": "redis-concurrency-limit/src/main/java/com/netflix/conductor/redis/limit/RedisConcurrentExecutionLimitDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.limit;\n\nimport java.util.Optional;\n\nimport org.apache.commons.lang3.ObjectUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.annotations.Trace;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.ConcurrentExecutionLimitDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.redis.limit.config.RedisConcurrentExecutionLimitProperties;\n\n@Trace\n@Component\n@ConditionalOnProperty(\n        value = \"conductor.redis-concurrent-execution-limit.enabled\",\n        havingValue = \"true\")\npublic class RedisConcurrentExecutionLimitDAO implements ConcurrentExecutionLimitDAO {\n\n    private static final Logger LOGGER =\n            LoggerFactory.getLogger(RedisConcurrentExecutionLimitDAO.class);\n    private static final String CLASS_NAME = RedisConcurrentExecutionLimitDAO.class.getSimpleName();\n\n    private final StringRedisTemplate stringRedisTemplate;\n    private final RedisConcurrentExecutionLimitProperties properties;\n\n    public RedisConcurrentExecutionLimitDAO(\n            StringRedisTemplate stringRedisTemplate,\n            RedisConcurrentExecutionLimitProperties properties) {\n        this.stringRedisTemplate = stringRedisTemplate;\n        this.properties = properties;\n    }\n\n    /**\n     * Adds the {@link TaskModel} identifier to a Redis Set for the {@link TaskDef}'s name.\n     *\n     * @param task The {@link TaskModel} object.\n     */\n    @Override\n    public void addTaskToLimit(TaskModel task) {\n        try {\n            Monitors.recordDaoRequests(\n                    CLASS_NAME, \"addTaskToLimit\", task.getTaskType(), task.getWorkflowType());\n            String taskId = task.getTaskId();\n            String taskDefName = task.getTaskDefName();\n            String keyName = createKeyName(taskDefName);\n\n            stringRedisTemplate.opsForSet().add(keyName, taskId);\n\n            LOGGER.debug(\"Added taskId: {} to key: {}\", taskId, keyName);\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"addTaskToLimit\");\n            String errorMsg =\n                    String.format(\n                            \"Error updating taskDefLimit for task - %s:%s in workflow: %s\",\n                            task.getTaskDefName(), task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    /**\n     * Remove the {@link TaskModel} identifier from the Redis Set for the {@link TaskDef}'s name.\n     *\n     * @param task The {@link TaskModel} object.\n     */\n    @Override\n    public void removeTaskFromLimit(TaskModel task) {\n        try {\n            Monitors.recordDaoRequests(\n                    CLASS_NAME, \"removeTaskFromLimit\", task.getTaskType(), task.getWorkflowType());\n            String taskId = task.getTaskId();\n            String taskDefName = task.getTaskDefName();\n\n            String keyName = createKeyName(taskDefName);\n\n            stringRedisTemplate.opsForSet().remove(keyName, taskId);\n\n            LOGGER.debug(\"Removed taskId: {} from key: {}\", taskId, keyName);\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"removeTaskFromLimit\");\n            String errorMsg =\n                    String.format(\n                            \"Error updating taskDefLimit for task - %s:%s in workflow: %s\",\n                            task.getTaskDefName(), task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg, e);\n        }\n    }\n\n    /**\n     * Checks if the {@link TaskModel} identifier is in the Redis Set and size of the set is more\n     * than the {@link TaskDef#concurrencyLimit()}.\n     *\n     * @param task The {@link TaskModel} object.\n     * @return true if the task id is not in the set and size of the set is more than the {@link\n     *     TaskDef#concurrencyLimit()}.\n     */\n    @Override\n    public boolean exceedsLimit(TaskModel task) {\n        Optional<TaskDef> taskDefinition = task.getTaskDefinition();\n        if (taskDefinition.isEmpty()) {\n            return false;\n        }\n        int limit = taskDefinition.get().concurrencyLimit();\n        if (limit <= 0) {\n            return false;\n        }\n\n        try {\n            Monitors.recordDaoRequests(\n                    CLASS_NAME, \"exceedsLimit\", task.getTaskType(), task.getWorkflowType());\n            String taskId = task.getTaskId();\n            String taskDefName = task.getTaskDefName();\n            String keyName = createKeyName(taskDefName);\n\n            boolean isMember =\n                    ObjectUtils.defaultIfNull(\n                            stringRedisTemplate.opsForSet().isMember(keyName, taskId), false);\n            long size =\n                    ObjectUtils.defaultIfNull(stringRedisTemplate.opsForSet().size(keyName), -1L);\n\n            LOGGER.debug(\n                    \"Task: {} is {} of {}, size: {} and limit: {}\",\n                    taskId,\n                    isMember ? \"a member\" : \"not a member\",\n                    keyName,\n                    size,\n                    limit);\n\n            return !isMember && size >= limit;\n        } catch (Exception e) {\n            Monitors.error(CLASS_NAME, \"exceedsLimit\");\n            String errorMsg =\n                    String.format(\n                            \"Failed to get in progress limit - %s:%s in workflow :%s\",\n                            task.getTaskDefName(), task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.error(errorMsg, e);\n            throw new TransientException(errorMsg);\n        }\n    }\n\n    private String createKeyName(String taskDefName) {\n        StringBuilder builder = new StringBuilder();\n        String namespace = properties.getNamespace();\n\n        if (StringUtils.isNotBlank(namespace)) {\n            builder.append(namespace).append(':');\n        }\n\n        return builder.append(taskDefName).toString();\n    }\n}\n"
  },
  {
    "path": "redis-concurrency-limit/src/main/java/com/netflix/conductor/redis/limit/config/RedisConcurrentExecutionLimitConfiguration.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.limit.config;\n\nimport java.util.List;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.connection.RedisClusterConfiguration;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.connection.RedisStandaloneConfiguration;\nimport org.springframework.data.redis.connection.jedis.JedisClientConfiguration;\nimport org.springframework.data.redis.connection.jedis.JedisConnectionFactory;\n\n@Configuration\n@ConditionalOnProperty(\n        value = \"conductor.redis-concurrent-execution-limit.enabled\",\n        havingValue = \"true\")\n@EnableConfigurationProperties(RedisConcurrentExecutionLimitProperties.class)\npublic class RedisConcurrentExecutionLimitConfiguration {\n\n    @Bean\n    @ConditionalOnProperty(\n            value = \"conductor.redis-concurrent-execution-limit.type\",\n            havingValue = \"cluster\")\n    public RedisConnectionFactory redisClusterConnectionFactory(\n            RedisConcurrentExecutionLimitProperties properties) {\n        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();\n        poolConfig.setMaxTotal(properties.getMaxConnectionsPerHost());\n        poolConfig.setTestWhileIdle(true);\n        JedisClientConfiguration clientConfig =\n                JedisClientConfiguration.builder()\n                        .usePooling()\n                        .poolConfig(poolConfig)\n                        .and()\n                        .clientName(properties.getClientName())\n                        .build();\n\n        RedisClusterConfiguration redisClusterConfiguration =\n                new RedisClusterConfiguration(\n                        List.of(properties.getHost() + \":\" + properties.getPort()));\n\n        return new JedisConnectionFactory(redisClusterConfiguration, clientConfig);\n    }\n\n    @Bean\n    @ConditionalOnProperty(\n            value = \"conductor.redis-concurrent-execution-limit.type\",\n            havingValue = \"standalone\",\n            matchIfMissing = true)\n    public RedisConnectionFactory redisStandaloneConnectionFactory(\n            RedisConcurrentExecutionLimitProperties properties) {\n        RedisStandaloneConfiguration config =\n                new RedisStandaloneConfiguration(properties.getHost(), properties.getPort());\n        return new JedisConnectionFactory(config);\n    }\n}\n"
  },
  {
    "path": "redis-concurrency-limit/src/main/java/com/netflix/conductor/redis/limit/config/RedisConcurrentExecutionLimitProperties.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.limit.config;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@ConfigurationProperties(\"conductor.redis-concurrent-execution-limit\")\npublic class RedisConcurrentExecutionLimitProperties {\n\n    public enum RedisType {\n        STANDALONE,\n        CLUSTER\n    }\n\n    private RedisType type;\n\n    private String host;\n\n    private int port;\n\n    private String password;\n\n    private int maxConnectionsPerHost;\n\n    private String clientName;\n\n    private String namespace = \"conductor\";\n\n    public RedisType getType() {\n        return type;\n    }\n\n    public void setType(RedisType type) {\n        this.type = type;\n    }\n\n    public int getMaxConnectionsPerHost() {\n        return maxConnectionsPerHost;\n    }\n\n    public void setMaxConnectionsPerHost(int maxConnectionsPerHost) {\n        this.maxConnectionsPerHost = maxConnectionsPerHost;\n    }\n\n    public String getClientName() {\n        return clientName;\n    }\n\n    public void setClientName(String clientName) {\n        this.clientName = clientName;\n    }\n\n    public String getHost() {\n        return host;\n    }\n\n    public void setHost(String host) {\n        this.host = host;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    public void setPort(int port) {\n        this.port = port;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public String getNamespace() {\n        return namespace;\n    }\n\n    public void setNamespace(String namespace) {\n        this.namespace = namespace;\n    }\n}\n"
  },
  {
    "path": "redis-concurrency-limit/src/test/groovy/com/netflix/conductor/redis/limit/RedisConcurrentExecutionLimitDAOSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.limit\n\nimport org.springframework.data.redis.connection.RedisStandaloneConfiguration\nimport org.springframework.data.redis.connection.jedis.JedisConnectionFactory\nimport org.springframework.data.redis.core.StringRedisTemplate\nimport org.testcontainers.containers.GenericContainer\nimport org.testcontainers.spock.Testcontainers\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask\nimport com.netflix.conductor.model.TaskModel\nimport com.netflix.conductor.redis.limit.config.RedisConcurrentExecutionLimitProperties\n\nimport spock.lang.Specification\nimport spock.lang.Subject\nimport spock.lang.Unroll\n\n@Testcontainers\nclass RedisConcurrentExecutionLimitDAOSpec extends Specification {\n\n    GenericContainer redis = new GenericContainer(\"redis:5.0.3-alpine\")\n            .withExposedPorts(6379)\n\n    @Subject\n    RedisConcurrentExecutionLimitDAO dao\n\n    StringRedisTemplate redisTemplate\n\n    RedisConcurrentExecutionLimitProperties properties\n\n    def setup() {\n        properties = new RedisConcurrentExecutionLimitProperties(namespace: 'conductor')\n        def factory = new JedisConnectionFactory(new RedisStandaloneConfiguration(redis.host, redis.firstMappedPort))\n        factory.afterPropertiesSet()\n        redisTemplate = new StringRedisTemplate(factory)\n        dao = new RedisConcurrentExecutionLimitDAO(redisTemplate, properties)\n    }\n\n    def \"verify addTaskToLimit adds the taskId to the right set\"() {\n        given:\n        def taskId = 'task1'\n        def taskDefName = 'task_def_name1'\n        def keyName = \"${properties.namespace}:$taskDefName\" as String\n\n        TaskModel task = new TaskModel(taskId: taskId, taskDefName: taskDefName)\n\n        when:\n        dao.addTaskToLimit(task)\n\n        then:\n        redisTemplate.hasKey(keyName)\n        redisTemplate.opsForSet().size(keyName) == 1\n        redisTemplate.opsForSet().isMember(keyName, taskId)\n    }\n\n    def \"verify removeTaskFromLimit removes the taskId from the right set\"() {\n        given:\n        def taskId = 'task1'\n        def taskDefName = 'task_def_name1'\n        def keyName = \"${properties.namespace}:$taskDefName\" as String\n\n        redisTemplate.opsForSet().add(keyName, taskId)\n\n        TaskModel task = new TaskModel(taskId: taskId, taskDefName: taskDefName)\n\n        when:\n        dao.removeTaskFromLimit(task)\n\n        then:\n        !redisTemplate.hasKey(keyName) // since the only element in the set is removed, Redis removes the set\n    }\n\n    @Unroll\n    def \"verify exceedsLimit returns false for #testCase\"() {\n        given:\n        def taskId = 'task1'\n        def taskDefName = 'task_def_name1'\n\n        TaskModel task = new TaskModel(taskId: taskId, taskDefName: taskDefName, workflowTask: workflowTask)\n\n        when:\n        def retVal = dao.exceedsLimit(task)\n\n        then:\n        !retVal\n\n        where:\n        workflowTask << [new WorkflowTask(taskDefinition: null), new WorkflowTask(taskDefinition: new TaskDef(concurrentExecLimit: -2))]\n        testCase << ['a task with no TaskDefinition', 'TaskDefinition with concurrentExecLimit is less than 0']\n    }\n\n    def \"verify exceedsLimit returns false for tasks less than concurrentExecLimit\"() {\n        given:\n        def taskId = 'task1'\n        def taskDefName = 'task_def_name1'\n        def keyName = \"${properties.namespace}:$taskDefName\" as String\n\n        TaskModel task = new TaskModel(taskId: taskId, taskDefName: taskDefName, workflowTask: new WorkflowTask(taskDefinition: new TaskDef(concurrentExecLimit: 2)))\n\n        redisTemplate.opsForSet().add(keyName, taskId)\n\n        when:\n        def retVal = dao.exceedsLimit(task)\n\n        then:\n        !retVal\n    }\n\n    def \"verify exceedsLimit returns false for taskId already in the set but more than concurrentExecLimit\"() {\n        given:\n        def taskId = 'task1'\n        def taskDefName = 'task_def_name1'\n        def keyName = \"${properties.namespace}:$taskDefName\" as String\n\n        TaskModel task = new TaskModel(taskId: taskId, taskDefName: taskDefName, workflowTask: new WorkflowTask(taskDefinition: new TaskDef(concurrentExecLimit: 2)))\n\n        redisTemplate.opsForSet().add(keyName, taskId) // add the id of the task passed as argument to exceedsLimit\n        redisTemplate.opsForSet().add(keyName, 'taskId2')\n\n        when:\n        def retVal = dao.exceedsLimit(task)\n\n        then:\n        !retVal\n    }\n\n    def \"verify exceedsLimit returns true for a new taskId more than concurrentExecLimit\"() {\n        given:\n        def taskId = 'task1'\n        def taskDefName = 'task_def_name1'\n        def keyName = \"${properties.namespace}:$taskDefName\" as String\n\n        TaskModel task = new TaskModel(taskId: taskId, taskDefName: taskDefName, workflowTask: new WorkflowTask(taskDefinition: new TaskDef(concurrentExecLimit: 2)))\n\n        // add task ids different from the id of the task passed to exceedsLimit\n        redisTemplate.opsForSet().add(keyName, 'taskId2')\n        redisTemplate.opsForSet().add(keyName, 'taskId3')\n\n        when:\n        def retVal = dao.exceedsLimit(task)\n\n        then:\n        retVal\n    }\n\n    def \"verify createKeyName ignores namespace if its not present\"() {\n        given:\n        def dao = new RedisConcurrentExecutionLimitDAO(null, conductorProperties)\n\n        when:\n        def keyName = dao.createKeyName('taskdefname')\n\n        then:\n        keyName == expectedKeyName\n\n        where:\n        conductorProperties << [new RedisConcurrentExecutionLimitProperties(), new RedisConcurrentExecutionLimitProperties(namespace: null), new RedisConcurrentExecutionLimitProperties(namespace: 'test')]\n        expectedKeyName << ['conductor:taskdefname', 'taskdefname', 'test:taskdefname']\n    }\n}\n"
  },
  {
    "path": "redis-lock/build.gradle",
    "content": "dependencies {\n    implementation project(':conductor-core')\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n\n    implementation \"org.apache.commons:commons-lang3\"\n    implementation \"org.redisson:redisson:${revRedisson}\"\n\n    testImplementation \"com.github.kstyrc:embedded-redis:${revEmbeddedRedis}\"\n}\n"
  },
  {
    "path": "redis-lock/src/main/java/com/netflix/conductor/redislock/config/RedisLockConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redislock.config;\n\nimport java.util.Arrays;\n\nimport org.redisson.Redisson;\nimport org.redisson.config.Config;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.core.sync.Lock;\nimport com.netflix.conductor.redislock.config.RedisLockProperties.REDIS_SERVER_TYPE;\nimport com.netflix.conductor.redislock.lock.RedisLock;\n\n@Configuration\n@EnableConfigurationProperties(RedisLockProperties.class)\n@ConditionalOnProperty(name = \"conductor.workflow-execution-lock.type\", havingValue = \"redis\")\npublic class RedisLockConfiguration {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockConfiguration.class);\n\n    @Bean\n    public Redisson getRedisson(RedisLockProperties properties) {\n        RedisLockProperties.REDIS_SERVER_TYPE redisServerType;\n        try {\n            redisServerType = properties.getServerType();\n        } catch (IllegalArgumentException ie) {\n            final String message =\n                    \"Invalid Redis server type: \"\n                            + properties.getServerType()\n                            + \", supported values are: \"\n                            + Arrays.toString(REDIS_SERVER_TYPE.values());\n            LOGGER.error(message);\n            throw new RuntimeException(message, ie);\n        }\n        String redisServerAddress = properties.getServerAddress();\n        String redisServerPassword = properties.getServerPassword();\n        String masterName = properties.getServerMasterName();\n\n        Config redisConfig = new Config();\n        if (properties.getNumNettyThreads() != null && properties.getNumNettyThreads() > 0) {\n            redisConfig.setNettyThreads(properties.getNumNettyThreads());\n        }\n\n        int connectionTimeout = 10000;\n        switch (redisServerType) {\n            case SINGLE:\n                LOGGER.info(\"Setting up Redis Single Server for RedisLockConfiguration\");\n                redisConfig\n                        .useSingleServer()\n                        .setAddress(redisServerAddress)\n                        .setPassword(redisServerPassword)\n                        .setTimeout(connectionTimeout);\n                break;\n            case CLUSTER:\n                LOGGER.info(\"Setting up Redis Cluster for RedisLockConfiguration\");\n                redisConfig\n                        .useClusterServers()\n                        .setScanInterval(2000) // cluster state scan interval in milliseconds\n                        .addNodeAddress(redisServerAddress.split(\",\"))\n                        .setPassword(redisServerPassword)\n                        .setTimeout(connectionTimeout)\n                        .setSlaveConnectionMinimumIdleSize(\n                                properties.getClusterReplicaConnectionMinIdleSize())\n                        .setSlaveConnectionPoolSize(\n                                properties.getClusterReplicaConnectionPoolSize())\n                        .setMasterConnectionMinimumIdleSize(\n                                properties.getClusterPrimaryConnectionMinIdleSize())\n                        .setMasterConnectionPoolSize(\n                                properties.getClusterPrimaryConnectionPoolSize());\n                break;\n            case SENTINEL:\n                LOGGER.info(\"Setting up Redis Sentinel Servers for RedisLockConfiguration\");\n                redisConfig\n                        .useSentinelServers()\n                        .setScanInterval(2000)\n                        .setMasterName(masterName)\n                        .addSentinelAddress(redisServerAddress)\n                        .setPassword(redisServerPassword)\n                        .setTimeout(connectionTimeout);\n                break;\n        }\n\n        return (Redisson) Redisson.create(redisConfig);\n    }\n\n    @Bean\n    public Lock provideLock(Redisson redisson, RedisLockProperties properties) {\n        return new RedisLock(redisson, properties);\n    }\n}\n"
  },
  {
    "path": "redis-lock/src/main/java/com/netflix/conductor/redislock/config/RedisLockProperties.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redislock.config;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@ConfigurationProperties(\"conductor.redis-lock\")\npublic class RedisLockProperties {\n\n    /** The redis server configuration to be used. */\n    private REDIS_SERVER_TYPE serverType = REDIS_SERVER_TYPE.SINGLE;\n\n    /** The address of the redis server following format -- host:port */\n    private String serverAddress = \"redis://127.0.0.1:6379\";\n\n    /** The password for redis authentication */\n    private String serverPassword = null;\n\n    /** The master server name used by Redis Sentinel servers and master change monitoring task */\n    private String serverMasterName = \"master\";\n\n    /** The namespace to use to prepend keys used for locking in redis */\n    private String namespace = \"\";\n\n    /** The number of natty threads to use */\n    private Integer numNettyThreads;\n\n    /** If using Cluster Mode, you can use this to set num of min idle connections for replica */\n    private int clusterReplicaConnectionMinIdleSize = 24;\n\n    /** If using Cluster Mode, you can use this to set num of min idle connections for replica */\n    private int clusterReplicaConnectionPoolSize = 64;\n\n    /** If using Cluster Mode, you can use this to set num of min idle connections for replica */\n    private int clusterPrimaryConnectionMinIdleSize = 24;\n\n    /** If using Cluster Mode, you can use this to set num of min idle connections for replica */\n    private int clusterPrimaryConnectionPoolSize = 64;\n\n    /**\n     * Enable to otionally continue without a lock to not block executions until the locking service\n     * becomes available\n     */\n    private boolean ignoreLockingExceptions = false;\n\n    public REDIS_SERVER_TYPE getServerType() {\n        return serverType;\n    }\n\n    public void setServerType(REDIS_SERVER_TYPE serverType) {\n        this.serverType = serverType;\n    }\n\n    public String getServerAddress() {\n        return serverAddress;\n    }\n\n    public void setServerAddress(String serverAddress) {\n        this.serverAddress = serverAddress;\n    }\n\n    public String getServerPassword() {\n        return serverPassword;\n    }\n\n    public void setServerPassword(String serverPassword) {\n        this.serverPassword = serverPassword;\n    }\n\n    public String getServerMasterName() {\n        return serverMasterName;\n    }\n\n    public void setServerMasterName(String serverMasterName) {\n        this.serverMasterName = serverMasterName;\n    }\n\n    public String getNamespace() {\n        return namespace;\n    }\n\n    public void setNamespace(String namespace) {\n        this.namespace = namespace;\n    }\n\n    public boolean isIgnoreLockingExceptions() {\n        return ignoreLockingExceptions;\n    }\n\n    public void setIgnoreLockingExceptions(boolean ignoreLockingExceptions) {\n        this.ignoreLockingExceptions = ignoreLockingExceptions;\n    }\n\n    public Integer getNumNettyThreads() {\n        return numNettyThreads;\n    }\n\n    public void setNumNettyThreads(Integer numNettyThreads) {\n        this.numNettyThreads = numNettyThreads;\n    }\n\n    public Integer getClusterReplicaConnectionMinIdleSize() {\n        return clusterReplicaConnectionMinIdleSize;\n    }\n\n    public void setClusterReplicaConnectionMinIdleSize(\n            Integer clusterReplicaConnectionMinIdleSize) {\n        this.clusterReplicaConnectionMinIdleSize = clusterReplicaConnectionMinIdleSize;\n    }\n\n    public Integer getClusterReplicaConnectionPoolSize() {\n        return clusterReplicaConnectionPoolSize;\n    }\n\n    public void setClusterReplicaConnectionPoolSize(Integer clusterReplicaConnectionPoolSize) {\n        this.clusterReplicaConnectionPoolSize = clusterReplicaConnectionPoolSize;\n    }\n\n    public Integer getClusterPrimaryConnectionMinIdleSize() {\n        return clusterPrimaryConnectionMinIdleSize;\n    }\n\n    public void setClusterPrimaryConnectionMinIdleSize(\n            Integer clusterPrimaryConnectionMinIdleSize) {\n        this.clusterPrimaryConnectionMinIdleSize = clusterPrimaryConnectionMinIdleSize;\n    }\n\n    public Integer getClusterPrimaryConnectionPoolSize() {\n        return clusterPrimaryConnectionPoolSize;\n    }\n\n    public void setClusterPrimaryConnectionPoolSize(Integer clusterPrimaryConnectionPoolSize) {\n        this.clusterPrimaryConnectionPoolSize = clusterPrimaryConnectionPoolSize;\n    }\n\n    public enum REDIS_SERVER_TYPE {\n        SINGLE,\n        CLUSTER,\n        SENTINEL\n    }\n}\n"
  },
  {
    "path": "redis-lock/src/main/java/com/netflix/conductor/redislock/lock/RedisLock.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redislock.lock;\n\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.redisson.Redisson;\nimport org.redisson.api.RLock;\nimport org.redisson.api.RedissonClient;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.core.sync.Lock;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.redislock.config.RedisLockProperties;\n\npublic class RedisLock implements Lock {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLock.class);\n\n    private final RedisLockProperties properties;\n    private final RedissonClient redisson;\n    private static String LOCK_NAMESPACE = \"\";\n\n    public RedisLock(Redisson redisson, RedisLockProperties properties) {\n        this.properties = properties;\n        this.redisson = redisson;\n        LOCK_NAMESPACE = properties.getNamespace();\n    }\n\n    @Override\n    public void acquireLock(String lockId) {\n        RLock lock = redisson.getLock(parseLockId(lockId));\n        lock.lock();\n    }\n\n    @Override\n    public boolean acquireLock(String lockId, long timeToTry, TimeUnit unit) {\n        RLock lock = redisson.getLock(parseLockId(lockId));\n        try {\n            return lock.tryLock(timeToTry, unit);\n        } catch (Exception e) {\n            return handleAcquireLockFailure(lockId, e);\n        }\n    }\n\n    /**\n     * @param lockId resource to lock on\n     * @param timeToTry blocks up to timeToTry duration in attempt to acquire the lock\n     * @param leaseTime Lock lease expiration duration. Redisson default is -1, meaning it holds the\n     *     lock until explicitly unlocked.\n     * @param unit time unit\n     * @return\n     */\n    @Override\n    public boolean acquireLock(String lockId, long timeToTry, long leaseTime, TimeUnit unit) {\n        RLock lock = redisson.getLock(parseLockId(lockId));\n        try {\n            return lock.tryLock(timeToTry, leaseTime, unit);\n        } catch (Exception e) {\n            return handleAcquireLockFailure(lockId, e);\n        }\n    }\n\n    @Override\n    public void releaseLock(String lockId) {\n        RLock lock = redisson.getLock(parseLockId(lockId));\n        try {\n            lock.unlock();\n        } catch (IllegalMonitorStateException e) {\n            // Releasing a lock twice using Redisson can cause this exception, which can be ignored.\n        }\n    }\n\n    @Override\n    public void deleteLock(String lockId) {\n        // Noop for Redlock algorithm as releaseLock / unlock deletes it.\n    }\n\n    private String parseLockId(String lockId) {\n        if (StringUtils.isEmpty(lockId)) {\n            throw new IllegalArgumentException(\"lockId cannot be NULL or empty: lockId=\" + lockId);\n        }\n        return LOCK_NAMESPACE + \".\" + lockId;\n    }\n\n    private boolean handleAcquireLockFailure(String lockId, Exception e) {\n        LOGGER.error(\"Failed to acquireLock for lockId: {}\", lockId, e);\n        Monitors.recordAcquireLockFailure(e.getClass().getName());\n        // A Valid failure to acquire lock when another thread has acquired it returns false.\n        // However, when an exception is thrown while acquiring lock, due to connection or others\n        // issues,\n        // we can optionally continue without a \"lock\" to not block executions until Locking service\n        // is available.\n        return properties.isIgnoreLockingExceptions();\n    }\n}\n"
  },
  {
    "path": "redis-lock/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"properties\": [\n    {\n      \"name\": \"conductor.redis-lock.server-type\",\n      \"defaultValue\": \"SINGLE\"\n    }\n  ],\n  \"hints\": [\n    {\n      \"name\": \"conductor.workflow-execution-lock.type\",\n      \"values\": [\n        {\n          \"value\": \"redis\",\n          \"description\": \"Use the redis-lock implementation as the lock provider.\"\n        }\n      ]\n    },\n    {\n      \"name\": \"conductor.redis-lock.server-type\",\n      \"providers\": [\n        {\n          \"name\": \"handle-as\",\n          \"parameters\": {\n            \"target\": \"java.lang.Enum\"\n          }\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "redis-lock/src/test/java/com/netflix/conductor/redis/lock/RedisLockTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.lock;\n\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.AfterClass;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.redisson.Redisson;\nimport org.redisson.api.RLock;\nimport org.redisson.api.RedissonClient;\nimport org.redisson.config.Config;\n\nimport com.netflix.conductor.redislock.config.RedisLockProperties;\nimport com.netflix.conductor.redislock.config.RedisLockProperties.REDIS_SERVER_TYPE;\nimport com.netflix.conductor.redislock.lock.RedisLock;\n\nimport redis.embedded.RedisServer;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class RedisLockTest {\n\n    private static RedisLock redisLock;\n    private static Config config;\n    private static RedissonClient redisson;\n    private static RedisServer redisServer = null;\n\n    @BeforeClass\n    public static void setUp() throws Exception {\n        String testServerAddress = \"redis://127.0.0.1:6371\";\n        redisServer = new RedisServer(6371);\n        if (redisServer.isActive()) {\n            redisServer.stop();\n        }\n        redisServer.start();\n\n        RedisLockProperties properties = mock(RedisLockProperties.class);\n        when(properties.getServerType()).thenReturn(REDIS_SERVER_TYPE.SINGLE);\n        when(properties.getServerAddress()).thenReturn(testServerAddress);\n        when(properties.getServerMasterName()).thenReturn(\"master\");\n        when(properties.getNamespace()).thenReturn(\"\");\n        when(properties.isIgnoreLockingExceptions()).thenReturn(false);\n\n        Config redissonConfig = new Config();\n        redissonConfig.useSingleServer().setAddress(testServerAddress).setTimeout(10000);\n        redisLock = new RedisLock((Redisson) Redisson.create(redissonConfig), properties);\n\n        // Create another instance of redisson for tests.\n        RedisLockTest.config = new Config();\n        RedisLockTest.config.useSingleServer().setAddress(testServerAddress).setTimeout(10000);\n        redisson = Redisson.create(RedisLockTest.config);\n    }\n\n    @AfterClass\n    public static void tearDown() {\n        redisServer.stop();\n    }\n\n    @Test\n    public void testLocking() {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n        assertTrue(redisLock.acquireLock(lockId, 1000, 1000, TimeUnit.MILLISECONDS));\n    }\n\n    @Test\n    public void testLockExpiration() throws InterruptedException {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n        boolean isLocked = redisLock.acquireLock(lockId, 1000, 1000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n\n        Thread.sleep(2000);\n\n        RLock lock = redisson.getLock(lockId);\n        assertFalse(lock.isLocked());\n    }\n\n    @Test\n    public void testLockReentry() throws InterruptedException {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n        boolean isLocked = redisLock.acquireLock(lockId, 1000, 60000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n\n        Thread.sleep(1000);\n\n        // get the lock back\n        isLocked = redisLock.acquireLock(lockId, 1000, 1000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n\n        RLock lock = redisson.getLock(lockId);\n        assertTrue(isLocked);\n    }\n\n    @Test\n    public void testReleaseLock() {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n\n        boolean isLocked = redisLock.acquireLock(lockId, 1000, 10000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n\n        redisLock.releaseLock(lockId);\n\n        RLock lock = redisson.getLock(lockId);\n        assertFalse(lock.isLocked());\n    }\n\n    @Test\n    public void testLockReleaseAndAcquire() throws InterruptedException {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n\n        boolean isLocked = redisLock.acquireLock(lockId, 1000, 10000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n\n        redisLock.releaseLock(lockId);\n\n        Worker worker1 = new Worker(redisLock, lockId);\n\n        worker1.start();\n        worker1.join();\n\n        assertTrue(worker1.isLocked);\n    }\n\n    @Test\n    public void testLockingDuplicateThreads() throws InterruptedException {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n\n        Worker worker1 = new Worker(redisLock, lockId);\n        Worker worker2 = new Worker(redisLock, lockId);\n\n        worker1.start();\n        worker2.start();\n\n        worker1.join();\n        worker2.join();\n\n        // Ensure only one of them had got the lock.\n        assertFalse(worker1.isLocked && worker2.isLocked);\n        assertTrue(worker1.isLocked || worker2.isLocked);\n    }\n\n    @Test\n    public void testDuplicateLockAcquireFailure() throws InterruptedException {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n        Worker worker1 = new Worker(redisLock, lockId, 100L, 60000L);\n\n        worker1.start();\n        worker1.join();\n\n        boolean isLocked = redisLock.acquireLock(lockId, 500L, 1000L, TimeUnit.MILLISECONDS);\n\n        // Ensure only one of them had got the lock.\n        assertFalse(isLocked);\n        assertTrue(worker1.isLocked);\n    }\n\n    @Test\n    public void testReacquireLostKey() {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n\n        boolean isLocked = redisLock.acquireLock(lockId, 1000, 10000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n\n        // Delete key from the cluster to reacquire\n        // Simulating the case when cluster goes down and possibly loses some keys.\n        redisson.getKeys().flushall();\n\n        isLocked = redisLock.acquireLock(lockId, 100, 10000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n    }\n\n    @Test\n    public void testReleaseLockTwice() {\n        redisson.getKeys().flushall();\n        String lockId = \"abcd-1234\";\n\n        boolean isLocked = redisLock.acquireLock(lockId, 1000, 10000, TimeUnit.MILLISECONDS);\n        assertTrue(isLocked);\n\n        redisLock.releaseLock(lockId);\n        redisLock.releaseLock(lockId);\n    }\n\n    private static class Worker extends Thread {\n\n        private final RedisLock lock;\n        private final String lockID;\n        boolean isLocked;\n        private Long timeToTry = 50L;\n        private Long leaseTime = 1000L;\n\n        Worker(RedisLock lock, String lockID) {\n            super(\"TestWorker-\" + lockID);\n            this.lock = lock;\n            this.lockID = lockID;\n        }\n\n        Worker(RedisLock lock, String lockID, Long timeToTry, Long leaseTime) {\n            super(\"TestWorker-\" + lockID);\n            this.lock = lock;\n            this.lockID = lockID;\n            this.timeToTry = timeToTry;\n            this.leaseTime = leaseTime;\n        }\n\n        @Override\n        public void run() {\n            isLocked = lock.acquireLock(lockID, timeToTry, leaseTime, TimeUnit.MILLISECONDS);\n        }\n    }\n}\n"
  },
  {
    "path": "redis-persistence/build.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\ndependencies {\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n    compileOnly 'org.springframework.boot:spring-boot-starter'\n\n    implementation \"redis.clients:jedis:${revJedis}\"\n    implementation \"com.netflix.dyno-queues:dyno-queues-redis:${revDynoQueues}\"\n    implementation('com.thoughtworks.xstream:xstream:1.4.20')\n\n    //In memory\n    implementation \"org.rarefiedredis.redis:redis-java:${revRarefiedRedis}\"\n\n    testImplementation project(':conductor-core').sourceSets.test.output\n    testImplementation project(':conductor-common').sourceSets.test.output\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/AnyRedisCondition.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport org.springframework.boot.autoconfigure.condition.AnyNestedCondition;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\n\npublic class AnyRedisCondition extends AnyNestedCondition {\n\n    public AnyRedisCondition() {\n        super(ConfigurationPhase.PARSE_CONFIGURATION);\n    }\n\n    @ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"dynomite\")\n    static class DynomiteClusterCondition {}\n\n    @ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"memory\")\n    static class InMemoryRedisCondition {}\n\n    @ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"redis_cluster\")\n    static class RedisClusterConfiguration {}\n\n    @ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"redis_sentinel\")\n    static class RedisSentinelConfiguration {}\n\n    @ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"redis_standalone\")\n    static class RedisStandaloneConfiguration {}\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/DynomiteClusterConfiguration.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.dyno.connectionpool.HostSupplier;\nimport com.netflix.dyno.connectionpool.TokenMapSupplier;\nimport com.netflix.dyno.connectionpool.impl.ConnectionPoolConfigurationImpl;\nimport com.netflix.dyno.jedis.DynoJedisClient;\n\nimport redis.clients.jedis.commands.JedisCommands;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"dynomite\")\npublic class DynomiteClusterConfiguration extends JedisCommandsConfigurer {\n\n    protected JedisCommands createJedisCommands(\n            RedisProperties properties,\n            ConductorProperties conductorProperties,\n            HostSupplier hostSupplier,\n            TokenMapSupplier tokenMapSupplier) {\n        ConnectionPoolConfigurationImpl connectionPoolConfiguration =\n                new ConnectionPoolConfigurationImpl(properties.getClusterName())\n                        .withTokenSupplier(tokenMapSupplier)\n                        .setLocalRack(properties.getAvailabilityZone())\n                        .setLocalDataCenter(properties.getDataCenterRegion())\n                        .setSocketTimeout(0)\n                        .setConnectTimeout(0)\n                        .setMaxConnsPerHost(properties.getMaxConnectionsPerHost())\n                        .setMaxTimeoutWhenExhausted(\n                                (int) properties.getMaxTimeoutWhenExhausted().toMillis())\n                        .setRetryPolicyFactory(properties.getConnectionRetryPolicy());\n\n        return new DynoJedisClient.Builder()\n                .withHostSupplier(hostSupplier)\n                .withApplicationName(conductorProperties.getAppId())\n                .withDynomiteClusterName(properties.getClusterName())\n                .withCPConfig(connectionPoolConfiguration)\n                .build();\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/InMemoryRedisConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.redis.dynoqueue.LocalhostHostSupplier;\nimport com.netflix.conductor.redis.jedis.JedisMock;\nimport com.netflix.dyno.connectionpool.HostSupplier;\n\nimport static com.netflix.conductor.redis.config.RedisCommonConfiguration.DEFAULT_CLIENT_INJECTION_NAME;\nimport static com.netflix.conductor.redis.config.RedisCommonConfiguration.READ_CLIENT_INJECTION_NAME;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"memory\")\npublic class InMemoryRedisConfiguration {\n\n    @Bean\n    public HostSupplier hostSupplier(RedisProperties properties) {\n        return new LocalhostHostSupplier(properties);\n    }\n\n    @Bean(name = {DEFAULT_CLIENT_INJECTION_NAME, READ_CLIENT_INJECTION_NAME})\n    public JedisMock jedisMock() {\n        return new JedisMock();\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/JedisCommandsConfigurer.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport org.springframework.context.annotation.Bean;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.redis.dynoqueue.ConfigurationHostSupplier;\nimport com.netflix.dyno.connectionpool.HostSupplier;\nimport com.netflix.dyno.connectionpool.TokenMapSupplier;\n\nimport redis.clients.jedis.commands.JedisCommands;\n\nimport static com.netflix.conductor.redis.config.RedisCommonConfiguration.DEFAULT_CLIENT_INJECTION_NAME;\nimport static com.netflix.conductor.redis.config.RedisCommonConfiguration.READ_CLIENT_INJECTION_NAME;\n\nabstract class JedisCommandsConfigurer {\n\n    @Bean\n    public HostSupplier hostSupplier(RedisProperties properties) {\n        return new ConfigurationHostSupplier(properties);\n    }\n\n    @Bean(name = DEFAULT_CLIENT_INJECTION_NAME)\n    public JedisCommands jedisCommands(\n            RedisProperties properties,\n            ConductorProperties conductorProperties,\n            HostSupplier hostSupplier,\n            TokenMapSupplier tokenMapSupplier) {\n        return createJedisCommands(properties, conductorProperties, hostSupplier, tokenMapSupplier);\n    }\n\n    @Bean(name = READ_CLIENT_INJECTION_NAME)\n    public JedisCommands readJedisCommands(\n            RedisProperties properties,\n            ConductorProperties conductorProperties,\n            HostSupplier hostSupplier,\n            TokenMapSupplier tokenMapSupplier) {\n        return createJedisCommands(properties, conductorProperties, hostSupplier, tokenMapSupplier);\n    }\n\n    protected abstract JedisCommands createJedisCommands(\n            RedisProperties properties,\n            ConductorProperties conductorProperties,\n            HostSupplier hostSupplier,\n            TokenMapSupplier tokenMapSupplier);\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisClusterConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.redis.jedis.JedisCluster;\nimport com.netflix.dyno.connectionpool.Host;\nimport com.netflix.dyno.connectionpool.HostSupplier;\nimport com.netflix.dyno.connectionpool.TokenMapSupplier;\n\nimport redis.clients.jedis.HostAndPort;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.commands.JedisCommands;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"redis_cluster\")\npublic class RedisClusterConfiguration extends JedisCommandsConfigurer {\n\n    private static final Logger log = LoggerFactory.getLogger(JedisCommandsConfigurer.class);\n\n    // Same as redis.clients.jedis.BinaryJedisCluster\n    protected static final int DEFAULT_MAX_ATTEMPTS = 5;\n\n    @Override\n    protected JedisCommands createJedisCommands(\n            RedisProperties properties,\n            ConductorProperties conductorProperties,\n            HostSupplier hostSupplier,\n            TokenMapSupplier tokenMapSupplier) {\n        GenericObjectPoolConfig<?> genericObjectPoolConfig = new GenericObjectPoolConfig<>();\n        genericObjectPoolConfig.setMaxTotal(properties.getMaxConnectionsPerHost());\n        Set<HostAndPort> hosts =\n                hostSupplier.getHosts().stream()\n                        .map(h -> new HostAndPort(h.getHostName(), h.getPort()))\n                        .collect(Collectors.toSet());\n        String password = getPassword(hostSupplier.getHosts());\n\n        if (password != null) {\n            log.info(\"Connecting to Redis Cluster with AUTH\");\n            return new JedisCluster(\n                    new redis.clients.jedis.JedisCluster(\n                            hosts,\n                            Protocol.DEFAULT_TIMEOUT,\n                            Protocol.DEFAULT_TIMEOUT,\n                            DEFAULT_MAX_ATTEMPTS,\n                            password,\n                            genericObjectPoolConfig));\n        } else {\n            return new JedisCluster(\n                    new redis.clients.jedis.JedisCluster(hosts, genericObjectPoolConfig));\n        }\n    }\n\n    private String getPassword(List<Host> hosts) {\n        return hosts.isEmpty() ? null : hosts.get(0).getPassword();\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisCommonConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.redis.dynoqueue.RedisQueuesShardingStrategyProvider;\nimport com.netflix.dyno.connectionpool.Host;\nimport com.netflix.dyno.connectionpool.HostSupplier;\nimport com.netflix.dyno.connectionpool.TokenMapSupplier;\nimport com.netflix.dyno.connectionpool.impl.lb.HostToken;\nimport com.netflix.dyno.connectionpool.impl.utils.CollectionUtils;\nimport com.netflix.dyno.queues.ShardSupplier;\nimport com.netflix.dyno.queues.redis.RedisQueues;\nimport com.netflix.dyno.queues.redis.sharding.ShardingStrategy;\nimport com.netflix.dyno.queues.shard.DynoShardSupplier;\n\nimport com.google.inject.ProvisionException;\nimport redis.clients.jedis.commands.JedisCommands;\n\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(RedisProperties.class)\n@Conditional(AnyRedisCondition.class)\npublic class RedisCommonConfiguration {\n\n    public static final String DEFAULT_CLIENT_INJECTION_NAME = \"DefaultJedisCommands\";\n    public static final String READ_CLIENT_INJECTION_NAME = \"ReadJedisCommands\";\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RedisCommonConfiguration.class);\n\n    @Bean\n    public ShardSupplier shardSupplier(HostSupplier hostSupplier, RedisProperties properties) {\n        if (properties.getAvailabilityZone() == null) {\n            throw new ProvisionException(\n                    \"Availability zone is not defined.  Ensure Configuration.getAvailabilityZone() returns a non-null \"\n                            + \"and non-empty value.\");\n        }\n        String localDC =\n                properties.getAvailabilityZone().replaceAll(properties.getDataCenterRegion(), \"\");\n        return new DynoShardSupplier(hostSupplier, properties.getDataCenterRegion(), localDC);\n    }\n\n    @Bean\n    public TokenMapSupplier tokenMapSupplier() {\n        final List<HostToken> hostTokens = new ArrayList<>();\n        return new TokenMapSupplier() {\n            @Override\n            public List<HostToken> getTokens(Set<Host> activeHosts) {\n                long i = activeHosts.size();\n                for (Host host : activeHosts) {\n                    HostToken hostToken = new HostToken(i, host);\n                    hostTokens.add(hostToken);\n                    i--;\n                }\n                return hostTokens;\n            }\n\n            @Override\n            public HostToken getTokenForHost(Host host, Set<Host> activeHosts) {\n                return CollectionUtils.find(\n                        hostTokens, token -> token.getHost().compareTo(host) == 0);\n            }\n        };\n    }\n\n    @Bean\n    public ShardingStrategy shardingStrategy(\n            ShardSupplier shardSupplier, RedisProperties properties) {\n        return new RedisQueuesShardingStrategyProvider(shardSupplier, properties).get();\n    }\n\n    @Bean\n    public RedisQueues redisQueues(\n            @Qualifier(DEFAULT_CLIENT_INJECTION_NAME) JedisCommands jedisCommands,\n            @Qualifier(READ_CLIENT_INJECTION_NAME) JedisCommands jedisCommandsRead,\n            ShardSupplier shardSupplier,\n            RedisProperties properties,\n            ShardingStrategy shardingStrategy) {\n        RedisQueues queues =\n                new RedisQueues(\n                        jedisCommands,\n                        jedisCommandsRead,\n                        properties.getQueuePrefix(),\n                        shardSupplier,\n                        60_000,\n                        60_000,\n                        shardingStrategy);\n        LOGGER.info(\"DynoQueueDAO initialized with prefix \" + properties.getQueuePrefix() + \"!\");\n        return queues;\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisProperties.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.redis.dynoqueue.RedisQueuesShardingStrategyProvider;\nimport com.netflix.dyno.connectionpool.RetryPolicy.RetryPolicyFactory;\nimport com.netflix.dyno.connectionpool.impl.RetryNTimes;\nimport com.netflix.dyno.connectionpool.impl.RunOnce;\n\n@ConfigurationProperties(\"conductor.redis\")\npublic class RedisProperties {\n\n    private final ConductorProperties conductorProperties;\n\n    @Autowired\n    public RedisProperties(ConductorProperties conductorProperties) {\n        this.conductorProperties = conductorProperties;\n    }\n\n    /**\n     * Data center region. If hosting on Amazon the value is something like us-east-1, us-west-2\n     * etc.\n     */\n    private String dataCenterRegion = \"us-east-1\";\n\n    /**\n     * Local rack / availability zone. For AWS deployments, the value is something like us-east-1a,\n     * etc.\n     */\n    private String availabilityZone = \"us-east-1c\";\n\n    /** The name of the redis / dynomite cluster */\n    private String clusterName = \"\";\n\n    /** Dynomite Cluster details. Format is host:port:rack separated by semicolon */\n    private String hosts = null;\n\n    /** The prefix used to prepend workflow data in redis */\n    private String workflowNamespacePrefix = null;\n\n    /** The prefix used to prepend keys for queues in redis */\n    private String queueNamespacePrefix = null;\n\n    /**\n     * The domain name to be used in the key prefix for logical separation of workflow data and\n     * queues in a shared redis setup\n     */\n    private String keyspaceDomain = null;\n\n    /**\n     * The maximum number of connections that can be managed by the connection pool on a given\n     * instance\n     */\n    private int maxConnectionsPerHost = 10;\n\n    /**\n     * The maximum amount of time to wait for a connection to become available from the connection\n     * pool\n     */\n    private Duration maxTimeoutWhenExhausted = Duration.ofMillis(800);\n\n    /** The maximum retry attempts to use with this connection pool */\n    private int maxRetryAttempts = 0;\n\n    /** The read connection port to be used for connecting to dyno-queues */\n    private int queuesNonQuorumPort = 22122;\n\n    /** The sharding strategy to be used for the dyno queue configuration */\n    private String queueShardingStrategy = RedisQueuesShardingStrategyProvider.ROUND_ROBIN_STRATEGY;\n\n    /** The time in seconds after which the in-memory task definitions cache will be refreshed */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration taskDefCacheRefreshInterval = Duration.ofSeconds(60);\n\n    /** The time to live in seconds for which the event execution will be persisted */\n    @DurationUnit(ChronoUnit.SECONDS)\n    private Duration eventExecutionPersistenceTTL = Duration.ofSeconds(60);\n\n    // Maximum number of idle connections to be maintained\n    private int maxIdleConnections = 8;\n\n    // Minimum number of idle connections to be maintained\n    private int minIdleConnections = 5;\n\n    private long minEvictableIdleTimeMillis = 1800000;\n\n    private long timeBetweenEvictionRunsMillis = -1L;\n\n    private boolean testWhileIdle = false;\n\n    private int numTestsPerEvictionRun = 3;\n\n    private int database = 0;\n\n    private String username = null;\n\n    public int getNumTestsPerEvictionRun() {\n        return numTestsPerEvictionRun;\n    }\n\n    public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {\n        this.numTestsPerEvictionRun = numTestsPerEvictionRun;\n    }\n\n    public boolean isTestWhileIdle() {\n        return testWhileIdle;\n    }\n\n    public void setTestWhileIdle(boolean testWhileIdle) {\n        this.testWhileIdle = testWhileIdle;\n    }\n\n    public long getMinEvictableIdleTimeMillis() {\n        return minEvictableIdleTimeMillis;\n    }\n\n    public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {\n        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;\n    }\n\n    public long getTimeBetweenEvictionRunsMillis() {\n        return timeBetweenEvictionRunsMillis;\n    }\n\n    public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {\n        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;\n    }\n\n    public int getMinIdleConnections() {\n        return minIdleConnections;\n    }\n\n    public void setMinIdleConnections(int minIdleConnections) {\n        this.minIdleConnections = minIdleConnections;\n    }\n\n    public int getMaxIdleConnections() {\n        return maxIdleConnections;\n    }\n\n    public void setMaxIdleConnections(int maxIdleConnections) {\n        this.maxIdleConnections = maxIdleConnections;\n    }\n\n    public String getDataCenterRegion() {\n        return dataCenterRegion;\n    }\n\n    public void setDataCenterRegion(String dataCenterRegion) {\n        this.dataCenterRegion = dataCenterRegion;\n    }\n\n    public String getAvailabilityZone() {\n        return availabilityZone;\n    }\n\n    public void setAvailabilityZone(String availabilityZone) {\n        this.availabilityZone = availabilityZone;\n    }\n\n    public String getClusterName() {\n        return clusterName;\n    }\n\n    public void setClusterName(String clusterName) {\n        this.clusterName = clusterName;\n    }\n\n    public String getHosts() {\n        return hosts;\n    }\n\n    public void setHosts(String hosts) {\n        this.hosts = hosts;\n    }\n\n    public String getWorkflowNamespacePrefix() {\n        return workflowNamespacePrefix;\n    }\n\n    public void setWorkflowNamespacePrefix(String workflowNamespacePrefix) {\n        this.workflowNamespacePrefix = workflowNamespacePrefix;\n    }\n\n    public String getQueueNamespacePrefix() {\n        return queueNamespacePrefix;\n    }\n\n    public void setQueueNamespacePrefix(String queueNamespacePrefix) {\n        this.queueNamespacePrefix = queueNamespacePrefix;\n    }\n\n    public String getKeyspaceDomain() {\n        return keyspaceDomain;\n    }\n\n    public void setKeyspaceDomain(String keyspaceDomain) {\n        this.keyspaceDomain = keyspaceDomain;\n    }\n\n    public int getMaxConnectionsPerHost() {\n        return maxConnectionsPerHost;\n    }\n\n    public void setMaxConnectionsPerHost(int maxConnectionsPerHost) {\n        this.maxConnectionsPerHost = maxConnectionsPerHost;\n    }\n\n    public Duration getMaxTimeoutWhenExhausted() {\n        return maxTimeoutWhenExhausted;\n    }\n\n    public void setMaxTimeoutWhenExhausted(Duration maxTimeoutWhenExhausted) {\n        this.maxTimeoutWhenExhausted = maxTimeoutWhenExhausted;\n    }\n\n    public int getMaxRetryAttempts() {\n        return maxRetryAttempts;\n    }\n\n    public void setMaxRetryAttempts(int maxRetryAttempts) {\n        this.maxRetryAttempts = maxRetryAttempts;\n    }\n\n    public int getQueuesNonQuorumPort() {\n        return queuesNonQuorumPort;\n    }\n\n    public void setQueuesNonQuorumPort(int queuesNonQuorumPort) {\n        this.queuesNonQuorumPort = queuesNonQuorumPort;\n    }\n\n    public String getQueueShardingStrategy() {\n        return queueShardingStrategy;\n    }\n\n    public void setQueueShardingStrategy(String queueShardingStrategy) {\n        this.queueShardingStrategy = queueShardingStrategy;\n    }\n\n    public Duration getTaskDefCacheRefreshInterval() {\n        return taskDefCacheRefreshInterval;\n    }\n\n    public void setTaskDefCacheRefreshInterval(Duration taskDefCacheRefreshInterval) {\n        this.taskDefCacheRefreshInterval = taskDefCacheRefreshInterval;\n    }\n\n    public Duration getEventExecutionPersistenceTTL() {\n        return eventExecutionPersistenceTTL;\n    }\n\n    public void setEventExecutionPersistenceTTL(Duration eventExecutionPersistenceTTL) {\n        this.eventExecutionPersistenceTTL = eventExecutionPersistenceTTL;\n    }\n\n    public String getQueuePrefix() {\n        String prefix = getQueueNamespacePrefix() + \".\" + conductorProperties.getStack();\n        if (getKeyspaceDomain() != null) {\n            prefix = prefix + \".\" + getKeyspaceDomain();\n        }\n        return prefix;\n    }\n\n    public RetryPolicyFactory getConnectionRetryPolicy() {\n        if (getMaxRetryAttempts() == 0) {\n            return RunOnce::new;\n        } else {\n            return () -> new RetryNTimes(maxRetryAttempts, false);\n        }\n    }\n\n    public int getDatabase() {\n        return database;\n    }\n\n    public void setDatabase(int database) {\n        this.database = database;\n    }\n\n    public String getUsername() {\n        return username;\n    }\n\n    public void setUsername(String username) {\n        this.username = username;\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisSentinelConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.redis.jedis.JedisSentinel;\nimport com.netflix.dyno.connectionpool.Host;\nimport com.netflix.dyno.connectionpool.HostSupplier;\nimport com.netflix.dyno.connectionpool.TokenMapSupplier;\n\nimport redis.clients.jedis.JedisSentinelPool;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.commands.JedisCommands;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"redis_sentinel\")\npublic class RedisSentinelConfiguration extends JedisCommandsConfigurer {\n\n    private static final Logger log = LoggerFactory.getLogger(RedisSentinelConfiguration.class);\n\n    @Override\n    protected JedisCommands createJedisCommands(\n            RedisProperties properties,\n            ConductorProperties conductorProperties,\n            HostSupplier hostSupplier,\n            TokenMapSupplier tokenMapSupplier) {\n        GenericObjectPoolConfig<?> genericObjectPoolConfig = new GenericObjectPoolConfig<>();\n        genericObjectPoolConfig.setMinIdle(properties.getMinIdleConnections());\n        genericObjectPoolConfig.setMaxIdle(properties.getMaxIdleConnections());\n        genericObjectPoolConfig.setMaxTotal(properties.getMaxConnectionsPerHost());\n        genericObjectPoolConfig.setTestWhileIdle(properties.isTestWhileIdle());\n        genericObjectPoolConfig.setMinEvictableIdleTimeMillis(\n                properties.getMinEvictableIdleTimeMillis());\n        genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(\n                properties.getTimeBetweenEvictionRunsMillis());\n        genericObjectPoolConfig.setNumTestsPerEvictionRun(properties.getNumTestsPerEvictionRun());\n        log.info(\n                \"Starting conductor server using redis_sentinel and cluster \"\n                        + properties.getClusterName());\n        Set<String> sentinels = new HashSet<>();\n        for (Host host : hostSupplier.getHosts()) {\n            sentinels.add(host.getHostName() + \":\" + host.getPort());\n        }\n        // We use the password of the first sentinel host as password and sentinelPassword\n        String password = getPassword(hostSupplier.getHosts());\n        if (properties.getUsername() != null && password != null) {\n            return new JedisSentinel(\n                    new JedisSentinelPool(\n                            properties.getClusterName(),\n                            sentinels,\n                            genericObjectPoolConfig,\n                            Protocol.DEFAULT_TIMEOUT,\n                            Protocol.DEFAULT_TIMEOUT,\n                            properties.getUsername(),\n                            password,\n                            properties.getDatabase(),\n                            null,\n                            Protocol.DEFAULT_TIMEOUT,\n                            Protocol.DEFAULT_TIMEOUT,\n                            properties.getUsername(),\n                            password,\n                            null));\n        } else if (password != null) {\n            return new JedisSentinel(\n                    new JedisSentinelPool(\n                            properties.getClusterName(),\n                            sentinels,\n                            genericObjectPoolConfig,\n                            Protocol.DEFAULT_TIMEOUT,\n                            Protocol.DEFAULT_TIMEOUT,\n                            password,\n                            properties.getDatabase(),\n                            null,\n                            Protocol.DEFAULT_TIMEOUT,\n                            Protocol.DEFAULT_TIMEOUT,\n                            password,\n                            null));\n        } else {\n            return new JedisSentinel(\n                    new JedisSentinelPool(\n                            properties.getClusterName(), sentinels, genericObjectPoolConfig));\n        }\n    }\n\n    private String getPassword(List<Host> hosts) {\n        return hosts.isEmpty() ? null : hosts.get(0).getPassword();\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisStandaloneConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Configuration;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.redis.jedis.JedisStandalone;\nimport com.netflix.dyno.connectionpool.Host;\nimport com.netflix.dyno.connectionpool.HostSupplier;\nimport com.netflix.dyno.connectionpool.TokenMapSupplier;\n\nimport redis.clients.jedis.JedisPool;\nimport redis.clients.jedis.JedisPoolConfig;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.commands.JedisCommands;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnProperty(name = \"conductor.db.type\", havingValue = \"redis_standalone\")\npublic class RedisStandaloneConfiguration extends JedisCommandsConfigurer {\n\n    private static final Logger log = LoggerFactory.getLogger(RedisSentinelConfiguration.class);\n\n    @Override\n    protected JedisCommands createJedisCommands(\n            RedisProperties properties,\n            ConductorProperties conductorProperties,\n            HostSupplier hostSupplier,\n            TokenMapSupplier tokenMapSupplier) {\n        JedisPoolConfig config = new JedisPoolConfig();\n        config.setMinIdle(2);\n        config.setMaxTotal(properties.getMaxConnectionsPerHost());\n        log.info(\"Starting conductor server using redis_standalone.\");\n        Host host = hostSupplier.getHosts().get(0);\n        return new JedisStandalone(getJedisPool(config, host, properties));\n    }\n\n    private JedisPool getJedisPool(JedisPoolConfig config, Host host, RedisProperties properties) {\n        if (properties.getUsername() != null && host.getPassword() != null) {\n            log.info(\"Connecting to Redis Standalone with AUTH\");\n            return new JedisPool(\n                    config,\n                    host.getHostName(),\n                    host.getPort(),\n                    Protocol.DEFAULT_TIMEOUT,\n                    properties.getUsername(),\n                    host.getPassword(),\n                    properties.getDatabase());\n        } else if (host.getPassword() != null) {\n            log.info(\"Connecting to Redis Standalone with AUTH\");\n            return new JedisPool(\n                    config,\n                    host.getHostName(),\n                    host.getPort(),\n                    Protocol.DEFAULT_TIMEOUT,\n                    host.getPassword(),\n                    properties.getDatabase());\n        } else {\n            return new JedisPool(config, host.getHostName(), host.getPort());\n        }\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dao/BaseDynoDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.io.IOException;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic class BaseDynoDAO {\n\n    private static final String NAMESPACE_SEP = \".\";\n    private static final String DAO_NAME = \"redis\";\n    private final String domain;\n    private final RedisProperties properties;\n    private final ConductorProperties conductorProperties;\n    protected JedisProxy jedisProxy;\n    protected ObjectMapper objectMapper;\n\n    protected BaseDynoDAO(\n            JedisProxy jedisProxy,\n            ObjectMapper objectMapper,\n            ConductorProperties conductorProperties,\n            RedisProperties properties) {\n        this.jedisProxy = jedisProxy;\n        this.objectMapper = objectMapper;\n        this.conductorProperties = conductorProperties;\n        this.properties = properties;\n        this.domain = properties.getKeyspaceDomain();\n    }\n\n    String nsKey(String... nsValues) {\n        String rootNamespace = properties.getWorkflowNamespacePrefix();\n        StringBuilder namespacedKey = new StringBuilder();\n        if (StringUtils.isNotBlank(rootNamespace)) {\n            namespacedKey.append(rootNamespace).append(NAMESPACE_SEP);\n        }\n        String stack = conductorProperties.getStack();\n        if (StringUtils.isNotBlank(stack)) {\n            namespacedKey.append(stack).append(NAMESPACE_SEP);\n        }\n        if (StringUtils.isNotBlank(domain)) {\n            namespacedKey.append(domain).append(NAMESPACE_SEP);\n        }\n        for (String nsValue : nsValues) {\n            namespacedKey.append(nsValue).append(NAMESPACE_SEP);\n        }\n        return StringUtils.removeEnd(namespacedKey.toString(), NAMESPACE_SEP);\n    }\n\n    public JedisProxy getDyno() {\n        return jedisProxy;\n    }\n\n    String toJson(Object value) {\n        try {\n            return objectMapper.writeValueAsString(value);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    <T> T readValue(String json, Class<T> clazz) {\n        try {\n            return objectMapper.readValue(json, clazz);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    void recordRedisDaoRequests(String action) {\n        recordRedisDaoRequests(action, \"n/a\", \"n/a\");\n    }\n\n    void recordRedisDaoRequests(String action, String taskType, String workflowType) {\n        Monitors.recordDaoRequests(DAO_NAME, action, taskType, workflowType);\n    }\n\n    void recordRedisDaoEventRequests(String action, String event) {\n        Monitors.recordDaoEventRequests(DAO_NAME, action, event);\n    }\n\n    void recordRedisDaoPayloadSize(String action, int size, String taskType, String workflowType) {\n        Monitors.recordDaoPayloadSize(\n                DAO_NAME,\n                action,\n                StringUtils.defaultIfBlank(taskType, \"\"),\n                StringUtils.defaultIfBlank(workflowType, \"\"),\n                size);\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dao/DynoQueueDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.redis.config.AnyRedisCondition;\nimport com.netflix.dyno.queues.DynoQueue;\nimport com.netflix.dyno.queues.Message;\nimport com.netflix.dyno.queues.redis.RedisQueues;\n\n@Component\n@Conditional(AnyRedisCondition.class)\npublic class DynoQueueDAO implements QueueDAO {\n\n    private final RedisQueues queues;\n\n    public DynoQueueDAO(RedisQueues queues) {\n        this.queues = queues;\n    }\n\n    @Override\n    public void push(String queueName, String id, long offsetTimeInSecond) {\n        push(queueName, id, -1, offsetTimeInSecond);\n    }\n\n    @Override\n    public void push(String queueName, String id, int priority, long offsetTimeInSecond) {\n        Message msg = new Message(id, null);\n        msg.setTimeout(offsetTimeInSecond, TimeUnit.SECONDS);\n        if (priority >= 0 && priority <= 99) {\n            msg.setPriority(priority);\n        }\n        queues.get(queueName).push(Collections.singletonList(msg));\n    }\n\n    @Override\n    public void push(\n            String queueName, List<com.netflix.conductor.core.events.queue.Message> messages) {\n        List<Message> msgs =\n                messages.stream()\n                        .map(\n                                msg -> {\n                                    Message m = new Message(msg.getId(), msg.getPayload());\n                                    if (msg.getPriority() > 0) {\n                                        m.setPriority(msg.getPriority());\n                                    }\n                                    return m;\n                                })\n                        .collect(Collectors.toList());\n        queues.get(queueName).push(msgs);\n    }\n\n    @Override\n    public boolean pushIfNotExists(String queueName, String id, long offsetTimeInSecond) {\n        return pushIfNotExists(queueName, id, -1, offsetTimeInSecond);\n    }\n\n    @Override\n    public boolean pushIfNotExists(\n            String queueName, String id, int priority, long offsetTimeInSecond) {\n        DynoQueue queue = queues.get(queueName);\n        if (queue.get(id) != null) {\n            return false;\n        }\n        Message msg = new Message(id, null);\n        if (priority >= 0 && priority <= 99) {\n            msg.setPriority(priority);\n        }\n        msg.setTimeout(offsetTimeInSecond, TimeUnit.SECONDS);\n        queue.push(Collections.singletonList(msg));\n        return true;\n    }\n\n    @Override\n    public List<String> pop(String queueName, int count, int timeout) {\n        List<Message> msg = queues.get(queueName).pop(count, timeout, TimeUnit.MILLISECONDS);\n        return msg.stream().map(Message::getId).collect(Collectors.toList());\n    }\n\n    @Override\n    public List<com.netflix.conductor.core.events.queue.Message> pollMessages(\n            String queueName, int count, int timeout) {\n        List<Message> msgs = queues.get(queueName).pop(count, timeout, TimeUnit.MILLISECONDS);\n        return msgs.stream()\n                .map(\n                        msg ->\n                                new com.netflix.conductor.core.events.queue.Message(\n                                        msg.getId(), msg.getPayload(), null, msg.getPriority()))\n                .collect(Collectors.toList());\n    }\n\n    @Override\n    public void remove(String queueName, String messageId) {\n        queues.get(queueName).remove(messageId);\n    }\n\n    @Override\n    public int getSize(String queueName) {\n        return (int) queues.get(queueName).size();\n    }\n\n    @Override\n    public boolean ack(String queueName, String messageId) {\n        return queues.get(queueName).ack(messageId);\n    }\n\n    @Override\n    public boolean setUnackTimeout(String queueName, String messageId, long timeout) {\n        return queues.get(queueName).setUnackTimeout(messageId, timeout);\n    }\n\n    @Override\n    public void flush(String queueName) {\n        DynoQueue queue = queues.get(queueName);\n        if (queue != null) {\n            queue.clear();\n        }\n    }\n\n    @Override\n    public Map<String, Long> queuesDetail() {\n        return queues.queues().stream()\n                .collect(Collectors.toMap(DynoQueue::getName, DynoQueue::size));\n    }\n\n    @Override\n    public Map<String, Map<String, Map<String, Long>>> queuesDetailVerbose() {\n        return queues.queues().stream()\n                .collect(Collectors.toMap(DynoQueue::getName, DynoQueue::shardSizes));\n    }\n\n    public void processUnacks(String queueName) {\n        queues.get(queueName).processUnacks();\n    }\n\n    @Override\n    public boolean resetOffsetTime(String queueName, String id) {\n        DynoQueue queue = queues.get(queueName);\n        return queue.setTimeout(id, 0);\n    }\n\n    @Override\n    public boolean containsMessage(String queueName, String messageId) {\n        DynoQueue queue = queues.get(queueName);\n        Message message = queue.get(messageId);\n        return Objects.nonNull(message);\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dao/RedisEventHandlerDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.ConflictException;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.EventHandlerDAO;\nimport com.netflix.conductor.redis.config.AnyRedisCondition;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.base.Preconditions;\n\n@Component\n@Conditional(AnyRedisCondition.class)\npublic class RedisEventHandlerDAO extends BaseDynoDAO implements EventHandlerDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RedisEventHandlerDAO.class);\n\n    private static final String EVENT_HANDLERS = \"EVENT_HANDLERS\";\n    private static final String EVENT_HANDLERS_BY_EVENT = \"EVENT_HANDLERS_BY_EVENT\";\n\n    public RedisEventHandlerDAO(\n            JedisProxy jedisProxy,\n            ObjectMapper objectMapper,\n            ConductorProperties conductorProperties,\n            RedisProperties properties) {\n        super(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Override\n    public void addEventHandler(EventHandler eventHandler) {\n        Preconditions.checkNotNull(eventHandler.getName(), \"Missing Name\");\n        if (getEventHandler(eventHandler.getName()) != null) {\n            throw new ConflictException(\n                    \"EventHandler with name %s already exists!\", eventHandler.getName());\n        }\n        index(eventHandler);\n        jedisProxy.hset(nsKey(EVENT_HANDLERS), eventHandler.getName(), toJson(eventHandler));\n        recordRedisDaoRequests(\"addEventHandler\");\n    }\n\n    @Override\n    public void updateEventHandler(EventHandler eventHandler) {\n        Preconditions.checkNotNull(eventHandler.getName(), \"Missing Name\");\n        EventHandler existing = getEventHandler(eventHandler.getName());\n        if (existing == null) {\n            throw new NotFoundException(\n                    \"EventHandler with name %s not found!\", eventHandler.getName());\n        }\n        if (!existing.getEvent().equals(eventHandler.getEvent())) {\n            removeIndex(existing);\n        }\n        index(eventHandler);\n        jedisProxy.hset(nsKey(EVENT_HANDLERS), eventHandler.getName(), toJson(eventHandler));\n        recordRedisDaoRequests(\"updateEventHandler\");\n    }\n\n    @Override\n    public void removeEventHandler(String name) {\n        EventHandler existing = getEventHandler(name);\n        if (existing == null) {\n            throw new NotFoundException(\"EventHandler with name %s not found!\", name);\n        }\n        jedisProxy.hdel(nsKey(EVENT_HANDLERS), name);\n        recordRedisDaoRequests(\"removeEventHandler\");\n        removeIndex(existing);\n    }\n\n    @Override\n    public List<EventHandler> getAllEventHandlers() {\n        Map<String, String> all = jedisProxy.hgetAll(nsKey(EVENT_HANDLERS));\n        List<EventHandler> handlers = new LinkedList<>();\n        all.forEach(\n                (key, json) -> {\n                    EventHandler eventHandler = readValue(json, EventHandler.class);\n                    handlers.add(eventHandler);\n                });\n        recordRedisDaoRequests(\"getAllEventHandlers\");\n        return handlers;\n    }\n\n    private void index(EventHandler eventHandler) {\n        String event = eventHandler.getEvent();\n        String key = nsKey(EVENT_HANDLERS_BY_EVENT, event);\n        jedisProxy.sadd(key, eventHandler.getName());\n    }\n\n    private void removeIndex(EventHandler eventHandler) {\n        String event = eventHandler.getEvent();\n        String key = nsKey(EVENT_HANDLERS_BY_EVENT, event);\n        jedisProxy.srem(key, eventHandler.getName());\n    }\n\n    @Override\n    public List<EventHandler> getEventHandlersForEvent(String event, boolean activeOnly) {\n        String key = nsKey(EVENT_HANDLERS_BY_EVENT, event);\n        Set<String> names = jedisProxy.smembers(key);\n        List<EventHandler> handlers = new LinkedList<>();\n        for (String name : names) {\n            try {\n                EventHandler eventHandler = getEventHandler(name);\n                recordRedisDaoEventRequests(\"getEventHandler\", event);\n                if (eventHandler.getEvent().equals(event)\n                        && (!activeOnly || eventHandler.isActive())) {\n                    handlers.add(eventHandler);\n                }\n            } catch (NotFoundException nfe) {\n                LOGGER.info(\"No matching event handler found for event: {}\", event);\n                throw nfe;\n            }\n        }\n        return handlers;\n    }\n\n    private EventHandler getEventHandler(String name) {\n        EventHandler eventHandler = null;\n        String json;\n        try {\n            json = jedisProxy.hget(nsKey(EVENT_HANDLERS), name);\n        } catch (Exception e) {\n            throw new TransientException(\"Unable to get event handler named \" + name, e);\n        }\n        if (json != null) {\n            eventHandler = readValue(json, EventHandler.class);\n        }\n        return eventHandler;\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dao/RedisExecutionDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.events.EventExecution;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.dao.ConcurrentExecutionLimitDAO;\nimport com.netflix.conductor.dao.ExecutionDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\nimport com.netflix.conductor.redis.config.AnyRedisCondition;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\n\n@Component\n@Conditional(AnyRedisCondition.class)\npublic class RedisExecutionDAO extends BaseDynoDAO\n        implements ExecutionDAO, ConcurrentExecutionLimitDAO {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(RedisExecutionDAO.class);\n\n    // Keys Families\n    private static final String TASK_LIMIT_BUCKET = \"TASK_LIMIT_BUCKET\";\n    private static final String IN_PROGRESS_TASKS = \"IN_PROGRESS_TASKS\";\n    private static final String TASKS_IN_PROGRESS_STATUS =\n            \"TASKS_IN_PROGRESS_STATUS\"; // Tasks which are in IN_PROGRESS status.\n    private static final String WORKFLOW_TO_TASKS = \"WORKFLOW_TO_TASKS\";\n    private static final String SCHEDULED_TASKS = \"SCHEDULED_TASKS\";\n    private static final String TASK = \"TASK\";\n    private static final String WORKFLOW = \"WORKFLOW\";\n    private static final String PENDING_WORKFLOWS = \"PENDING_WORKFLOWS\";\n    private static final String WORKFLOW_DEF_TO_WORKFLOWS = \"WORKFLOW_DEF_TO_WORKFLOWS\";\n    private static final String CORR_ID_TO_WORKFLOWS = \"CORR_ID_TO_WORKFLOWS\";\n    private static final String EVENT_EXECUTION = \"EVENT_EXECUTION\";\n    private final int ttlEventExecutionSeconds;\n\n    public RedisExecutionDAO(\n            JedisProxy jedisProxy,\n            ObjectMapper objectMapper,\n            ConductorProperties conductorProperties,\n            RedisProperties properties) {\n        super(jedisProxy, objectMapper, conductorProperties, properties);\n\n        ttlEventExecutionSeconds = (int) properties.getEventExecutionPersistenceTTL().getSeconds();\n    }\n\n    private static String dateStr(Long timeInMs) {\n        Date date = new Date(timeInMs);\n        return dateStr(date);\n    }\n\n    private static String dateStr(Date date) {\n        SimpleDateFormat format = new SimpleDateFormat(\"yyyyMMdd\");\n        return format.format(date);\n    }\n\n    private static List<String> dateStrBetweenDates(Long startdatems, Long enddatems) {\n        List<String> dates = new ArrayList<>();\n        Calendar calendar = new GregorianCalendar();\n        Date startdate = new Date(startdatems);\n        Date enddate = new Date(enddatems);\n        calendar.setTime(startdate);\n        while (calendar.getTime().before(enddate) || calendar.getTime().equals(enddate)) {\n            Date result = calendar.getTime();\n            dates.add(dateStr(result));\n            calendar.add(Calendar.DATE, 1);\n        }\n        return dates;\n    }\n\n    @Override\n    public List<TaskModel> getPendingTasksByWorkflow(String taskName, String workflowId) {\n        List<TaskModel> tasks = new LinkedList<>();\n\n        List<TaskModel> pendingTasks = getPendingTasksForTaskType(taskName);\n        pendingTasks.forEach(\n                pendingTask -> {\n                    if (pendingTask.getWorkflowInstanceId().equals(workflowId)) {\n                        tasks.add(pendingTask);\n                    }\n                });\n\n        return tasks;\n    }\n\n    @Override\n    public List<TaskModel> getTasks(String taskDefName, String startKey, int count) {\n        List<TaskModel> tasks = new LinkedList<>();\n\n        List<TaskModel> pendingTasks = getPendingTasksForTaskType(taskDefName);\n        boolean startKeyFound = startKey == null;\n        int foundcount = 0;\n        for (TaskModel pendingTask : pendingTasks) {\n            if (!startKeyFound) {\n                if (pendingTask.getTaskId().equals(startKey)) {\n                    startKeyFound = true;\n                    if (startKey != null) {\n                        continue;\n                    }\n                }\n            }\n            if (startKeyFound && foundcount < count) {\n                tasks.add(pendingTask);\n                foundcount++;\n            }\n        }\n        return tasks;\n    }\n\n    @Override\n    public List<TaskModel> createTasks(List<TaskModel> tasks) {\n\n        List<TaskModel> tasksCreated = new LinkedList<>();\n\n        for (TaskModel task : tasks) {\n            validate(task);\n\n            recordRedisDaoRequests(\"createTask\", task.getTaskType(), task.getWorkflowType());\n\n            String taskKey = task.getReferenceTaskName() + \"\" + task.getRetryCount();\n            Long added =\n                    jedisProxy.hset(\n                            nsKey(SCHEDULED_TASKS, task.getWorkflowInstanceId()),\n                            taskKey,\n                            task.getTaskId());\n            if (added < 1) {\n                LOGGER.debug(\n                        \"Task already scheduled, skipping the run \"\n                                + task.getTaskId()\n                                + \", ref=\"\n                                + task.getReferenceTaskName()\n                                + \", key=\"\n                                + taskKey);\n                continue;\n            }\n\n            if (task.getStatus() != null\n                    && !task.getStatus().isTerminal()\n                    && task.getScheduledTime() == 0) {\n                task.setScheduledTime(System.currentTimeMillis());\n            }\n\n            correlateTaskToWorkflowInDS(task.getTaskId(), task.getWorkflowInstanceId());\n            LOGGER.debug(\n                    \"Scheduled task added to WORKFLOW_TO_TASKS workflowId: {}, taskId: {}, taskType: {} during createTasks\",\n                    task.getWorkflowInstanceId(),\n                    task.getTaskId(),\n                    task.getTaskType());\n\n            String inProgressTaskKey = nsKey(IN_PROGRESS_TASKS, task.getTaskDefName());\n            jedisProxy.sadd(inProgressTaskKey, task.getTaskId());\n            LOGGER.debug(\n                    \"Scheduled task added to IN_PROGRESS_TASKS with inProgressTaskKey: {}, workflowId: {}, taskId: {}, taskType: {} during createTasks\",\n                    inProgressTaskKey,\n                    task.getWorkflowInstanceId(),\n                    task.getTaskId(),\n                    task.getTaskType());\n\n            updateTask(task);\n            tasksCreated.add(task);\n        }\n\n        return tasksCreated;\n    }\n\n    @Override\n    public void updateTask(TaskModel task) {\n        Optional<TaskDef> taskDefinition = task.getTaskDefinition();\n\n        if (taskDefinition.isPresent() && taskDefinition.get().concurrencyLimit() > 0) {\n\n            if (task.getStatus() != null && task.getStatus().equals(TaskModel.Status.IN_PROGRESS)) {\n                jedisProxy.sadd(\n                        nsKey(TASKS_IN_PROGRESS_STATUS, task.getTaskDefName()), task.getTaskId());\n                LOGGER.debug(\n                        \"Workflow Task added to TASKS_IN_PROGRESS_STATUS with tasksInProgressKey: {}, workflowId: {}, taskId: {}, taskType: {}, taskStatus: {} during updateTask\",\n                        nsKey(TASKS_IN_PROGRESS_STATUS, task.getTaskDefName(), task.getTaskId()),\n                        task.getWorkflowInstanceId(),\n                        task.getTaskId(),\n                        task.getTaskType(),\n                        task.getStatus().name());\n            } else {\n                jedisProxy.srem(\n                        nsKey(TASKS_IN_PROGRESS_STATUS, task.getTaskDefName()), task.getTaskId());\n                LOGGER.debug(\n                        \"Workflow Task removed from TASKS_IN_PROGRESS_STATUS with tasksInProgressKey: {}, workflowId: {}, taskId: {}, taskType: {}, taskStatus: {} during updateTask\",\n                        nsKey(TASKS_IN_PROGRESS_STATUS, task.getTaskDefName(), task.getTaskId()),\n                        task.getWorkflowInstanceId(),\n                        task.getTaskId(),\n                        task.getTaskType(),\n                        task.getStatus().name());\n                String key = nsKey(TASK_LIMIT_BUCKET, task.getTaskDefName());\n                jedisProxy.zrem(key, task.getTaskId());\n                LOGGER.debug(\n                        \"Workflow Task removed from TASK_LIMIT_BUCKET with taskLimitBucketKey: {}, workflowId: {}, taskId: {}, taskType: {}, taskStatus: {} during updateTask\",\n                        key,\n                        task.getWorkflowInstanceId(),\n                        task.getTaskId(),\n                        task.getTaskType(),\n                        task.getStatus().name());\n            }\n        }\n\n        String payload = toJson(task);\n        recordRedisDaoPayloadSize(\n                \"updateTask\",\n                payload.length(),\n                taskDefinition.map(TaskDef::getName).orElse(\"n/a\"),\n                task.getWorkflowType());\n\n        recordRedisDaoRequests(\"updateTask\", task.getTaskType(), task.getWorkflowType());\n        jedisProxy.set(nsKey(TASK, task.getTaskId()), payload);\n        LOGGER.debug(\n                \"Workflow task payload saved to TASK with taskKey: {}, workflowId: {}, taskId: {}, taskType: {} during updateTask\",\n                nsKey(TASK, task.getTaskId()),\n                task.getWorkflowInstanceId(),\n                task.getTaskId(),\n                task.getTaskType());\n        if (task.getStatus() != null && task.getStatus().isTerminal()) {\n            jedisProxy.srem(nsKey(IN_PROGRESS_TASKS, task.getTaskDefName()), task.getTaskId());\n            LOGGER.debug(\n                    \"Workflow Task removed from TASKS_IN_PROGRESS_STATUS with tasksInProgressKey: {}, workflowId: {}, taskId: {}, taskType: {}, taskStatus: {} during updateTask\",\n                    nsKey(IN_PROGRESS_TASKS, task.getTaskDefName()),\n                    task.getWorkflowInstanceId(),\n                    task.getTaskId(),\n                    task.getTaskType(),\n                    task.getStatus().name());\n        }\n\n        Set<String> taskIds =\n                jedisProxy.smembers(nsKey(WORKFLOW_TO_TASKS, task.getWorkflowInstanceId()));\n        if (!taskIds.contains(task.getTaskId())) {\n            correlateTaskToWorkflowInDS(task.getTaskId(), task.getWorkflowInstanceId());\n        }\n    }\n\n    @Override\n    public boolean exceedsLimit(TaskModel task) {\n        Optional<TaskDef> taskDefinition = task.getTaskDefinition();\n        if (taskDefinition.isEmpty()) {\n            return false;\n        }\n        int limit = taskDefinition.get().concurrencyLimit();\n        if (limit <= 0) {\n            return false;\n        }\n\n        long current = getInProgressTaskCount(task.getTaskDefName());\n        if (current >= limit) {\n            LOGGER.info(\n                    \"Task execution count limited. task - {}:{}, limit: {}, current: {}\",\n                    task.getTaskId(),\n                    task.getTaskDefName(),\n                    limit,\n                    current);\n            Monitors.recordTaskConcurrentExecutionLimited(task.getTaskDefName(), limit);\n            return true;\n        }\n\n        String rateLimitKey = nsKey(TASK_LIMIT_BUCKET, task.getTaskDefName());\n        double score = System.currentTimeMillis();\n        String taskId = task.getTaskId();\n        jedisProxy.zaddnx(rateLimitKey, score, taskId);\n        recordRedisDaoRequests(\"checkTaskRateLimiting\", task.getTaskType(), task.getWorkflowType());\n\n        Set<String> ids = jedisProxy.zrangeByScore(rateLimitKey, 0, score + 1, limit);\n        boolean rateLimited = !ids.contains(taskId);\n        if (rateLimited) {\n            LOGGER.info(\n                    \"Task execution count limited. task - {}:{}, limit: {}, current: {}\",\n                    task.getTaskId(),\n                    task.getTaskDefName(),\n                    limit,\n                    current);\n            String inProgressKey = nsKey(TASKS_IN_PROGRESS_STATUS, task.getTaskDefName());\n            // Cleanup any items that are still present in the rate limit bucket but not in progress\n            // anymore!\n            ids.stream()\n                    .filter(id -> !jedisProxy.sismember(inProgressKey, id))\n                    .forEach(id2 -> jedisProxy.zrem(rateLimitKey, id2));\n            Monitors.recordTaskRateLimited(task.getTaskDefName(), limit);\n        }\n        return rateLimited;\n    }\n\n    private void removeTaskMappings(TaskModel task) {\n        String taskKey = task.getReferenceTaskName() + \"\" + task.getRetryCount();\n\n        jedisProxy.hdel(nsKey(SCHEDULED_TASKS, task.getWorkflowInstanceId()), taskKey);\n        jedisProxy.srem(nsKey(IN_PROGRESS_TASKS, task.getTaskDefName()), task.getTaskId());\n        jedisProxy.srem(nsKey(WORKFLOW_TO_TASKS, task.getWorkflowInstanceId()), task.getTaskId());\n        jedisProxy.srem(nsKey(TASKS_IN_PROGRESS_STATUS, task.getTaskDefName()), task.getTaskId());\n        jedisProxy.zrem(nsKey(TASK_LIMIT_BUCKET, task.getTaskDefName()), task.getTaskId());\n    }\n\n    private void removeTaskMappingsWithExpiry(TaskModel task) {\n        String taskKey = task.getReferenceTaskName() + \"\" + task.getRetryCount();\n\n        jedisProxy.hdel(nsKey(SCHEDULED_TASKS, task.getWorkflowInstanceId()), taskKey);\n        jedisProxy.srem(nsKey(IN_PROGRESS_TASKS, task.getTaskDefName()), task.getTaskId());\n        jedisProxy.srem(nsKey(TASKS_IN_PROGRESS_STATUS, task.getTaskDefName()), task.getTaskId());\n        jedisProxy.zrem(nsKey(TASK_LIMIT_BUCKET, task.getTaskDefName()), task.getTaskId());\n    }\n\n    @Override\n    public boolean removeTask(String taskId) {\n        TaskModel task = getTask(taskId);\n        if (task == null) {\n            LOGGER.warn(\"No such task found by id {}\", taskId);\n            return false;\n        }\n        removeTaskMappings(task);\n\n        jedisProxy.del(nsKey(TASK, task.getTaskId()));\n        recordRedisDaoRequests(\"removeTask\", task.getTaskType(), task.getWorkflowType());\n        return true;\n    }\n\n    private boolean removeTaskWithExpiry(String taskId, int ttlSeconds) {\n        TaskModel task = getTask(taskId);\n        if (task == null) {\n            LOGGER.warn(\"No such task found by id {}\", taskId);\n            return false;\n        }\n        removeTaskMappingsWithExpiry(task);\n\n        jedisProxy.expire(nsKey(TASK, task.getTaskId()), ttlSeconds);\n        recordRedisDaoRequests(\"removeTask\", task.getTaskType(), task.getWorkflowType());\n        return true;\n    }\n\n    @Override\n    public TaskModel getTask(String taskId) {\n        Preconditions.checkNotNull(taskId, \"taskId cannot be null\");\n        return Optional.ofNullable(jedisProxy.get(nsKey(TASK, taskId)))\n                .map(\n                        json -> {\n                            TaskModel task = readValue(json, TaskModel.class);\n                            recordRedisDaoRequests(\n                                    \"getTask\", task.getTaskType(), task.getWorkflowType());\n                            recordRedisDaoPayloadSize(\n                                    \"getTask\",\n                                    toJson(task).length(),\n                                    task.getTaskType(),\n                                    task.getWorkflowType());\n                            return task;\n                        })\n                .orElse(null);\n    }\n\n    @Override\n    public List<TaskModel> getTasks(List<String> taskIds) {\n        return taskIds.stream()\n                .map(taskId -> nsKey(TASK, taskId))\n                .map(jedisProxy::get)\n                .filter(Objects::nonNull)\n                .map(\n                        jsonString -> {\n                            TaskModel task = readValue(jsonString, TaskModel.class);\n                            recordRedisDaoRequests(\n                                    \"getTask\", task.getTaskType(), task.getWorkflowType());\n                            recordRedisDaoPayloadSize(\n                                    \"getTask\",\n                                    jsonString.length(),\n                                    task.getTaskType(),\n                                    task.getWorkflowType());\n                            return task;\n                        })\n                .collect(Collectors.toList());\n    }\n\n    @Override\n    public List<TaskModel> getTasksForWorkflow(String workflowId) {\n        Preconditions.checkNotNull(workflowId, \"workflowId cannot be null\");\n        Set<String> taskIds = jedisProxy.smembers(nsKey(WORKFLOW_TO_TASKS, workflowId));\n        recordRedisDaoRequests(\"getTasksForWorkflow\");\n        return getTasks(new ArrayList<>(taskIds));\n    }\n\n    @Override\n    public List<TaskModel> getPendingTasksForTaskType(String taskName) {\n        Preconditions.checkNotNull(taskName, \"task name cannot be null\");\n        Set<String> taskIds = jedisProxy.smembers(nsKey(IN_PROGRESS_TASKS, taskName));\n        recordRedisDaoRequests(\"getPendingTasksForTaskType\");\n        return getTasks(new ArrayList<>(taskIds));\n    }\n\n    @Override\n    public String createWorkflow(WorkflowModel workflow) {\n        return insertOrUpdateWorkflow(workflow, false);\n    }\n\n    @Override\n    public String updateWorkflow(WorkflowModel workflow) {\n        return insertOrUpdateWorkflow(workflow, true);\n    }\n\n    @Override\n    public boolean removeWorkflow(String workflowId) {\n        WorkflowModel workflow = getWorkflow(workflowId, true);\n        if (workflow != null) {\n            recordRedisDaoRequests(\"removeWorkflow\");\n\n            // Remove from lists\n            String key =\n                    nsKey(\n                            WORKFLOW_DEF_TO_WORKFLOWS,\n                            workflow.getWorkflowName(),\n                            dateStr(workflow.getCreateTime()));\n            jedisProxy.srem(key, workflowId);\n            jedisProxy.srem(nsKey(CORR_ID_TO_WORKFLOWS, workflow.getCorrelationId()), workflowId);\n            jedisProxy.srem(nsKey(PENDING_WORKFLOWS, workflow.getWorkflowName()), workflowId);\n\n            // Remove the object\n            jedisProxy.del(nsKey(WORKFLOW, workflowId));\n            for (TaskModel task : workflow.getTasks()) {\n                removeTask(task.getTaskId());\n            }\n            return true;\n        }\n        return false;\n    }\n\n    public boolean removeWorkflowWithExpiry(String workflowId, int ttlSeconds) {\n        WorkflowModel workflow = getWorkflow(workflowId, true);\n        if (workflow != null) {\n            recordRedisDaoRequests(\"removeWorkflow\");\n\n            // Remove from lists\n            String key =\n                    nsKey(\n                            WORKFLOW_DEF_TO_WORKFLOWS,\n                            workflow.getWorkflowName(),\n                            dateStr(workflow.getCreateTime()));\n            jedisProxy.srem(key, workflowId);\n            jedisProxy.srem(nsKey(CORR_ID_TO_WORKFLOWS, workflow.getCorrelationId()), workflowId);\n            jedisProxy.srem(nsKey(PENDING_WORKFLOWS, workflow.getWorkflowName()), workflowId);\n\n            // Remove the object\n            jedisProxy.expire(nsKey(WORKFLOW, workflowId), ttlSeconds);\n            for (TaskModel task : workflow.getTasks()) {\n                removeTaskWithExpiry(task.getTaskId(), ttlSeconds);\n            }\n            jedisProxy.expire(nsKey(WORKFLOW_TO_TASKS, workflowId), ttlSeconds);\n\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public void removeFromPendingWorkflow(String workflowType, String workflowId) {\n        recordRedisDaoRequests(\"removePendingWorkflow\");\n        jedisProxy.del(nsKey(SCHEDULED_TASKS, workflowId));\n        jedisProxy.srem(nsKey(PENDING_WORKFLOWS, workflowType), workflowId);\n    }\n\n    @Override\n    public WorkflowModel getWorkflow(String workflowId) {\n        return getWorkflow(workflowId, true);\n    }\n\n    @Override\n    public WorkflowModel getWorkflow(String workflowId, boolean includeTasks) {\n        String json = jedisProxy.get(nsKey(WORKFLOW, workflowId));\n        WorkflowModel workflow = null;\n\n        if (json != null) {\n            workflow = readValue(json, WorkflowModel.class);\n            recordRedisDaoRequests(\"getWorkflow\", \"n/a\", workflow.getWorkflowName());\n            recordRedisDaoPayloadSize(\n                    \"getWorkflow\", json.length(), \"n/a\", workflow.getWorkflowName());\n            if (includeTasks) {\n                List<TaskModel> tasks = getTasksForWorkflow(workflowId);\n                tasks.sort(Comparator.comparingInt(TaskModel::getSeq));\n                workflow.setTasks(tasks);\n            }\n        }\n        return workflow;\n    }\n\n    /**\n     * @param workflowName name of the workflow\n     * @param version the workflow version\n     * @return list of workflow ids that are in RUNNING state <em>returns workflows of all versions\n     *     for the given workflow name</em>\n     */\n    @Override\n    public List<String> getRunningWorkflowIds(String workflowName, int version) {\n        Preconditions.checkNotNull(workflowName, \"workflowName cannot be null\");\n        List<String> workflowIds;\n        recordRedisDaoRequests(\"getRunningWorkflowsByName\");\n        Set<String> pendingWorkflows = jedisProxy.smembers(nsKey(PENDING_WORKFLOWS, workflowName));\n        workflowIds = new LinkedList<>(pendingWorkflows);\n        return workflowIds;\n    }\n\n    /**\n     * @param workflowName name of the workflow\n     * @param version the workflow version\n     * @return list of workflows that are in RUNNING state\n     */\n    @Override\n    public List<WorkflowModel> getPendingWorkflowsByType(String workflowName, int version) {\n        Preconditions.checkNotNull(workflowName, \"workflowName cannot be null\");\n        List<String> workflowIds = getRunningWorkflowIds(workflowName, version);\n        return workflowIds.stream()\n                .map(this::getWorkflow)\n                .filter(workflow -> workflow.getWorkflowVersion() == version)\n                .collect(Collectors.toList());\n    }\n\n    @Override\n    public List<WorkflowModel> getWorkflowsByType(\n            String workflowName, Long startTime, Long endTime) {\n        Preconditions.checkNotNull(workflowName, \"workflowName cannot be null\");\n        Preconditions.checkNotNull(startTime, \"startTime cannot be null\");\n        Preconditions.checkNotNull(endTime, \"endTime cannot be null\");\n\n        List<WorkflowModel> workflows = new LinkedList<>();\n\n        // Get all date strings between start and end\n        List<String> dateStrs = dateStrBetweenDates(startTime, endTime);\n        dateStrs.forEach(\n                dateStr -> {\n                    String key = nsKey(WORKFLOW_DEF_TO_WORKFLOWS, workflowName, dateStr);\n                    jedisProxy\n                            .smembers(key)\n                            .forEach(\n                                    workflowId -> {\n                                        try {\n                                            WorkflowModel workflow = getWorkflow(workflowId);\n                                            if (workflow.getCreateTime() >= startTime\n                                                    && workflow.getCreateTime() <= endTime) {\n                                                workflows.add(workflow);\n                                            }\n                                        } catch (Exception e) {\n                                            LOGGER.error(\n                                                    \"Failed to get workflow: {}\", workflowId, e);\n                                        }\n                                    });\n                });\n\n        return workflows;\n    }\n\n    @Override\n    public List<WorkflowModel> getWorkflowsByCorrelationId(\n            String workflowName, String correlationId, boolean includeTasks) {\n        throw new UnsupportedOperationException(\n                \"This method is not implemented in RedisExecutionDAO. Please use ExecutionDAOFacade instead.\");\n    }\n\n    @Override\n    public boolean canSearchAcrossWorkflows() {\n        return false;\n    }\n\n    /**\n     * Inserts a new workflow/ updates an existing workflow in the datastore. Additionally, if a\n     * workflow is in terminal state, it is removed from the set of pending workflows.\n     *\n     * @param workflow the workflow instance\n     * @param update flag to identify if update or create operation\n     * @return the workflowId\n     */\n    private String insertOrUpdateWorkflow(WorkflowModel workflow, boolean update) {\n        Preconditions.checkNotNull(workflow, \"workflow object cannot be null\");\n\n        List<TaskModel> tasks = workflow.getTasks();\n        workflow.setTasks(new LinkedList<>());\n\n        String payload = toJson(workflow);\n        // Store the workflow object\n        jedisProxy.set(nsKey(WORKFLOW, workflow.getWorkflowId()), payload);\n        recordRedisDaoRequests(\"storeWorkflow\", \"n/a\", workflow.getWorkflowName());\n        recordRedisDaoPayloadSize(\n                \"storeWorkflow\", payload.length(), \"n/a\", workflow.getWorkflowName());\n        if (!update) {\n            // Add to list of workflows for a workflowdef\n            String key =\n                    nsKey(\n                            WORKFLOW_DEF_TO_WORKFLOWS,\n                            workflow.getWorkflowName(),\n                            dateStr(workflow.getCreateTime()));\n            jedisProxy.sadd(key, workflow.getWorkflowId());\n            if (workflow.getCorrelationId() != null) {\n                // Add to list of workflows for a correlationId\n                jedisProxy.sadd(\n                        nsKey(CORR_ID_TO_WORKFLOWS, workflow.getCorrelationId()),\n                        workflow.getWorkflowId());\n            }\n        }\n        // Add or remove from the pending workflows\n        if (workflow.getStatus().isTerminal()) {\n            jedisProxy.srem(\n                    nsKey(PENDING_WORKFLOWS, workflow.getWorkflowName()), workflow.getWorkflowId());\n        } else {\n            jedisProxy.sadd(\n                    nsKey(PENDING_WORKFLOWS, workflow.getWorkflowName()), workflow.getWorkflowId());\n        }\n\n        workflow.setTasks(tasks);\n        return workflow.getWorkflowId();\n    }\n\n    /**\n     * Stores the correlation of a task to the workflow instance in the datastore\n     *\n     * @param taskId the taskId to be correlated\n     * @param workflowInstanceId the workflowId to which the tasks belongs to\n     */\n    @VisibleForTesting\n    void correlateTaskToWorkflowInDS(String taskId, String workflowInstanceId) {\n        String workflowToTaskKey = nsKey(WORKFLOW_TO_TASKS, workflowInstanceId);\n        jedisProxy.sadd(workflowToTaskKey, taskId);\n        LOGGER.debug(\n                \"Task mapped in WORKFLOW_TO_TASKS with workflowToTaskKey: {}, workflowId: {}, taskId: {}\",\n                workflowToTaskKey,\n                workflowInstanceId,\n                taskId);\n    }\n\n    public long getPendingWorkflowCount(String workflowName) {\n        String key = nsKey(PENDING_WORKFLOWS, workflowName);\n        recordRedisDaoRequests(\"getPendingWorkflowCount\");\n        return jedisProxy.scard(key);\n    }\n\n    @Override\n    public long getInProgressTaskCount(String taskDefName) {\n        String inProgressKey = nsKey(TASKS_IN_PROGRESS_STATUS, taskDefName);\n        recordRedisDaoRequests(\"getInProgressTaskCount\");\n        return jedisProxy.scard(inProgressKey);\n    }\n\n    @Override\n    public boolean addEventExecution(EventExecution eventExecution) {\n        try {\n            String key =\n                    nsKey(\n                            EVENT_EXECUTION,\n                            eventExecution.getName(),\n                            eventExecution.getEvent(),\n                            eventExecution.getMessageId());\n            String json = objectMapper.writeValueAsString(eventExecution);\n            recordRedisDaoEventRequests(\"addEventExecution\", eventExecution.getEvent());\n            recordRedisDaoPayloadSize(\n                    \"addEventExecution\", json.length(), eventExecution.getEvent(), \"n/a\");\n            boolean added = jedisProxy.hsetnx(key, eventExecution.getId(), json) == 1L;\n\n            if (ttlEventExecutionSeconds > 0) {\n                jedisProxy.expire(key, ttlEventExecutionSeconds);\n            }\n\n            return added;\n        } catch (Exception e) {\n            throw new TransientException(\n                    \"Unable to add event execution for \" + eventExecution.getId(), e);\n        }\n    }\n\n    @Override\n    public void updateEventExecution(EventExecution eventExecution) {\n        try {\n\n            String key =\n                    nsKey(\n                            EVENT_EXECUTION,\n                            eventExecution.getName(),\n                            eventExecution.getEvent(),\n                            eventExecution.getMessageId());\n            String json = objectMapper.writeValueAsString(eventExecution);\n            LOGGER.info(\"updating event execution {}\", key);\n            jedisProxy.hset(key, eventExecution.getId(), json);\n            recordRedisDaoEventRequests(\"updateEventExecution\", eventExecution.getEvent());\n            recordRedisDaoPayloadSize(\n                    \"updateEventExecution\", json.length(), eventExecution.getEvent(), \"n/a\");\n        } catch (Exception e) {\n            throw new TransientException(\n                    \"Unable to update event execution for \" + eventExecution.getId(), e);\n        }\n    }\n\n    @Override\n    public void removeEventExecution(EventExecution eventExecution) {\n        try {\n            String key =\n                    nsKey(\n                            EVENT_EXECUTION,\n                            eventExecution.getName(),\n                            eventExecution.getEvent(),\n                            eventExecution.getMessageId());\n            LOGGER.info(\"removing event execution {}\", key);\n            jedisProxy.hdel(key, eventExecution.getId());\n            recordRedisDaoEventRequests(\"removeEventExecution\", eventExecution.getEvent());\n        } catch (Exception e) {\n            throw new TransientException(\n                    \"Unable to remove event execution for \" + eventExecution.getId(), e);\n        }\n    }\n\n    public List<EventExecution> getEventExecutions(\n            String eventHandlerName, String eventName, String messageId, int max) {\n        try {\n            String key = nsKey(EVENT_EXECUTION, eventHandlerName, eventName, messageId);\n            LOGGER.info(\"getting event execution {}\", key);\n            List<EventExecution> executions = new LinkedList<>();\n            for (int i = 0; i < max; i++) {\n                String field = messageId + \"_\" + i;\n                String value = jedisProxy.hget(key, field);\n                if (value == null) {\n                    break;\n                }\n                recordRedisDaoEventRequests(\"getEventExecution\", eventHandlerName);\n                recordRedisDaoPayloadSize(\n                        \"getEventExecution\", value.length(), eventHandlerName, \"n/a\");\n                EventExecution eventExecution = objectMapper.readValue(value, EventExecution.class);\n                executions.add(eventExecution);\n            }\n            return executions;\n\n        } catch (Exception e) {\n            throw new TransientException(\n                    \"Unable to get event executions for \" + eventHandlerName, e);\n        }\n    }\n\n    private void validate(TaskModel task) {\n        try {\n            Preconditions.checkNotNull(task, \"task object cannot be null\");\n            Preconditions.checkNotNull(task.getTaskId(), \"Task id cannot be null\");\n            Preconditions.checkNotNull(\n                    task.getWorkflowInstanceId(), \"Workflow instance id cannot be null\");\n            Preconditions.checkNotNull(\n                    task.getReferenceTaskName(), \"Task reference name cannot be null\");\n        } catch (NullPointerException npe) {\n            throw new IllegalArgumentException(npe.getMessage(), npe);\n        }\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dao/RedisMetadataDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.ConflictException;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.dao.MetadataDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.redis.config.AnyRedisCondition;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.base.Preconditions;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskDef.ONE_HOUR;\n\n@Component\n@Conditional(AnyRedisCondition.class)\npublic class RedisMetadataDAO extends BaseDynoDAO implements MetadataDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RedisMetadataDAO.class);\n\n    // Keys Families\n    private static final String ALL_TASK_DEFS = \"TASK_DEFS\";\n    private static final String WORKFLOW_DEF_NAMES = \"WORKFLOW_DEF_NAMES\";\n    private static final String WORKFLOW_DEF = \"WORKFLOW_DEF\";\n    private static final String LATEST = \"latest\";\n    private static final String className = RedisMetadataDAO.class.getSimpleName();\n    private Map<String, TaskDef> taskDefCache = new HashMap<>();\n\n    public RedisMetadataDAO(\n            JedisProxy jedisProxy,\n            ObjectMapper objectMapper,\n            ConductorProperties conductorProperties,\n            RedisProperties properties) {\n        super(jedisProxy, objectMapper, conductorProperties, properties);\n        refreshTaskDefs();\n        long cacheRefreshTime = properties.getTaskDefCacheRefreshInterval().getSeconds();\n        Executors.newSingleThreadScheduledExecutor()\n                .scheduleWithFixedDelay(\n                        this::refreshTaskDefs,\n                        cacheRefreshTime,\n                        cacheRefreshTime,\n                        TimeUnit.SECONDS);\n    }\n\n    @Override\n    public TaskDef createTaskDef(TaskDef taskDef) {\n        return insertOrUpdateTaskDef(taskDef);\n    }\n\n    @Override\n    public TaskDef updateTaskDef(TaskDef taskDef) {\n        return insertOrUpdateTaskDef(taskDef);\n    }\n\n    private TaskDef insertOrUpdateTaskDef(TaskDef taskDef) {\n        // Store all task def in under one key\n        String payload = toJson(taskDef);\n        jedisProxy.hset(nsKey(ALL_TASK_DEFS), taskDef.getName(), payload);\n        recordRedisDaoRequests(\"storeTaskDef\");\n        recordRedisDaoPayloadSize(\"storeTaskDef\", payload.length(), taskDef.getName(), \"n/a\");\n        refreshTaskDefs();\n        return taskDef;\n    }\n\n    private void refreshTaskDefs() {\n        try {\n            Map<String, TaskDef> map = new HashMap<>();\n            getAllTaskDefs().forEach(taskDef -> map.put(taskDef.getName(), taskDef));\n            this.taskDefCache = map;\n            LOGGER.debug(\"Refreshed task defs \" + this.taskDefCache.size());\n        } catch (Exception e) {\n            Monitors.error(className, \"refreshTaskDefs\");\n            LOGGER.error(\"refresh TaskDefs failed \", e);\n        }\n    }\n\n    @Override\n    public TaskDef getTaskDef(String name) {\n        return Optional.ofNullable(taskDefCache.get(name)).orElseGet(() -> getTaskDefFromDB(name));\n    }\n\n    private TaskDef getTaskDefFromDB(String name) {\n        Preconditions.checkNotNull(name, \"TaskDef name cannot be null\");\n\n        TaskDef taskDef = null;\n        String taskDefJsonStr = jedisProxy.hget(nsKey(ALL_TASK_DEFS), name);\n        if (taskDefJsonStr != null) {\n            taskDef = readValue(taskDefJsonStr, TaskDef.class);\n            recordRedisDaoRequests(\"getTaskDef\");\n            recordRedisDaoPayloadSize(\n                    \"getTaskDef\", taskDefJsonStr.length(), taskDef.getName(), \"n/a\");\n        }\n        setDefaults(taskDef);\n        return taskDef;\n    }\n\n    private void setDefaults(TaskDef taskDef) {\n        if (taskDef != null && taskDef.getResponseTimeoutSeconds() == 0) {\n            taskDef.setResponseTimeoutSeconds(\n                    taskDef.getTimeoutSeconds() == 0 ? ONE_HOUR : taskDef.getTimeoutSeconds() - 1);\n        }\n    }\n\n    @Override\n    public List<TaskDef> getAllTaskDefs() {\n        List<TaskDef> allTaskDefs = new LinkedList<>();\n\n        recordRedisDaoRequests(\"getAllTaskDefs\");\n        Map<String, String> taskDefs = jedisProxy.hgetAll(nsKey(ALL_TASK_DEFS));\n        int size = 0;\n        if (taskDefs.size() > 0) {\n            for (String taskDefJsonStr : taskDefs.values()) {\n                if (taskDefJsonStr != null) {\n                    TaskDef taskDef = readValue(taskDefJsonStr, TaskDef.class);\n                    setDefaults(taskDef);\n                    allTaskDefs.add(taskDef);\n                    size += taskDefJsonStr.length();\n                }\n            }\n            recordRedisDaoPayloadSize(\"getAllTaskDefs\", size, \"n/a\", \"n/a\");\n        }\n\n        return allTaskDefs;\n    }\n\n    @Override\n    public void removeTaskDef(String name) {\n        Preconditions.checkNotNull(name, \"TaskDef name cannot be null\");\n        Long result = jedisProxy.hdel(nsKey(ALL_TASK_DEFS), name);\n        if (!result.equals(1L)) {\n            throw new NotFoundException(\"Cannot remove the task - no such task definition\");\n        }\n        recordRedisDaoRequests(\"removeTaskDef\");\n        refreshTaskDefs();\n    }\n\n    @Override\n    public void createWorkflowDef(WorkflowDef def) {\n        if (jedisProxy.hexists(\n                nsKey(WORKFLOW_DEF, def.getName()), String.valueOf(def.getVersion()))) {\n            throw new ConflictException(\"Workflow with %s already exists!\", def.key());\n        }\n        _createOrUpdate(def);\n    }\n\n    @Override\n    public void updateWorkflowDef(WorkflowDef def) {\n        _createOrUpdate(def);\n    }\n\n    @Override\n    /*\n     * @param name Name of the workflow definition\n     * @return     Latest version of workflow definition\n     * @see        WorkflowDef\n     */\n    public Optional<WorkflowDef> getLatestWorkflowDef(String name) {\n        Preconditions.checkNotNull(name, \"WorkflowDef name cannot be null\");\n        WorkflowDef workflowDef = null;\n\n        Optional<Integer> optionalMaxVersion = getWorkflowMaxVersion(name);\n\n        if (optionalMaxVersion.isPresent()) {\n            String latestdata =\n                    jedisProxy.hget(nsKey(WORKFLOW_DEF, name), optionalMaxVersion.get().toString());\n            if (latestdata != null) {\n                workflowDef = readValue(latestdata, WorkflowDef.class);\n            }\n        }\n\n        return Optional.ofNullable(workflowDef);\n    }\n\n    private Optional<Integer> getWorkflowMaxVersion(String workflowName) {\n        return jedisProxy.hkeys(nsKey(WORKFLOW_DEF, workflowName)).stream()\n                .filter(key -> !key.equals(LATEST))\n                .map(Integer::valueOf)\n                .max(Comparator.naturalOrder());\n    }\n\n    public List<WorkflowDef> getAllVersions(String name) {\n        Preconditions.checkNotNull(name, \"WorkflowDef name cannot be null\");\n        List<WorkflowDef> workflows = new LinkedList<>();\n\n        recordRedisDaoRequests(\"getAllWorkflowDefsByName\");\n        Map<String, String> workflowDefs = jedisProxy.hgetAll(nsKey(WORKFLOW_DEF, name));\n        int size = 0;\n        for (String key : workflowDefs.keySet()) {\n            if (key.equals(LATEST)) {\n                continue;\n            }\n            String workflowDef = workflowDefs.get(key);\n            workflows.add(readValue(workflowDef, WorkflowDef.class));\n            size += workflowDef.length();\n        }\n        recordRedisDaoPayloadSize(\"getAllWorkflowDefsByName\", size, \"n/a\", name);\n\n        return workflows;\n    }\n\n    @Override\n    public Optional<WorkflowDef> getWorkflowDef(String name, int version) {\n        Preconditions.checkNotNull(name, \"WorkflowDef name cannot be null\");\n        WorkflowDef def = null;\n\n        recordRedisDaoRequests(\"getWorkflowDef\");\n        String workflowDefJsonString =\n                jedisProxy.hget(nsKey(WORKFLOW_DEF, name), String.valueOf(version));\n        if (workflowDefJsonString != null) {\n            def = readValue(workflowDefJsonString, WorkflowDef.class);\n            recordRedisDaoPayloadSize(\n                    \"getWorkflowDef\", workflowDefJsonString.length(), \"n/a\", name);\n        }\n        return Optional.ofNullable(def);\n    }\n\n    @Override\n    public void removeWorkflowDef(String name, Integer version) {\n        Preconditions.checkArgument(\n                StringUtils.isNotBlank(name), \"WorkflowDef name cannot be null\");\n        Preconditions.checkNotNull(version, \"Input version cannot be null\");\n        Long result = jedisProxy.hdel(nsKey(WORKFLOW_DEF, name), String.valueOf(version));\n        if (!result.equals(1L)) {\n            throw new NotFoundException(\n                    \"Cannot remove the workflow - no such workflow\" + \" definition: %s version: %d\",\n                    name, version);\n        }\n\n        // check if there are any more versions remaining if not delete the\n        // workflow name\n        Optional<Integer> optionMaxVersion = getWorkflowMaxVersion(name);\n\n        // delete workflow name\n        if (optionMaxVersion.isEmpty()) {\n            jedisProxy.srem(nsKey(WORKFLOW_DEF_NAMES), name);\n        }\n\n        recordRedisDaoRequests(\"removeWorkflowDef\");\n    }\n\n    public List<String> findAll() {\n        Set<String> wfNames = jedisProxy.smembers(nsKey(WORKFLOW_DEF_NAMES));\n        return new ArrayList<>(wfNames);\n    }\n\n    @Override\n    public List<WorkflowDef> getAllWorkflowDefs() {\n        List<WorkflowDef> workflows = new LinkedList<>();\n\n        // Get all from WORKFLOW_DEF_NAMES\n        recordRedisDaoRequests(\"getAllWorkflowDefs\");\n        Set<String> wfNames = jedisProxy.smembers(nsKey(WORKFLOW_DEF_NAMES));\n        int size = 0;\n        for (String wfName : wfNames) {\n            Map<String, String> workflowDefs = jedisProxy.hgetAll(nsKey(WORKFLOW_DEF, wfName));\n            for (String key : workflowDefs.keySet()) {\n                if (key.equals(LATEST)) {\n                    continue;\n                }\n                String workflowDef = workflowDefs.get(key);\n                workflows.add(readValue(workflowDef, WorkflowDef.class));\n                size += workflowDef.length();\n            }\n        }\n        recordRedisDaoPayloadSize(\"getAllWorkflowDefs\", size, \"n/a\", \"n/a\");\n        return workflows;\n    }\n\n    @Override\n    public List<WorkflowDef> getAllWorkflowDefsLatestVersions() {\n        List<WorkflowDef> workflows = new LinkedList<>();\n\n        // Get all definitions latest versions from WORKFLOW_DEF_NAMES\n        recordRedisDaoRequests(\"getAllWorkflowLatestVersionsDefs\");\n        Set<String> wfNames = jedisProxy.smembers(nsKey(WORKFLOW_DEF_NAMES));\n        int size = 0;\n        // Place all workflows into the Priority Queue. The PQ will allow us to grab the latest\n        // version of the workflows.\n        for (String wfName : wfNames) {\n            WorkflowDef def = getLatestWorkflowDef(wfName).orElse(null);\n            if (def != null) {\n                workflows.add(def);\n                size += def.toString().length();\n            }\n        }\n        recordRedisDaoPayloadSize(\"getAllWorkflowLatestVersionsDefs\", size, \"n/a\", \"n/a\");\n        return workflows;\n    }\n\n    private void _createOrUpdate(WorkflowDef workflowDef) {\n        // First set the workflow def\n        jedisProxy.hset(\n                nsKey(WORKFLOW_DEF, workflowDef.getName()),\n                String.valueOf(workflowDef.getVersion()),\n                toJson(workflowDef));\n\n        jedisProxy.sadd(nsKey(WORKFLOW_DEF_NAMES), workflowDef.getName());\n        recordRedisDaoRequests(\"storeWorkflowDef\", \"n/a\", workflowDef.getName());\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dao/RedisPollDataDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.dao.PollDataDAO;\nimport com.netflix.conductor.redis.config.AnyRedisCondition;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.base.Preconditions;\n\n@Component\n@Conditional(AnyRedisCondition.class)\npublic class RedisPollDataDAO extends BaseDynoDAO implements PollDataDAO {\n\n    private static final String POLL_DATA = \"POLL_DATA\";\n\n    public RedisPollDataDAO(\n            JedisProxy jedisProxy,\n            ObjectMapper objectMapper,\n            ConductorProperties conductorProperties,\n            RedisProperties properties) {\n        super(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Override\n    public void updateLastPollData(String taskDefName, String domain, String workerId) {\n        Preconditions.checkNotNull(taskDefName, \"taskDefName name cannot be null\");\n        PollData pollData = new PollData(taskDefName, domain, workerId, System.currentTimeMillis());\n\n        String key = nsKey(POLL_DATA, pollData.getQueueName());\n        String field = (domain == null) ? \"DEFAULT\" : domain;\n\n        String payload = toJson(pollData);\n        recordRedisDaoRequests(\"updatePollData\");\n        recordRedisDaoPayloadSize(\"updatePollData\", payload.length(), \"n/a\", \"n/a\");\n        jedisProxy.hset(key, field, payload);\n    }\n\n    @Override\n    public PollData getPollData(String taskDefName, String domain) {\n        Preconditions.checkNotNull(taskDefName, \"taskDefName name cannot be null\");\n\n        String key = nsKey(POLL_DATA, taskDefName);\n        String field = (domain == null) ? \"DEFAULT\" : domain;\n\n        String pollDataJsonString = jedisProxy.hget(key, field);\n        recordRedisDaoRequests(\"getPollData\");\n        recordRedisDaoPayloadSize(\n                \"getPollData\", StringUtils.length(pollDataJsonString), \"n/a\", \"n/a\");\n\n        PollData pollData = null;\n        if (StringUtils.isNotBlank(pollDataJsonString)) {\n            pollData = readValue(pollDataJsonString, PollData.class);\n        }\n        return pollData;\n    }\n\n    @Override\n    public List<PollData> getPollData(String taskDefName) {\n        Preconditions.checkNotNull(taskDefName, \"taskDefName name cannot be null\");\n\n        String key = nsKey(POLL_DATA, taskDefName);\n\n        Map<String, String> pMapdata = jedisProxy.hgetAll(key);\n        List<PollData> pollData = new ArrayList<>();\n        if (pMapdata != null) {\n            pMapdata.values()\n                    .forEach(\n                            pollDataJsonString -> {\n                                pollData.add(readValue(pollDataJsonString, PollData.class));\n                                recordRedisDaoRequests(\"getPollData\");\n                                recordRedisDaoPayloadSize(\n                                        \"getPollData\", pollDataJsonString.length(), \"n/a\", \"n/a\");\n                            });\n        }\n        return pollData;\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dao/RedisRateLimitingDAO.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.Optional;\n\nimport org.apache.commons.lang3.tuple.ImmutablePair;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.dao.RateLimitingDAO;\nimport com.netflix.conductor.metrics.Monitors;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.redis.config.AnyRedisCondition;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\n@Component\n@Conditional(AnyRedisCondition.class)\npublic class RedisRateLimitingDAO extends BaseDynoDAO implements RateLimitingDAO {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RedisRateLimitingDAO.class);\n\n    private static final String TASK_RATE_LIMIT_BUCKET = \"TASK_RATE_LIMIT_BUCKET\";\n\n    public RedisRateLimitingDAO(\n            JedisProxy jedisProxy,\n            ObjectMapper objectMapper,\n            ConductorProperties conductorProperties,\n            RedisProperties properties) {\n        super(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    /**\n     * This method evaluates if the {@link TaskDef} is rate limited or not based on {@link\n     * TaskModel#getRateLimitPerFrequency()} and {@link TaskModel#getRateLimitFrequencyInSeconds()}\n     * if not checks the {@link TaskModel} is rate limited or not based on {@link\n     * TaskModel#getRateLimitPerFrequency()} and {@link TaskModel#getRateLimitFrequencyInSeconds()}\n     *\n     * <p>The rate limiting is implemented using the Redis constructs of sorted set and TTL of each\n     * element in the rate limited bucket.\n     *\n     * <ul>\n     *   <li>All the entries that are in the not in the frequency bucket are cleaned up by\n     *       leveraging {@link JedisProxy#zremrangeByScore(String, String, String)}, this is done to\n     *       make the next step of evaluation efficient\n     *   <li>A current count(tasks executed within the frequency) is calculated based on the current\n     *       time and the beginning of the rate limit frequency time(which is current time - {@link\n     *       TaskModel#getRateLimitFrequencyInSeconds()} in millis), this is achieved by using\n     *       {@link JedisProxy#zcount(String, double, double)}\n     *   <li>Once the count is calculated then a evaluation is made to determine if it is within the\n     *       bounds of {@link TaskModel#getRateLimitPerFrequency()}, if so the count is increased\n     *       and an expiry TTL is added to the entry\n     * </ul>\n     *\n     * @param task: which needs to be evaluated whether it is rateLimited or not\n     * @return true: If the {@link TaskModel} is rateLimited false: If the {@link TaskModel} is not\n     *     rateLimited\n     */\n    @Override\n    public boolean exceedsRateLimitPerFrequency(TaskModel task, TaskDef taskDef) {\n        // Check if the TaskDefinition is not null then pick the definition values or else pick from\n        // the Task\n        ImmutablePair<Integer, Integer> rateLimitPair =\n                Optional.ofNullable(taskDef)\n                        .map(\n                                definition ->\n                                        new ImmutablePair<>(\n                                                definition.getRateLimitPerFrequency(),\n                                                definition.getRateLimitFrequencyInSeconds()))\n                        .orElse(\n                                new ImmutablePair<>(\n                                        task.getRateLimitPerFrequency(),\n                                        task.getRateLimitFrequencyInSeconds()));\n\n        int rateLimitPerFrequency = rateLimitPair.getLeft();\n        int rateLimitFrequencyInSeconds = rateLimitPair.getRight();\n        if (rateLimitPerFrequency <= 0 || rateLimitFrequencyInSeconds <= 0) {\n            LOGGER.debug(\n                    \"Rate limit not applied to the Task: {}  either rateLimitPerFrequency: {} or rateLimitFrequencyInSeconds: {} is 0 or less\",\n                    task,\n                    rateLimitPerFrequency,\n                    rateLimitFrequencyInSeconds);\n            return false;\n        } else {\n            LOGGER.debug(\n                    \"Evaluating rate limiting for TaskId: {} with TaskDefinition of: {} with rateLimitPerFrequency: {} and rateLimitFrequencyInSeconds: {}\",\n                    task.getTaskId(),\n                    task.getTaskDefName(),\n                    rateLimitPerFrequency,\n                    rateLimitFrequencyInSeconds);\n            long currentTimeEpochMillis = System.currentTimeMillis();\n            long currentTimeEpochMinusRateLimitBucket =\n                    currentTimeEpochMillis - (rateLimitFrequencyInSeconds * 1000L);\n            String key = nsKey(TASK_RATE_LIMIT_BUCKET, task.getTaskDefName());\n            jedisProxy.zremrangeByScore(\n                    key, \"-inf\", String.valueOf(currentTimeEpochMinusRateLimitBucket));\n            int currentBucketCount =\n                    Math.toIntExact(\n                            jedisProxy.zcount(\n                                    key,\n                                    currentTimeEpochMinusRateLimitBucket,\n                                    currentTimeEpochMillis));\n            if (currentBucketCount < rateLimitPerFrequency) {\n                jedisProxy.zadd(\n                        key, currentTimeEpochMillis, String.valueOf(currentTimeEpochMillis));\n                jedisProxy.expire(key, rateLimitFrequencyInSeconds);\n                LOGGER.info(\n                        \"TaskId: {} with TaskDefinition of: {} has rateLimitPerFrequency: {} and rateLimitFrequencyInSeconds: {} within the rate limit with current count {}\",\n                        task.getTaskId(),\n                        task.getTaskDefName(),\n                        rateLimitPerFrequency,\n                        rateLimitFrequencyInSeconds,\n                        ++currentBucketCount);\n                Monitors.recordTaskRateLimited(task.getTaskDefName(), rateLimitPerFrequency);\n                return false;\n            } else {\n                LOGGER.info(\n                        \"TaskId: {} with TaskDefinition of: {} has rateLimitPerFrequency: {} and rateLimitFrequencyInSeconds: {} is out of bounds of rate limit with current count {}\",\n                        task.getTaskId(),\n                        task.getTaskDefName(),\n                        rateLimitPerFrequency,\n                        rateLimitFrequencyInSeconds,\n                        currentBucketCount);\n                return true;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dynoqueue/ConfigurationHostSupplier.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dynoqueue;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.dyno.connectionpool.Host;\nimport com.netflix.dyno.connectionpool.HostBuilder;\nimport com.netflix.dyno.connectionpool.HostSupplier;\n\npublic class ConfigurationHostSupplier implements HostSupplier {\n\n    private static final Logger log = LoggerFactory.getLogger(ConfigurationHostSupplier.class);\n\n    private final RedisProperties properties;\n\n    public ConfigurationHostSupplier(RedisProperties properties) {\n        this.properties = properties;\n    }\n\n    @Override\n    public List<Host> getHosts() {\n        return parseHostsFromConfig();\n    }\n\n    private List<Host> parseHostsFromConfig() {\n        String hosts = properties.getHosts();\n        if (hosts == null) {\n            String message =\n                    \"Missing dynomite/redis hosts. Ensure 'conductor.redis.hosts' has been set in the supplied configuration.\";\n            log.error(message);\n            throw new RuntimeException(message);\n        }\n        return parseHostsFrom(hosts);\n    }\n\n    private List<Host> parseHostsFrom(String hostConfig) {\n        List<String> hostConfigs = Arrays.asList(hostConfig.split(\";\"));\n\n        return hostConfigs.stream()\n                .map(\n                        hc -> {\n                            String[] hostConfigValues = hc.split(\":\");\n                            String host = hostConfigValues[0];\n                            int port = Integer.parseInt(hostConfigValues[1]);\n                            String rack = hostConfigValues[2];\n\n                            if (hostConfigValues.length >= 4) {\n                                String password = hostConfigValues[3];\n                                return new HostBuilder()\n                                        .setHostname(host)\n                                        .setPort(port)\n                                        .setRack(rack)\n                                        .setStatus(Host.Status.Up)\n                                        .setPassword(password)\n                                        .createHost();\n                            }\n                            return new HostBuilder()\n                                    .setHostname(host)\n                                    .setPort(port)\n                                    .setRack(rack)\n                                    .setStatus(Host.Status.Up)\n                                    .createHost();\n                        })\n                .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dynoqueue/LocalhostHostSupplier.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dynoqueue;\n\nimport java.util.List;\n\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.dyno.connectionpool.Host;\nimport com.netflix.dyno.connectionpool.HostBuilder;\nimport com.netflix.dyno.connectionpool.HostSupplier;\n\nimport com.google.common.collect.Lists;\n\npublic class LocalhostHostSupplier implements HostSupplier {\n\n    private final RedisProperties properties;\n\n    public LocalhostHostSupplier(RedisProperties properties) {\n        this.properties = properties;\n    }\n\n    @Override\n    public List<Host> getHosts() {\n        Host dynoHost =\n                new HostBuilder()\n                        .setHostname(\"localhost\")\n                        .setIpAddress(\"0\")\n                        .setRack(properties.getAvailabilityZone())\n                        .setStatus(Host.Status.Up)\n                        .createHost();\n        return Lists.newArrayList(dynoHost);\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/dynoqueue/RedisQueuesShardingStrategyProvider.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dynoqueue;\n\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.dyno.queues.Message;\nimport com.netflix.dyno.queues.ShardSupplier;\nimport com.netflix.dyno.queues.redis.sharding.RoundRobinStrategy;\nimport com.netflix.dyno.queues.redis.sharding.ShardingStrategy;\n\npublic class RedisQueuesShardingStrategyProvider {\n\n    public static final String LOCAL_ONLY_STRATEGY = \"localOnly\";\n    public static final String ROUND_ROBIN_STRATEGY = \"roundRobin\";\n\n    private static final Logger LOGGER =\n            LoggerFactory.getLogger(RedisQueuesShardingStrategyProvider.class);\n    private final ShardSupplier shardSupplier;\n    private final RedisProperties properties;\n\n    public RedisQueuesShardingStrategyProvider(\n            ShardSupplier shardSupplier, RedisProperties properties) {\n        this.shardSupplier = shardSupplier;\n        this.properties = properties;\n    }\n\n    public ShardingStrategy get() {\n        String shardingStrat = properties.getQueueShardingStrategy();\n        if (shardingStrat.equals(LOCAL_ONLY_STRATEGY)) {\n            LOGGER.info(\n                    \"Using {} sharding strategy for queues\",\n                    LocalOnlyStrategy.class.getSimpleName());\n            return new LocalOnlyStrategy(shardSupplier);\n        } else {\n            LOGGER.info(\n                    \"Using {} sharding strategy for queues\",\n                    RoundRobinStrategy.class.getSimpleName());\n            return new RoundRobinStrategy();\n        }\n    }\n\n    public static final class LocalOnlyStrategy implements ShardingStrategy {\n\n        private static final Logger LOGGER = LoggerFactory.getLogger(LocalOnlyStrategy.class);\n\n        private final ShardSupplier shardSupplier;\n\n        public LocalOnlyStrategy(ShardSupplier shardSupplier) {\n            this.shardSupplier = shardSupplier;\n        }\n\n        @Override\n        public String getNextShard(List<String> allShards, Message message) {\n            LOGGER.debug(\n                    \"Always using {} shard out of {}\", shardSupplier.getCurrentShard(), allShards);\n            return shardSupplier.getCurrentShard();\n        }\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/jedis/JedisCluster.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.AbstractMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport redis.clients.jedis.BitPosParams;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.GeoRadiusResponse;\nimport redis.clients.jedis.GeoUnit;\nimport redis.clients.jedis.ListPosition;\nimport redis.clients.jedis.ScanParams;\nimport redis.clients.jedis.ScanResult;\nimport redis.clients.jedis.SortingParams;\nimport redis.clients.jedis.StreamConsumersInfo;\nimport redis.clients.jedis.StreamEntry;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.StreamGroupInfo;\nimport redis.clients.jedis.StreamInfo;\nimport redis.clients.jedis.StreamPendingEntry;\nimport redis.clients.jedis.Tuple;\nimport redis.clients.jedis.commands.JedisCommands;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\n\npublic class JedisCluster implements JedisCommands {\n\n    private final redis.clients.jedis.JedisCluster jedisCluster;\n\n    public JedisCluster(redis.clients.jedis.JedisCluster jedisCluster) {\n        this.jedisCluster = jedisCluster;\n    }\n\n    @Override\n    public String set(String key, String value) {\n        return jedisCluster.set(key, value);\n    }\n\n    @Override\n    public String set(String key, String value, SetParams params) {\n        return jedisCluster.set(key, value, params);\n    }\n\n    @Override\n    public String get(String key) {\n        return jedisCluster.get(key);\n    }\n\n    @Override\n    public Boolean exists(String key) {\n        return jedisCluster.exists(key);\n    }\n\n    @Override\n    public Long persist(String key) {\n        return jedisCluster.persist(key);\n    }\n\n    @Override\n    public String type(String key) {\n        return jedisCluster.type(key);\n    }\n\n    @Override\n    public byte[] dump(String key) {\n        return jedisCluster.dump(key);\n    }\n\n    @Override\n    public String restore(String key, int ttl, byte[] serializedValue) {\n        return jedisCluster.restore(key, ttl, serializedValue);\n    }\n\n    @Override\n    public String restoreReplace(String key, int ttl, byte[] serializedValue) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Long expire(String key, int seconds) {\n        return jedisCluster.expire(key, seconds);\n    }\n\n    @Override\n    public Long pexpire(String key, long milliseconds) {\n        return jedisCluster.pexpire(key, milliseconds);\n    }\n\n    @Override\n    public Long expireAt(String key, long unixTime) {\n        return jedisCluster.expireAt(key, unixTime);\n    }\n\n    @Override\n    public Long pexpireAt(String key, long millisecondsTimestamp) {\n        return jedisCluster.pexpireAt(key, millisecondsTimestamp);\n    }\n\n    @Override\n    public Long ttl(String key) {\n        return jedisCluster.ttl(key);\n    }\n\n    @Override\n    public Long pttl(String key) {\n        return jedisCluster.pttl(key);\n    }\n\n    @Override\n    public Long touch(String key) {\n        return jedisCluster.touch(key);\n    }\n\n    @Override\n    public Boolean setbit(String key, long offset, boolean value) {\n        return jedisCluster.setbit(key, offset, value);\n    }\n\n    @Override\n    public Boolean setbit(String key, long offset, String value) {\n        return jedisCluster.setbit(key, offset, value);\n    }\n\n    @Override\n    public Boolean getbit(String key, long offset) {\n        return jedisCluster.getbit(key, offset);\n    }\n\n    @Override\n    public Long setrange(String key, long offset, String value) {\n        return jedisCluster.setrange(key, offset, value);\n    }\n\n    @Override\n    public String getrange(String key, long startOffset, long endOffset) {\n        return jedisCluster.getrange(key, startOffset, endOffset);\n    }\n\n    @Override\n    public String getSet(String key, String value) {\n        return jedisCluster.getSet(key, value);\n    }\n\n    @Override\n    public Long setnx(String key, String value) {\n        return jedisCluster.setnx(key, value);\n    }\n\n    @Override\n    public String setex(String key, int seconds, String value) {\n        return jedisCluster.setex(key, seconds, value);\n    }\n\n    @Override\n    public String psetex(String key, long milliseconds, String value) {\n        return jedisCluster.psetex(key, milliseconds, value);\n    }\n\n    @Override\n    public Long decrBy(String key, long integer) {\n        return jedisCluster.decrBy(key, integer);\n    }\n\n    @Override\n    public Long decr(String key) {\n        return jedisCluster.decr(key);\n    }\n\n    @Override\n    public Long incrBy(String key, long integer) {\n        return jedisCluster.incrBy(key, integer);\n    }\n\n    @Override\n    public Double incrByFloat(String key, double value) {\n        return jedisCluster.incrByFloat(key, value);\n    }\n\n    @Override\n    public Long incr(String key) {\n        return jedisCluster.incr(key);\n    }\n\n    @Override\n    public Long append(String key, String value) {\n        return jedisCluster.append(key, value);\n    }\n\n    @Override\n    public String substr(String key, int start, int end) {\n        return jedisCluster.substr(key, start, end);\n    }\n\n    @Override\n    public Long hset(String key, String field, String value) {\n        return jedisCluster.hset(key, field, value);\n    }\n\n    @Override\n    public Long hset(String key, Map<String, String> hash) {\n        return jedisCluster.hset(key, hash);\n    }\n\n    @Override\n    public String hget(String key, String field) {\n        return jedisCluster.hget(key, field);\n    }\n\n    @Override\n    public Long hsetnx(String key, String field, String value) {\n        return jedisCluster.hsetnx(key, field, value);\n    }\n\n    @Override\n    public String hmset(String key, Map<String, String> hash) {\n        return jedisCluster.hmset(key, hash);\n    }\n\n    @Override\n    public List<String> hmget(String key, String... fields) {\n        return jedisCluster.hmget(key, fields);\n    }\n\n    @Override\n    public Long hincrBy(String key, String field, long value) {\n        return jedisCluster.hincrBy(key, field, value);\n    }\n\n    @Override\n    public Double hincrByFloat(String key, String field, double value) {\n        return jedisCluster.hincrByFloat(key.getBytes(), field.getBytes(), value);\n    }\n\n    @Override\n    public Boolean hexists(String key, String field) {\n        return jedisCluster.hexists(key, field);\n    }\n\n    @Override\n    public Long hdel(String key, String... field) {\n        return jedisCluster.hdel(key, field);\n    }\n\n    @Override\n    public Long hlen(String key) {\n        return jedisCluster.hlen(key);\n    }\n\n    @Override\n    public Set<String> hkeys(String key) {\n        return jedisCluster.hkeys(key);\n    }\n\n    @Override\n    public List<String> hvals(String key) {\n        return jedisCluster.hvals(key);\n    }\n\n    @Override\n    public Map<String, String> hgetAll(String key) {\n        return jedisCluster.hgetAll(key);\n    }\n\n    @Override\n    public Long rpush(String key, String... string) {\n        return jedisCluster.rpush(key, string);\n    }\n\n    @Override\n    public Long lpush(String key, String... string) {\n        return jedisCluster.lpush(key, string);\n    }\n\n    @Override\n    public Long llen(String key) {\n        return jedisCluster.llen(key);\n    }\n\n    @Override\n    public List<String> lrange(String key, long start, long end) {\n        return jedisCluster.lrange(key, start, end);\n    }\n\n    @Override\n    public String ltrim(String key, long start, long end) {\n        return jedisCluster.ltrim(key, start, end);\n    }\n\n    @Override\n    public String lindex(String key, long index) {\n        return jedisCluster.lindex(key, index);\n    }\n\n    @Override\n    public String lset(String key, long index, String value) {\n        return jedisCluster.lset(key, index, value);\n    }\n\n    @Override\n    public Long lrem(String key, long count, String value) {\n        return jedisCluster.lrem(key, count, value);\n    }\n\n    @Override\n    public String lpop(String key) {\n        return jedisCluster.lpop(key);\n    }\n\n    @Override\n    public String rpop(String key) {\n        return jedisCluster.rpop(key);\n    }\n\n    @Override\n    public Long sadd(String key, String... member) {\n        return jedisCluster.sadd(key, member);\n    }\n\n    @Override\n    public Set<String> smembers(String key) {\n        return jedisCluster.smembers(key);\n    }\n\n    @Override\n    public Long srem(String key, String... member) {\n        return jedisCluster.srem(key, member);\n    }\n\n    @Override\n    public String spop(String key) {\n        return jedisCluster.spop(key);\n    }\n\n    @Override\n    public Set<String> spop(String key, long count) {\n        return jedisCluster.spop(key, count);\n    }\n\n    @Override\n    public Long scard(String key) {\n        return jedisCluster.scard(key);\n    }\n\n    @Override\n    public Boolean sismember(String key, String member) {\n        return jedisCluster.sismember(key, member);\n    }\n\n    @Override\n    public String srandmember(String key) {\n        return jedisCluster.srandmember(key);\n    }\n\n    @Override\n    public List<String> srandmember(String key, int count) {\n        return jedisCluster.srandmember(key, count);\n    }\n\n    @Override\n    public Long strlen(String key) {\n        return jedisCluster.strlen(key);\n    }\n\n    @Override\n    public Long zadd(String key, double score, String member) {\n        return jedisCluster.zadd(key, score, member);\n    }\n\n    @Override\n    public Long zadd(String key, double score, String member, ZAddParams params) {\n        return jedisCluster.zadd(key, score, member, params);\n    }\n\n    @Override\n    public Long zadd(String key, Map<String, Double> scoreMembers) {\n        return jedisCluster.zadd(key, scoreMembers);\n    }\n\n    @Override\n    public Long zadd(String key, Map<String, Double> scoreMembers, ZAddParams params) {\n        return jedisCluster.zadd(key, scoreMembers, params);\n    }\n\n    @Override\n    public Set<String> zrange(String key, long start, long end) {\n        return jedisCluster.zrange(key, start, end);\n    }\n\n    @Override\n    public Long zrem(String key, String... member) {\n        return jedisCluster.zrem(key, member);\n    }\n\n    @Override\n    public Double zincrby(String key, double score, String member) {\n        return jedisCluster.zincrby(key, score, member);\n    }\n\n    @Override\n    public Double zincrby(String key, double score, String member, ZIncrByParams params) {\n        return jedisCluster.zincrby(key, score, member, params);\n    }\n\n    @Override\n    public Long zrank(String key, String member) {\n        return jedisCluster.zrank(key, member);\n    }\n\n    @Override\n    public Long zrevrank(String key, String member) {\n        return jedisCluster.zrevrank(key, member);\n    }\n\n    @Override\n    public Set<String> zrevrange(String key, long start, long end) {\n        return jedisCluster.zrevrange(key, start, end);\n    }\n\n    @Override\n    public Set<Tuple> zrangeWithScores(String key, long start, long end) {\n        return jedisCluster.zrangeWithScores(key, start, end);\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeWithScores(String key, long start, long end) {\n        return jedisCluster.zrevrangeWithScores(key, start, end);\n    }\n\n    @Override\n    public Long zcard(String key) {\n        return jedisCluster.zcard(key);\n    }\n\n    @Override\n    public Double zscore(String key, String member) {\n        return jedisCluster.zscore(key, member);\n    }\n\n    @Override\n    public Tuple zpopmax(String key) {\n        return jedisCluster.zpopmax(key);\n    }\n\n    @Override\n    public Set<Tuple> zpopmax(String key, int count) {\n        return jedisCluster.zpopmax(key, count);\n    }\n\n    @Override\n    public Tuple zpopmin(String key) {\n        return jedisCluster.zpopmin(key);\n    }\n\n    @Override\n    public Set<Tuple> zpopmin(String key, int count) {\n        return jedisCluster.zpopmin(key, count);\n    }\n\n    @Override\n    public List<String> sort(String key) {\n        return jedisCluster.sort(key);\n    }\n\n    @Override\n    public List<String> sort(String key, SortingParams sortingParameters) {\n        return jedisCluster.sort(key, sortingParameters);\n    }\n\n    @Override\n    public Long zcount(String key, double min, double max) {\n        return jedisCluster.zcount(key, min, max);\n    }\n\n    @Override\n    public Long zcount(String key, String min, String max) {\n        return jedisCluster.zcount(key, min, max);\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, double min, double max) {\n        return jedisCluster.zrangeByScore(key, min, max);\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, String min, String max) {\n        return jedisCluster.zrangeByScore(key, min, max);\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, double max, double min) {\n        return jedisCluster.zrevrangeByScore(key, max, min);\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, double min, double max, int offset, int count) {\n        return jedisCluster.zrangeByScore(key, min, max, offset, count);\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, String max, String min) {\n        return jedisCluster.zrevrangeByScore(key, max, min);\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, String min, String max, int offset, int count) {\n        return jedisCluster.zrangeByScore(key, min, max, offset, count);\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, double max, double min, int offset, int count) {\n        return jedisCluster.zrevrangeByScore(key, max, min, offset, count);\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(String key, double min, double max) {\n        return jedisCluster.zrangeByScoreWithScores(key, min, max);\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(String key, double max, double min) {\n        return jedisCluster.zrevrangeByScoreWithScores(key, max, min);\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            String key, double min, double max, int offset, int count) {\n        return jedisCluster.zrangeByScoreWithScores(key, min, max, offset, count);\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, String max, String min, int offset, int count) {\n        return jedisCluster.zrevrangeByScore(key, max, min, offset, count);\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(String key, String min, String max) {\n        return jedisCluster.zrangeByScoreWithScores(key, min, max);\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(String key, String max, String min) {\n        return jedisCluster.zrevrangeByScoreWithScores(key, max, min);\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            String key, String min, String max, int offset, int count) {\n        return jedisCluster.zrangeByScoreWithScores(key, min, max, offset, count);\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            String key, double max, double min, int offset, int count) {\n        return jedisCluster.zrevrangeByScoreWithScores(key, max, min, offset, count);\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            String key, String max, String min, int offset, int count) {\n        return jedisCluster.zrevrangeByScoreWithScores(key, max, min, offset, count);\n    }\n\n    @Override\n    public Long zremrangeByRank(String key, long start, long end) {\n        return jedisCluster.zremrangeByRank(key, start, end);\n    }\n\n    @Override\n    public Long zremrangeByScore(String key, double start, double end) {\n        return jedisCluster.zremrangeByScore(key, start, end);\n    }\n\n    @Override\n    public Long zremrangeByScore(String key, String start, String end) {\n        return jedisCluster.zremrangeByScore(key, start, end);\n    }\n\n    @Override\n    public Long zlexcount(String key, String min, String max) {\n        return jedisCluster.zlexcount(key, min, max);\n    }\n\n    @Override\n    public Set<String> zrangeByLex(String key, String min, String max) {\n        return jedisCluster.zrangeByLex(key, min, max);\n    }\n\n    @Override\n    public Set<String> zrangeByLex(String key, String min, String max, int offset, int count) {\n        return jedisCluster.zrangeByLex(key, min, max, offset, count);\n    }\n\n    @Override\n    public Set<String> zrevrangeByLex(String key, String max, String min) {\n        return jedisCluster.zrevrangeByLex(key, max, min);\n    }\n\n    @Override\n    public Set<String> zrevrangeByLex(String key, String max, String min, int offset, int count) {\n        return jedisCluster.zrevrangeByLex(key, max, min, offset, count);\n    }\n\n    @Override\n    public Long zremrangeByLex(String key, String min, String max) {\n        return jedisCluster.zremrangeByLex(key, min, max);\n    }\n\n    @Override\n    public Long linsert(String key, ListPosition where, String pivot, String value) {\n        return jedisCluster.linsert(key, where, pivot, value);\n    }\n\n    @Override\n    public Long lpushx(String key, String... string) {\n        return jedisCluster.lpushx(key, string);\n    }\n\n    @Override\n    public Long rpushx(String key, String... string) {\n        return jedisCluster.rpushx(key, string);\n    }\n\n    @Override\n    public List<String> blpop(int timeout, String key) {\n        return jedisCluster.blpop(timeout, key);\n    }\n\n    @Override\n    public List<String> brpop(int timeout, String key) {\n        return jedisCluster.brpop(timeout, key);\n    }\n\n    @Override\n    public Long del(String key) {\n        return jedisCluster.del(key);\n    }\n\n    @Override\n    public Long unlink(String key) {\n        return jedisCluster.unlink(key);\n    }\n\n    @Override\n    public String echo(String string) {\n        return jedisCluster.echo(string);\n    }\n\n    @Override\n    public Long move(String key, int dbIndex) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Long bitcount(String key) {\n        return jedisCluster.bitcount(key);\n    }\n\n    @Override\n    public Long bitcount(String key, long start, long end) {\n        return jedisCluster.bitcount(key, start, end);\n    }\n\n    @Override\n    public Long bitpos(String key, boolean value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Long bitpos(String key, boolean value, BitPosParams params) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public ScanResult<Entry<String, String>> hscan(String key, String cursor) {\n        return jedisCluster.hscan(key, cursor);\n    }\n\n    @Override\n    public ScanResult<Map.Entry<String, String>> hscan(\n            String key, String cursor, ScanParams params) {\n        ScanResult<Map.Entry<byte[], byte[]>> scanResult =\n                jedisCluster.hscan(key.getBytes(), cursor.getBytes(), params);\n        List<Map.Entry<String, String>> results =\n                scanResult.getResult().stream()\n                        .map(\n                                entry ->\n                                        new AbstractMap.SimpleEntry<>(\n                                                new String(entry.getKey()),\n                                                new String(entry.getValue())))\n                        .collect(Collectors.toList());\n        return new ScanResult<>(scanResult.getCursorAsBytes(), results);\n    }\n\n    @Override\n    public ScanResult<String> sscan(String key, String cursor) {\n        return jedisCluster.sscan(key, cursor);\n    }\n\n    @Override\n    public ScanResult<String> sscan(String key, String cursor, ScanParams params) {\n        ScanResult<byte[]> scanResult =\n                jedisCluster.sscan(key.getBytes(), cursor.getBytes(), params);\n        List<String> results =\n                scanResult.getResult().stream().map(String::new).collect(Collectors.toList());\n        return new ScanResult<>(scanResult.getCursorAsBytes(), results);\n    }\n\n    @Override\n    public ScanResult<Tuple> zscan(String key, String cursor) {\n        return jedisCluster.zscan(key, cursor);\n    }\n\n    @Override\n    public ScanResult<Tuple> zscan(String key, String cursor, ScanParams params) {\n        return jedisCluster.zscan(key.getBytes(), cursor.getBytes(), params);\n    }\n\n    @Override\n    public Long pfadd(String key, String... elements) {\n        return jedisCluster.pfadd(key, elements);\n    }\n\n    @Override\n    public long pfcount(String key) {\n        return jedisCluster.pfcount(key);\n    }\n\n    @Override\n    public Long geoadd(String key, double longitude, double latitude, String member) {\n        return jedisCluster.geoadd(key, longitude, latitude, member);\n    }\n\n    @Override\n    public Long geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap) {\n        return jedisCluster.geoadd(key, memberCoordinateMap);\n    }\n\n    @Override\n    public Double geodist(String key, String member1, String member2) {\n        return jedisCluster.geodist(key, member1, member2);\n    }\n\n    @Override\n    public Double geodist(String key, String member1, String member2, GeoUnit unit) {\n        return jedisCluster.geodist(key, member1, member2, unit);\n    }\n\n    @Override\n    public List<String> geohash(String key, String... members) {\n        return jedisCluster.geohash(key, members);\n    }\n\n    @Override\n    public List<GeoCoordinate> geopos(String key, String... members) {\n        return jedisCluster.geopos(key, members);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadius(\n            String key, double longitude, double latitude, double radius, GeoUnit unit) {\n        return jedisCluster.georadius(key, longitude, latitude, radius, unit);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusReadonly(\n            String key, double longitude, double latitude, double radius, GeoUnit unit) {\n        return jedisCluster.georadiusReadonly(key, longitude, latitude, radius, unit);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadius(\n            String key,\n            double longitude,\n            double latitude,\n            double radius,\n            GeoUnit unit,\n            GeoRadiusParam param) {\n        return jedisCluster.georadius(key, longitude, latitude, radius, unit, param);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusReadonly(\n            String key,\n            double longitude,\n            double latitude,\n            double radius,\n            GeoUnit unit,\n            GeoRadiusParam param) {\n        return jedisCluster.georadiusReadonly(key, longitude, latitude, radius, unit, param);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMember(\n            String key, String member, double radius, GeoUnit unit) {\n        return jedisCluster.georadiusByMember(key, member, radius, unit);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMemberReadonly(\n            String key, String member, double radius, GeoUnit unit) {\n        return jedisCluster.georadiusByMemberReadonly(key, member, radius, unit);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMember(\n            String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n        return jedisCluster.georadiusByMember(key, member, radius, unit, param);\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMemberReadonly(\n            String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n        return jedisCluster.georadiusByMemberReadonly(key, member, radius, unit, param);\n    }\n\n    @Override\n    public List<Long> bitfield(String key, String... arguments) {\n        return jedisCluster.bitfield(key, arguments);\n    }\n\n    @Override\n    public List<Long> bitfieldReadonly(String key, String... arguments) {\n        return jedisCluster.bitfieldReadonly(key, arguments);\n    }\n\n    @Override\n    public Long hstrlen(String key, String field) {\n        return jedisCluster.hstrlen(key, field);\n    }\n\n    @Override\n    public StreamEntryID xadd(String key, StreamEntryID id, Map<String, String> hash) {\n        return jedisCluster.xadd(key, id, hash);\n    }\n\n    @Override\n    public StreamEntryID xadd(\n            String key,\n            StreamEntryID id,\n            Map<String, String> hash,\n            long maxLen,\n            boolean approximateLength) {\n        return jedisCluster.xadd(key, id, hash, maxLen, approximateLength);\n    }\n\n    @Override\n    public Long xlen(String key) {\n        return jedisCluster.xlen(key);\n    }\n\n    @Override\n    public List<StreamEntry> xrange(String key, StreamEntryID start, StreamEntryID end, int count) {\n        return jedisCluster.xrange(key, start, end, count);\n    }\n\n    @Override\n    public List<StreamEntry> xrevrange(\n            String key, StreamEntryID end, StreamEntryID start, int count) {\n        return jedisCluster.xrevrange(key, end, start, count);\n    }\n\n    @Override\n    public long xack(String key, String group, StreamEntryID... ids) {\n        return jedisCluster.xack(key, group, ids);\n    }\n\n    @Override\n    public String xgroupCreate(String key, String groupname, StreamEntryID id, boolean makeStream) {\n        return jedisCluster.xgroupCreate(key, groupname, id, makeStream);\n    }\n\n    @Override\n    public String xgroupSetID(String key, String groupname, StreamEntryID id) {\n        return jedisCluster.xgroupSetID(key, groupname, id);\n    }\n\n    @Override\n    public long xgroupDestroy(String key, String groupname) {\n        return jedisCluster.xgroupDestroy(key, groupname);\n    }\n\n    @Override\n    public Long xgroupDelConsumer(String key, String groupname, String consumername) {\n        return jedisCluster.xgroupDelConsumer(key, groupname, consumername);\n    }\n\n    @Override\n    public List<StreamPendingEntry> xpending(\n            String key,\n            String groupname,\n            StreamEntryID start,\n            StreamEntryID end,\n            int count,\n            String consumername) {\n        return jedisCluster.xpending(key, groupname, start, end, count, consumername);\n    }\n\n    @Override\n    public long xdel(String key, StreamEntryID... ids) {\n        return jedisCluster.xdel(key, ids);\n    }\n\n    @Override\n    public long xtrim(String key, long maxLen, boolean approximate) {\n        return jedisCluster.xtrim(key, maxLen, approximate);\n    }\n\n    @Override\n    public List<StreamEntry> xclaim(\n            String key,\n            String group,\n            String consumername,\n            long minIdleTime,\n            long newIdleTime,\n            int retries,\n            boolean force,\n            StreamEntryID... ids) {\n        return jedisCluster.xclaim(\n                key, group, consumername, minIdleTime, newIdleTime, retries, force, ids);\n    }\n\n    @Override\n    public StreamInfo xinfoStream(String key) {\n        return null;\n    }\n\n    @Override\n    public List<StreamGroupInfo> xinfoGroup(String key) {\n        return null;\n    }\n\n    @Override\n    public List<StreamConsumersInfo> xinfoConsumers(String key, String group) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/jedis/JedisMock.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\n\nimport org.rarefiedredis.redis.IRedisClient;\nimport org.rarefiedredis.redis.IRedisSortedSet.ZsetPair;\nimport org.rarefiedredis.redis.RedisMock;\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.ScanParams;\nimport redis.clients.jedis.ScanResult;\nimport redis.clients.jedis.Tuple;\nimport redis.clients.jedis.exceptions.JedisException;\nimport redis.clients.jedis.params.ZAddParams;\n\npublic class JedisMock extends Jedis {\n\n    private final IRedisClient redis;\n\n    public JedisMock() {\n        super(\"\");\n        this.redis = new RedisMock();\n    }\n\n    private Set<Tuple> toTupleSet(Set<ZsetPair> pairs) {\n        Set<Tuple> set = new HashSet<>();\n        for (ZsetPair pair : pairs) {\n            set.add(new Tuple(pair.member, pair.score));\n        }\n        return set;\n    }\n\n    @Override\n    public String set(final String key, String value) {\n        try {\n            return redis.set(key, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String get(final String key) {\n        try {\n            return redis.get(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Boolean exists(final String key) {\n        try {\n            return redis.exists(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long del(final String... keys) {\n        try {\n            return redis.del(keys);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long del(String key) {\n        try {\n            return redis.del(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String type(final String key) {\n        try {\n            return redis.type(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long expire(final String key, final int seconds) {\n        try {\n            return redis.expire(key, seconds) ? 1L : 0L;\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long expireAt(final String key, final long unixTime) {\n        try {\n            return redis.expireat(key, unixTime) ? 1L : 0L;\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long ttl(final String key) {\n        try {\n            return redis.ttl(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long move(final String key, final int dbIndex) {\n        try {\n            return redis.move(key, dbIndex);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String getSet(final String key, final String value) {\n        try {\n            return redis.getset(key, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public List<String> mget(final String... keys) {\n        try {\n            String[] mget = redis.mget(keys);\n            List<String> lst = new ArrayList<>(mget.length);\n            for (String get : mget) {\n                lst.add(get);\n            }\n            return lst;\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long setnx(final String key, final String value) {\n        try {\n            return redis.setnx(key, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String setex(final String key, final int seconds, final String value) {\n        try {\n            return redis.setex(key, seconds, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String mset(final String... keysvalues) {\n        try {\n            return redis.mset(keysvalues);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long msetnx(final String... keysvalues) {\n        try {\n            return redis.msetnx(keysvalues) ? 1L : 0L;\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long decrBy(final String key, final long integer) {\n        try {\n            return redis.decrby(key, integer);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long decr(final String key) {\n        try {\n            return redis.decr(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long incrBy(final String key, final long integer) {\n        try {\n            return redis.incrby(key, integer);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Double incrByFloat(final String key, final double value) {\n        try {\n            return Double.parseDouble(redis.incrbyfloat(key, value));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long incr(final String key) {\n        try {\n            return redis.incr(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long append(final String key, final String value) {\n        try {\n            return redis.append(key, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String substr(final String key, final int start, final int end) {\n        try {\n            return redis.getrange(key, start, end);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long hset(final String key, final String field, final String value) {\n        try {\n            return redis.hset(key, field, value) ? 1L : 0L;\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String hget(final String key, final String field) {\n        try {\n            return redis.hget(key, field);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long hsetnx(final String key, final String field, final String value) {\n        try {\n            return redis.hsetnx(key, field, value) ? 1L : 0L;\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String hmset(final String key, final Map<String, String> hash) {\n        try {\n            String field = null, value = null;\n            String[] args = new String[(hash.size() - 1) * 2];\n            int idx = 0;\n            for (String f : hash.keySet()) {\n                if (field == null) {\n                    field = f;\n                    value = hash.get(f);\n                    continue;\n                }\n                args[idx] = f;\n                args[idx + 1] = hash.get(f);\n                idx += 2;\n            }\n            return redis.hmset(key, field, value, args);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public List<String> hmget(final String key, final String... fields) {\n        try {\n            String field = fields[0];\n            String[] f = new String[fields.length - 1];\n            for (int idx = 1; idx < fields.length; ++idx) {\n                f[idx - 1] = fields[idx];\n            }\n            return redis.hmget(key, field, f);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long hincrBy(final String key, final String field, final long value) {\n        try {\n            return redis.hincrby(key, field, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Double hincrByFloat(final String key, final String field, final double value) {\n        try {\n            return Double.parseDouble(redis.hincrbyfloat(key, field, value));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Boolean hexists(final String key, final String field) {\n        try {\n            return redis.hexists(key, field);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long hdel(final String key, final String... fields) {\n        try {\n            String field = fields[0];\n            String[] f = new String[fields.length - 1];\n            for (int idx = 1; idx < fields.length; ++idx) {\n                f[idx - 1] = fields[idx];\n            }\n            return redis.hdel(key, field, f);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long hlen(final String key) {\n        try {\n            return redis.hlen(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> hkeys(final String key) {\n        try {\n            return redis.hkeys(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public List<String> hvals(final String key) {\n        try {\n            return redis.hvals(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Map<String, String> hgetAll(final String key) {\n        try {\n            return redis.hgetall(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long rpush(final String key, final String... strings) {\n        try {\n            String element = strings[0];\n            String[] elements = new String[strings.length - 1];\n            for (int idx = 1; idx < strings.length; ++idx) {\n                elements[idx - 1] = strings[idx];\n            }\n            return redis.rpush(key, element, elements);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long lpush(final String key, final String... strings) {\n        try {\n            String element = strings[0];\n            String[] elements = new String[strings.length - 1];\n            for (int idx = 1; idx < strings.length; ++idx) {\n                elements[idx - 1] = strings[idx];\n            }\n            return redis.lpush(key, element, elements);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long llen(final String key) {\n        try {\n            return redis.llen(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public List<String> lrange(final String key, final long start, final long end) {\n        try {\n            return redis.lrange(key, start, end);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String ltrim(final String key, final long start, final long end) {\n        try {\n            return redis.ltrim(key, start, end);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String lindex(final String key, final long index) {\n        try {\n            return redis.lindex(key, index);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String lset(final String key, final long index, final String value) {\n        try {\n            return redis.lset(key, index, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long lrem(final String key, final long count, final String value) {\n        try {\n            return redis.lrem(key, count, value);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String lpop(final String key) {\n        try {\n            return redis.lpop(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String rpop(final String key) {\n        try {\n            return redis.rpop(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String rpoplpush(final String srckey, final String dstkey) {\n        try {\n            return redis.rpoplpush(srckey, dstkey);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long sadd(final String key, final String... members) {\n        try {\n            String member = members[0];\n            String[] m = new String[members.length - 1];\n            for (int idx = 1; idx < members.length; ++idx) {\n                m[idx - 1] = members[idx];\n            }\n            return redis.sadd(key, member, m);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> smembers(final String key) {\n        try {\n            return redis.smembers(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long srem(final String key, final String... members) {\n        try {\n            String member = members[0];\n            String[] m = new String[members.length - 1];\n            for (int idx = 1; idx < members.length; ++idx) {\n                m[idx - 1] = members[idx];\n            }\n            return redis.srem(key, member, m);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String spop(final String key) {\n        try {\n            return redis.spop(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long smove(final String srckey, final String dstkey, final String member) {\n        try {\n            return redis.smove(srckey, dstkey, member) ? 1L : 0L;\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long scard(final String key) {\n        try {\n            return redis.scard(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Boolean sismember(final String key, final String member) {\n        try {\n            return redis.sismember(key, member);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> sinter(final String... keys) {\n        try {\n            String key = keys[0];\n            String[] k = new String[keys.length - 1];\n            for (int idx = 0; idx < keys.length; ++idx) {\n                k[idx - 1] = keys[idx];\n            }\n            return redis.sinter(key, k);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long sinterstore(final String dstkey, final String... keys) {\n        try {\n            String key = keys[0];\n            String[] k = new String[keys.length - 1];\n            for (int idx = 0; idx < keys.length; ++idx) {\n                k[idx - 1] = keys[idx];\n            }\n            return redis.sinterstore(dstkey, key, k);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> sunion(final String... keys) {\n        try {\n            String key = keys[0];\n            String[] k = new String[keys.length - 1];\n            for (int idx = 0; idx < keys.length; ++idx) {\n                k[idx - 1] = keys[idx];\n            }\n            return redis.sunion(key, k);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long sunionstore(final String dstkey, final String... keys) {\n        try {\n            String key = keys[0];\n            String[] k = new String[keys.length - 1];\n            for (int idx = 0; idx < keys.length; ++idx) {\n                k[idx - 1] = keys[idx];\n            }\n            return redis.sunionstore(dstkey, key, k);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> sdiff(final String... keys) {\n        try {\n            String key = keys[0];\n            String[] k = new String[keys.length - 1];\n            for (int idx = 0; idx < keys.length; ++idx) {\n                k[idx - 1] = keys[idx];\n            }\n            return redis.sdiff(key, k);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long sdiffstore(final String dstkey, final String... keys) {\n        try {\n            String key = keys[0];\n            String[] k = new String[keys.length - 1];\n            for (int idx = 0; idx < keys.length; ++idx) {\n                k[idx - 1] = keys[idx];\n            }\n            return redis.sdiffstore(dstkey, key, k);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String srandmember(final String key) {\n        try {\n            return redis.srandmember(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public List<String> srandmember(final String key, final int count) {\n        try {\n            return redis.srandmember(key, count);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zadd(final String key, final double score, final String member) {\n        try {\n            return redis.zadd(key, new ZsetPair(member, score));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zadd(String key, double score, String member, ZAddParams params) {\n\n        try {\n\n            if (params.getParam(\"xx\") != null) {\n                Double existing = redis.zscore(key, member);\n                if (existing == null) {\n                    return 0L;\n                }\n                return redis.zadd(key, new ZsetPair(member, score));\n            } else {\n                return redis.zadd(key, new ZsetPair(member, score));\n            }\n\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zadd(final String key, final Map<String, Double> scoreMembers) {\n        try {\n            Double score = null;\n            String member = null;\n            List<ZsetPair> scoresmembers = new ArrayList<>((scoreMembers.size() - 1) * 2);\n            for (String m : scoreMembers.keySet()) {\n                if (m == null) {\n                    member = m;\n                    score = scoreMembers.get(m);\n                    continue;\n                }\n                scoresmembers.add(new ZsetPair(m, scoreMembers.get(m)));\n            }\n            return redis.zadd(\n                    key, new ZsetPair(member, score), (ZsetPair[]) scoresmembers.toArray());\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrange(final String key, final long start, final long end) {\n        try {\n            return ZsetPair.members(redis.zrange(key, start, end));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zrem(final String key, final String... members) {\n        try {\n            String member = members[0];\n            String[] ms = new String[members.length - 1];\n            for (int idx = 1; idx < members.length; ++idx) {\n                ms[idx - 1] = members[idx];\n            }\n            return redis.zrem(key, member, ms);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Double zincrby(final String key, final double score, final String member) {\n        try {\n            return Double.parseDouble(redis.zincrby(key, score, member));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zrank(final String key, final String member) {\n        try {\n            return redis.zrank(key, member);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zrevrank(final String key, final String member) {\n        try {\n            return redis.zrevrank(key, member);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrange(final String key, final long start, final long end) {\n        try {\n            return ZsetPair.members(redis.zrevrange(key, start, end));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeWithScores(final String key, final long start, final long end) {\n        try {\n            return toTupleSet(redis.zrange(key, start, end, \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeWithScores(final String key, final long start, final long end) {\n        try {\n            return toTupleSet(redis.zrevrange(key, start, end, \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zcard(final String key) {\n        try {\n            return redis.zcard(key);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Double zscore(final String key, final String member) {\n        try {\n            return redis.zscore(key, member);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public String watch(final String... keys) {\n        try {\n            for (String key : keys) {\n                redis.watch(key);\n            }\n            return \"OK\";\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zcount(final String key, final double min, final double max) {\n        try {\n            return redis.zcount(key, min, max);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zcount(final String key, final String min, final String max) {\n        try {\n            return redis.zcount(key, Double.parseDouble(min), Double.parseDouble(max));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(final String key, final double min, final double max) {\n        try {\n            return ZsetPair.members(\n                    redis.zrangebyscore(key, String.valueOf(min), String.valueOf(max)));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(final String key, final String min, final String max) {\n        try {\n            return ZsetPair.members(redis.zrangebyscore(key, min, max));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(\n            final String key,\n            final double min,\n            final double max,\n            final int offset,\n            final int count) {\n        try {\n            return ZsetPair.members(\n                    redis.zrangebyscore(\n                            key,\n                            String.valueOf(min),\n                            String.valueOf(max),\n                            \"limit\",\n                            String.valueOf(offset),\n                            String.valueOf(count)));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(\n            final String key,\n            final String min,\n            final String max,\n            final int offset,\n            final int count) {\n        try {\n            return ZsetPair.members(\n                    redis.zrangebyscore(\n                            key, min, max, \"limit\", String.valueOf(offset), String.valueOf(count)));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            final String key, final double min, final double max) {\n        try {\n            return toTupleSet(\n                    redis.zrangebyscore(\n                            key, String.valueOf(min), String.valueOf(max), \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            final String key, final String min, final String max) {\n        try {\n            return toTupleSet(redis.zrangebyscore(key, min, max, \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            final String key,\n            final double min,\n            final double max,\n            final int offset,\n            final int count) {\n        try {\n            return toTupleSet(\n                    redis.zrangebyscore(\n                            key,\n                            String.valueOf(min),\n                            String.valueOf(max),\n                            \"limit\",\n                            String.valueOf(offset),\n                            String.valueOf(count),\n                            \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            final String key,\n            final String min,\n            final String max,\n            final int offset,\n            final int count) {\n        try {\n            return toTupleSet(\n                    redis.zrangebyscore(\n                            key,\n                            min,\n                            max,\n                            \"limit\",\n                            String.valueOf(offset),\n                            String.valueOf(count),\n                            \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(final String key, final double max, final double min) {\n        try {\n            return ZsetPair.members(\n                    redis.zrevrangebyscore(key, String.valueOf(max), String.valueOf(min)));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(final String key, final String max, final String min) {\n        try {\n            return ZsetPair.members(redis.zrevrangebyscore(key, max, min));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(\n            final String key,\n            final double max,\n            final double min,\n            final int offset,\n            final int count) {\n        try {\n            return ZsetPair.members(\n                    redis.zrevrangebyscore(\n                            key,\n                            String.valueOf(max),\n                            String.valueOf(min),\n                            \"limit\",\n                            String.valueOf(offset),\n                            String.valueOf(count)));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            final String key, final double max, final double min) {\n        try {\n            return toTupleSet(\n                    redis.zrevrangebyscore(\n                            key, String.valueOf(max), String.valueOf(min), \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            final String key,\n            final double max,\n            final double min,\n            final int offset,\n            final int count) {\n        try {\n            return toTupleSet(\n                    redis.zrevrangebyscore(\n                            key,\n                            String.valueOf(max),\n                            String.valueOf(min),\n                            \"limit\",\n                            String.valueOf(offset),\n                            String.valueOf(count),\n                            \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            final String key,\n            final String max,\n            final String min,\n            final int offset,\n            final int count) {\n        try {\n            return toTupleSet(\n                    redis.zrevrangebyscore(\n                            key,\n                            max,\n                            min,\n                            \"limit\",\n                            String.valueOf(offset),\n                            String.valueOf(count),\n                            \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(\n            final String key,\n            final String max,\n            final String min,\n            final int offset,\n            final int count) {\n        try {\n            return ZsetPair.members(\n                    redis.zrevrangebyscore(\n                            key, max, min, \"limit\", String.valueOf(offset), String.valueOf(count)));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            final String key, final String max, final String min) {\n        try {\n            return toTupleSet(redis.zrevrangebyscore(key, max, min, \"withscores\"));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zremrangeByRank(final String key, final long start, final long end) {\n        try {\n            return redis.zremrangebyrank(key, start, end);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zremrangeByScore(final String key, final double start, final double end) {\n        try {\n            return redis.zremrangebyscore(key, String.valueOf(start), String.valueOf(end));\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zremrangeByScore(final String key, final String start, final String end) {\n        try {\n            return redis.zremrangebyscore(key, start, end);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public Long zunionstore(final String dstkey, final String... sets) {\n        try {\n            return redis.zunionstore(dstkey, sets.length, sets);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    @Override\n    public ScanResult<String> sscan(String key, String cursor, ScanParams params) {\n        try {\n            org.rarefiedredis.redis.ScanResult<Set<String>> sr =\n                    redis.sscan(key, Long.parseLong(cursor), \"count\", \"1000000\");\n            List<String> list = new ArrayList<>(sr.results);\n            return new ScanResult<>(\"0\", list);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    public ScanResult<Entry<String, String>> hscan(final String key, final String cursor) {\n        try {\n            org.rarefiedredis.redis.ScanResult<Map<String, String>> mockr =\n                    redis.hscan(key, Long.parseLong(cursor), \"count\", \"1000000\");\n            Map<String, String> results = mockr.results;\n            List<Entry<String, String>> list = new ArrayList<>(results.entrySet());\n            return new ScanResult<>(\"0\", list);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n\n    public ScanResult<Tuple> zscan(final String key, final String cursor) {\n        try {\n            org.rarefiedredis.redis.ScanResult<Set<ZsetPair>> sr =\n                    redis.zscan(key, Long.parseLong(cursor), \"count\", \"1000000\");\n            List<ZsetPair> list = new ArrayList<>(sr.results);\n            List<Tuple> tl = new LinkedList<>();\n            list.forEach(p -> tl.add(new Tuple(p.member, p.score)));\n            return new ScanResult<>(\"0\", tl);\n        } catch (Exception e) {\n            throw new JedisException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/jedis/JedisProxy.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Optional;\nimport java.util.Set;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.redis.config.AnyRedisCondition;\n\nimport redis.clients.jedis.ScanParams;\nimport redis.clients.jedis.ScanResult;\nimport redis.clients.jedis.Tuple;\nimport redis.clients.jedis.commands.JedisCommands;\nimport redis.clients.jedis.params.ZAddParams;\n\nimport static com.netflix.conductor.redis.config.RedisCommonConfiguration.DEFAULT_CLIENT_INJECTION_NAME;\n\n/** Proxy for the {@link JedisCommands} object. */\n@Component\n@Conditional(AnyRedisCondition.class)\npublic class JedisProxy {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(JedisProxy.class);\n\n    protected JedisCommands jedisCommands;\n\n    public JedisProxy(@Qualifier(DEFAULT_CLIENT_INJECTION_NAME) JedisCommands jedisCommands) {\n        this.jedisCommands = jedisCommands;\n    }\n\n    public Set<String> zrange(String key, long start, long end) {\n        return jedisCommands.zrange(key, start, end);\n    }\n\n    public Set<Tuple> zrangeByScoreWithScores(String key, double maxScore, int count) {\n        return jedisCommands.zrangeByScoreWithScores(key, 0, maxScore, 0, count);\n    }\n\n    public Set<String> zrangeByScore(String key, double maxScore, int count) {\n        return jedisCommands.zrangeByScore(key, 0, maxScore, 0, count);\n    }\n\n    public Set<String> zrangeByScore(String key, double minScore, double maxScore, int count) {\n        return jedisCommands.zrangeByScore(key, minScore, maxScore, 0, count);\n    }\n\n    public ScanResult<Tuple> zscan(String key, int cursor) {\n        return jedisCommands.zscan(key, \"\" + cursor);\n    }\n\n    public String get(String key) {\n        return jedisCommands.get(key);\n    }\n\n    public Long zcard(String key) {\n        return jedisCommands.zcard(key);\n    }\n\n    public Long del(String key) {\n        return jedisCommands.del(key);\n    }\n\n    public Long zrem(String key, String member) {\n        return jedisCommands.zrem(key, member);\n    }\n\n    public long zremrangeByScore(String key, String start, String end) {\n        return jedisCommands.zremrangeByScore(key, start, end);\n    }\n\n    public long zcount(String key, double min, double max) {\n        return jedisCommands.zcount(key, min, max);\n    }\n\n    public String set(String key, String value) {\n        return jedisCommands.set(key, value);\n    }\n\n    public Long setnx(String key, String value) {\n        return jedisCommands.setnx(key, value);\n    }\n\n    public Long zadd(String key, double score, String member) {\n        return jedisCommands.zadd(key, score, member);\n    }\n\n    public Long zaddnx(String key, double score, String member) {\n        ZAddParams params = ZAddParams.zAddParams().nx();\n        return jedisCommands.zadd(key, score, member, params);\n    }\n\n    public Long hset(String key, String field, String value) {\n        return jedisCommands.hset(key, field, value);\n    }\n\n    public Long hsetnx(String key, String field, String value) {\n        return jedisCommands.hsetnx(key, field, value);\n    }\n\n    public Long hlen(String key) {\n        return jedisCommands.hlen(key);\n    }\n\n    public String hget(String key, String field) {\n        return jedisCommands.hget(key, field);\n    }\n\n    public Optional<String> optionalHget(String key, String field) {\n        return Optional.ofNullable(jedisCommands.hget(key, field));\n    }\n\n    public Map<String, String> hscan(String key, int count) {\n        Map<String, String> m = new HashMap<>();\n        int cursor = 0;\n        do {\n            ScanResult<Entry<String, String>> scanResult = jedisCommands.hscan(key, \"\" + cursor);\n            cursor = Integer.parseInt(scanResult.getCursor());\n            for (Entry<String, String> r : scanResult.getResult()) {\n                m.put(r.getKey(), r.getValue());\n            }\n            if (m.size() > count) {\n                break;\n            }\n        } while (cursor > 0);\n\n        return m;\n    }\n\n    public Map<String, String> hgetAll(String key) {\n        Map<String, String> m = new HashMap<>();\n        int cursor = 0;\n        do {\n            ScanResult<Entry<String, String>> scanResult = jedisCommands.hscan(key, \"\" + cursor);\n            cursor = Integer.parseInt(scanResult.getCursor());\n            for (Entry<String, String> r : scanResult.getResult()) {\n                m.put(r.getKey(), r.getValue());\n            }\n        } while (cursor > 0);\n\n        return m;\n    }\n\n    public List<String> hvals(String key) {\n        LOGGER.trace(\"hvals {}\", key);\n        return jedisCommands.hvals(key);\n    }\n\n    public Set<String> hkeys(String key) {\n        LOGGER.trace(\"hkeys {}\", key);\n        Set<String> keys = new HashSet<>();\n        int cursor = 0;\n        do {\n            ScanResult<Entry<String, String>> sr = jedisCommands.hscan(key, \"\" + cursor);\n            cursor = Integer.parseInt(sr.getCursor());\n            List<Entry<String, String>> result = sr.getResult();\n            for (Entry<String, String> e : result) {\n                keys.add(e.getKey());\n            }\n        } while (cursor > 0);\n\n        return keys;\n    }\n\n    public Long hdel(String key, String... fields) {\n        LOGGER.trace(\"hdel {} {}\", key, fields[0]);\n        return jedisCommands.hdel(key, fields);\n    }\n\n    public Long expire(String key, int seconds) {\n        return jedisCommands.expire(key, seconds);\n    }\n\n    public Boolean hexists(String key, String field) {\n        return jedisCommands.hexists(key, field);\n    }\n\n    public Long sadd(String key, String value) {\n        LOGGER.trace(\"sadd {} {}\", key, value);\n        return jedisCommands.sadd(key, value);\n    }\n\n    public Long srem(String key, String member) {\n        LOGGER.trace(\"srem {} {}\", key, member);\n        return jedisCommands.srem(key, member);\n    }\n\n    public boolean sismember(String key, String member) {\n        return jedisCommands.sismember(key, member);\n    }\n\n    public Set<String> smembers(String key) {\n        LOGGER.trace(\"smembers {}\", key);\n        Set<String> r = new HashSet<>();\n        int cursor = 0;\n        ScanParams sp = new ScanParams();\n        sp.count(50);\n\n        do {\n            ScanResult<String> scanResult = jedisCommands.sscan(key, \"\" + cursor, sp);\n            cursor = Integer.parseInt(scanResult.getCursor());\n            r.addAll(scanResult.getResult());\n        } while (cursor > 0);\n\n        return r;\n    }\n\n    public Long scard(String key) {\n        return jedisCommands.scard(key);\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/jedis/JedisSentinel.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\n\nimport redis.clients.jedis.BitPosParams;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.GeoRadiusResponse;\nimport redis.clients.jedis.GeoUnit;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPoolAbstract;\nimport redis.clients.jedis.ListPosition;\nimport redis.clients.jedis.ScanParams;\nimport redis.clients.jedis.ScanResult;\nimport redis.clients.jedis.SortingParams;\nimport redis.clients.jedis.StreamConsumersInfo;\nimport redis.clients.jedis.StreamEntry;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.StreamGroupInfo;\nimport redis.clients.jedis.StreamInfo;\nimport redis.clients.jedis.StreamPendingEntry;\nimport redis.clients.jedis.Tuple;\nimport redis.clients.jedis.commands.JedisCommands;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\n\npublic class JedisSentinel implements JedisCommands {\n\n    private final JedisPoolAbstract jedisPool;\n\n    public JedisSentinel(JedisPoolAbstract jedisPool) {\n        this.jedisPool = jedisPool;\n    }\n\n    @Override\n    public String set(String key, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.set(key, value);\n        }\n    }\n\n    @Override\n    public String set(String key, String value, SetParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.set(key, value, params);\n        }\n    }\n\n    @Override\n    public String get(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.get(key);\n        }\n    }\n\n    @Override\n    public Boolean exists(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.exists(key);\n        }\n    }\n\n    @Override\n    public Long persist(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.persist(key);\n        }\n    }\n\n    @Override\n    public String type(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.type(key);\n        }\n    }\n\n    @Override\n    public byte[] dump(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.dump(key);\n        }\n    }\n\n    @Override\n    public String restore(String key, int ttl, byte[] serializedValue) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.restore(key, ttl, serializedValue);\n        }\n    }\n\n    @Override\n    public String restoreReplace(String key, int ttl, byte[] serializedValue) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.restoreReplace(key, ttl, serializedValue);\n        }\n    }\n\n    @Override\n    public Long expire(String key, int seconds) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.expire(key, seconds);\n        }\n    }\n\n    @Override\n    public Long pexpire(String key, long milliseconds) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.pexpire(key, milliseconds);\n        }\n    }\n\n    @Override\n    public Long expireAt(String key, long unixTime) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.expireAt(key, unixTime);\n        }\n    }\n\n    @Override\n    public Long pexpireAt(String key, long millisecondsTimestamp) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.pexpireAt(key, millisecondsTimestamp);\n        }\n    }\n\n    @Override\n    public Long ttl(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.ttl(key);\n        }\n    }\n\n    @Override\n    public Long pttl(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.pttl(key);\n        }\n    }\n\n    @Override\n    public Long touch(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.touch(key);\n        }\n    }\n\n    @Override\n    public Boolean setbit(String key, long offset, boolean value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.setbit(key, offset, value);\n        }\n    }\n\n    @Override\n    public Boolean setbit(String key, long offset, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.setbit(key, offset, value);\n        }\n    }\n\n    @Override\n    public Boolean getbit(String key, long offset) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.getbit(key, offset);\n        }\n    }\n\n    @Override\n    public Long setrange(String key, long offset, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.setrange(key, offset, value);\n        }\n    }\n\n    @Override\n    public String getrange(String key, long startOffset, long endOffset) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.getrange(key, startOffset, endOffset);\n        }\n    }\n\n    @Override\n    public String getSet(String key, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.getSet(key, value);\n        }\n    }\n\n    @Override\n    public Long setnx(String key, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.setnx(key, value);\n        }\n    }\n\n    @Override\n    public String setex(String key, int seconds, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.setex(key, seconds, value);\n        }\n    }\n\n    @Override\n    public String psetex(String key, long milliseconds, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.psetex(key, milliseconds, value);\n        }\n    }\n\n    @Override\n    public Long decrBy(String key, long integer) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.decrBy(key, integer);\n        }\n    }\n\n    @Override\n    public Long decr(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.decr(key);\n        }\n    }\n\n    @Override\n    public Long incrBy(String key, long integer) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.incrBy(key, integer);\n        }\n    }\n\n    @Override\n    public Double incrByFloat(String key, double value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.incrByFloat(key, value);\n        }\n    }\n\n    @Override\n    public Long incr(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.incr(key);\n        }\n    }\n\n    @Override\n    public Long append(String key, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.append(key, value);\n        }\n    }\n\n    @Override\n    public String substr(String key, int start, int end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.substr(key, start, end);\n        }\n    }\n\n    @Override\n    public Long hset(String key, String field, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hset(key, field, value);\n        }\n    }\n\n    @Override\n    public Long hset(String key, Map<String, String> hash) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hset(key, hash);\n        }\n    }\n\n    @Override\n    public String hget(String key, String field) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hget(key, field);\n        }\n    }\n\n    @Override\n    public Long hsetnx(String key, String field, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hsetnx(key, field, value);\n        }\n    }\n\n    @Override\n    public String hmset(String key, Map<String, String> hash) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hmset(key, hash);\n        }\n    }\n\n    @Override\n    public List<String> hmget(String key, String... fields) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hmget(key, fields);\n        }\n    }\n\n    @Override\n    public Long hincrBy(String key, String field, long value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hincrBy(key, field, value);\n        }\n    }\n\n    @Override\n    public Double hincrByFloat(String key, String field, double value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hincrByFloat(key, field, value);\n        }\n    }\n\n    @Override\n    public Boolean hexists(String key, String field) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hexists(key, field);\n        }\n    }\n\n    @Override\n    public Long hdel(String key, String... field) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hdel(key, field);\n        }\n    }\n\n    @Override\n    public Long hlen(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hlen(key);\n        }\n    }\n\n    @Override\n    public Set<String> hkeys(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hkeys(key);\n        }\n    }\n\n    @Override\n    public List<String> hvals(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hvals(key);\n        }\n    }\n\n    @Override\n    public Map<String, String> hgetAll(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hgetAll(key);\n        }\n    }\n\n    @Override\n    public Long rpush(String key, String... string) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.rpush(key, string);\n        }\n    }\n\n    @Override\n    public Long lpush(String key, String... string) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.lpush(key, string);\n        }\n    }\n\n    @Override\n    public Long llen(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.llen(key);\n        }\n    }\n\n    @Override\n    public List<String> lrange(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.lrange(key, start, end);\n        }\n    }\n\n    @Override\n    public String ltrim(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.ltrim(key, start, end);\n        }\n    }\n\n    @Override\n    public String lindex(String key, long index) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.lindex(key, index);\n        }\n    }\n\n    @Override\n    public String lset(String key, long index, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.lset(key, index, value);\n        }\n    }\n\n    @Override\n    public Long lrem(String key, long count, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.lrem(key, count, value);\n        }\n    }\n\n    @Override\n    public String lpop(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.lpop(key);\n        }\n    }\n\n    @Override\n    public String rpop(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.rpop(key);\n        }\n    }\n\n    @Override\n    public Long sadd(String key, String... member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.sadd(key, member);\n        }\n    }\n\n    @Override\n    public Set<String> smembers(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.smembers(key);\n        }\n    }\n\n    @Override\n    public Long srem(String key, String... member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.srem(key, member);\n        }\n    }\n\n    @Override\n    public String spop(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.spop(key);\n        }\n    }\n\n    @Override\n    public Set<String> spop(String key, long count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.spop(key, count);\n        }\n    }\n\n    @Override\n    public Long scard(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.scard(key);\n        }\n    }\n\n    @Override\n    public Boolean sismember(String key, String member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.sismember(key, member);\n        }\n    }\n\n    @Override\n    public String srandmember(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.srandmember(key);\n        }\n    }\n\n    @Override\n    public List<String> srandmember(String key, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.srandmember(key, count);\n        }\n    }\n\n    @Override\n    public Long strlen(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.strlen(key);\n        }\n    }\n\n    @Override\n    public Long zadd(String key, double score, String member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zadd(key, score, member);\n        }\n    }\n\n    @Override\n    public Long zadd(String key, double score, String member, ZAddParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zadd(key, score, member, params);\n        }\n    }\n\n    @Override\n    public Long zadd(String key, Map<String, Double> scoreMembers) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zadd(key, scoreMembers);\n        }\n    }\n\n    @Override\n    public Long zadd(String key, Map<String, Double> scoreMembers, ZAddParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zadd(key, scoreMembers, params);\n        }\n    }\n\n    @Override\n    public Set<String> zrange(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrange(key, start, end);\n        }\n    }\n\n    @Override\n    public Long zrem(String key, String... member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrem(key, member);\n        }\n    }\n\n    @Override\n    public Double zincrby(String key, double score, String member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zincrby(key, score, member);\n        }\n    }\n\n    @Override\n    public Double zincrby(String key, double score, String member, ZIncrByParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zincrby(key, score, member, params);\n        }\n    }\n\n    @Override\n    public Long zrank(String key, String member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrank(key, member);\n        }\n    }\n\n    @Override\n    public Long zrevrank(String key, String member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrank(key, member);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrange(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrange(key, start, end);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeWithScores(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeWithScores(key, start, end);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeWithScores(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeWithScores(key, start, end);\n        }\n    }\n\n    @Override\n    public Long zcard(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zcard(key);\n        }\n    }\n\n    @Override\n    public Double zscore(String key, String member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zscore(key, member);\n        }\n    }\n\n    @Override\n    public Tuple zpopmax(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zpopmax(key);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zpopmax(String key, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zpopmax(key, count);\n        }\n    }\n\n    @Override\n    public Tuple zpopmin(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zpopmin(key);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zpopmin(String key, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zpopmin(key, count);\n        }\n    }\n\n    @Override\n    public List<String> sort(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.sort(key);\n        }\n    }\n\n    @Override\n    public List<String> sort(String key, SortingParams sortingParameters) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.sort(key, sortingParameters);\n        }\n    }\n\n    @Override\n    public Long zcount(String key, double min, double max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zcount(key, min, max);\n        }\n    }\n\n    @Override\n    public Long zcount(String key, String min, String max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zcount(key, min, max);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, double min, double max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScore(key, min, max);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, String min, String max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScore(key, min, max);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, double max, double min) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScore(key, max, min);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, double min, double max, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScore(key, min, max, offset, count);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, String max, String min) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScore(key, max, min);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, String min, String max, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScore(key, min, max, offset, count);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, double max, double min, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScore(key, max, min, offset, count);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(String key, double min, double max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScoreWithScores(key, min, max);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(String key, double max, double min) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScoreWithScores(key, max, min);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            String key, double min, double max, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScoreWithScores(key, min, max, offset, count);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, String max, String min, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScore(key, max, min, offset, count);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(String key, String min, String max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScoreWithScores(key, min, max);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(String key, String max, String min) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScoreWithScores(key, max, min);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            String key, String min, String max, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByScoreWithScores(key, min, max, offset, count);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            String key, double max, double min, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScoreWithScores(key, max, min, offset, count);\n        }\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            String key, String max, String min, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByScoreWithScores(key, max, min, offset, count);\n        }\n    }\n\n    @Override\n    public Long zremrangeByRank(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zremrangeByRank(key, start, end);\n        }\n    }\n\n    @Override\n    public Long zremrangeByScore(String key, double start, double end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zremrangeByScore(key, start, end);\n        }\n    }\n\n    @Override\n    public Long zremrangeByScore(String key, String start, String end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zremrangeByScore(key, start, end);\n        }\n    }\n\n    @Override\n    public Long zlexcount(String key, String min, String max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zlexcount(key, min, max);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByLex(String key, String min, String max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByLex(key, min, max);\n        }\n    }\n\n    @Override\n    public Set<String> zrangeByLex(String key, String min, String max, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrangeByLex(key, min, max, offset, count);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByLex(String key, String max, String min) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByLex(key, max, min);\n        }\n    }\n\n    @Override\n    public Set<String> zrevrangeByLex(String key, String max, String min, int offset, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zrevrangeByLex(key, max, min, offset, count);\n        }\n    }\n\n    @Override\n    public Long zremrangeByLex(String key, String min, String max) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zremrangeByLex(key, min, max);\n        }\n    }\n\n    @Override\n    public Long linsert(String key, ListPosition where, String pivot, String value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.linsert(key, where, pivot, value);\n        }\n    }\n\n    @Override\n    public Long lpushx(String key, String... string) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.lpushx(key, string);\n        }\n    }\n\n    @Override\n    public Long rpushx(String key, String... string) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.rpushx(key, string);\n        }\n    }\n\n    @Override\n    public List<String> blpop(int timeout, String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.blpop(timeout, key);\n        }\n    }\n\n    @Override\n    public List<String> brpop(int timeout, String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.brpop(timeout, key);\n        }\n    }\n\n    @Override\n    public Long del(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.del(key);\n        }\n    }\n\n    @Override\n    public Long unlink(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.unlink(key);\n        }\n    }\n\n    @Override\n    public String echo(String string) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.echo(string);\n        }\n    }\n\n    @Override\n    public Long move(String key, int dbIndex) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.move(key, dbIndex);\n        }\n    }\n\n    @Override\n    public Long bitcount(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.bitcount(key);\n        }\n    }\n\n    @Override\n    public Long bitcount(String key, long start, long end) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.bitcount(key, start, end);\n        }\n    }\n\n    @Override\n    public Long bitpos(String key, boolean value) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.bitpos(key, value);\n        }\n    }\n\n    @Override\n    public Long bitpos(String key, boolean value, BitPosParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.bitpos(key, value, params);\n        }\n    }\n\n    @Override\n    public ScanResult<Entry<String, String>> hscan(String key, String cursor) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hscan(key, cursor);\n        }\n    }\n\n    @Override\n    public ScanResult<Entry<String, String>> hscan(String key, String cursor, ScanParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hscan(key, cursor, params);\n        }\n    }\n\n    @Override\n    public ScanResult<String> sscan(String key, String cursor) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.sscan(key, cursor);\n        }\n    }\n\n    @Override\n    public ScanResult<String> sscan(String key, String cursor, ScanParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.sscan(key, cursor, params);\n        }\n    }\n\n    @Override\n    public ScanResult<Tuple> zscan(String key, String cursor) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zscan(key, cursor);\n        }\n    }\n\n    @Override\n    public ScanResult<Tuple> zscan(String key, String cursor, ScanParams params) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.zscan(key, cursor, params);\n        }\n    }\n\n    @Override\n    public Long pfadd(String key, String... elements) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.pfadd(key, elements);\n        }\n    }\n\n    @Override\n    public long pfcount(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.pfcount(key);\n        }\n    }\n\n    @Override\n    public Long geoadd(String key, double longitude, double latitude, String member) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.geoadd(key, longitude, latitude, member);\n        }\n    }\n\n    @Override\n    public Long geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.geoadd(key, memberCoordinateMap);\n        }\n    }\n\n    @Override\n    public Double geodist(String key, String member1, String member2) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.geodist(key, member1, member2);\n        }\n    }\n\n    @Override\n    public Double geodist(String key, String member1, String member2, GeoUnit unit) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.geodist(key, member1, member2, unit);\n        }\n    }\n\n    @Override\n    public List<String> geohash(String key, String... members) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.geohash(key, members);\n        }\n    }\n\n    @Override\n    public List<GeoCoordinate> geopos(String key, String... members) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.geopos(key, members);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadius(\n            String key, double longitude, double latitude, double radius, GeoUnit unit) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadius(key, longitude, latitude, radius, unit);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusReadonly(\n            String key, double longitude, double latitude, double radius, GeoUnit unit) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadiusReadonly(key, longitude, latitude, radius, unit);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadius(\n            String key,\n            double longitude,\n            double latitude,\n            double radius,\n            GeoUnit unit,\n            GeoRadiusParam param) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadius(key, longitude, latitude, radius, unit, param);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusReadonly(\n            String key,\n            double longitude,\n            double latitude,\n            double radius,\n            GeoUnit unit,\n            GeoRadiusParam param) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadiusReadonly(key, longitude, latitude, radius, unit, param);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMember(\n            String key, String member, double radius, GeoUnit unit) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadiusByMember(key, member, radius, unit);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMemberReadonly(\n            String key, String member, double radius, GeoUnit unit) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadiusByMemberReadonly(key, member, radius, unit);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMember(\n            String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadiusByMember(key, member, radius, unit, param);\n        }\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMemberReadonly(\n            String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.georadiusByMemberReadonly(key, member, radius, unit, param);\n        }\n    }\n\n    @Override\n    public List<Long> bitfield(String key, String... arguments) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.bitfield(key, arguments);\n        }\n    }\n\n    @Override\n    public List<Long> bitfieldReadonly(String key, String... arguments) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.bitfieldReadonly(key, arguments);\n        }\n    }\n\n    @Override\n    public Long hstrlen(String key, String field) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.hstrlen(key, field);\n        }\n    }\n\n    @Override\n    public StreamEntryID xadd(String key, StreamEntryID id, Map<String, String> hash) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xadd(key, id, hash);\n        }\n    }\n\n    @Override\n    public StreamEntryID xadd(\n            String key,\n            StreamEntryID id,\n            Map<String, String> hash,\n            long maxLen,\n            boolean approximateLength) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xadd(key, id, hash, maxLen, approximateLength);\n        }\n    }\n\n    @Override\n    public Long xlen(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xlen(key);\n        }\n    }\n\n    @Override\n    public List<StreamEntry> xrange(String key, StreamEntryID start, StreamEntryID end, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xrange(key, start, end, count);\n        }\n    }\n\n    @Override\n    public List<StreamEntry> xrevrange(\n            String key, StreamEntryID end, StreamEntryID start, int count) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xrevrange(key, end, start, count);\n        }\n    }\n\n    @Override\n    public long xack(String key, String group, StreamEntryID... ids) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xack(key, group, ids);\n        }\n    }\n\n    @Override\n    public String xgroupCreate(String key, String groupname, StreamEntryID id, boolean makeStream) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xgroupCreate(key, groupname, id, makeStream);\n        }\n    }\n\n    @Override\n    public String xgroupSetID(String key, String groupname, StreamEntryID id) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xgroupSetID(key, groupname, id);\n        }\n    }\n\n    @Override\n    public long xgroupDestroy(String key, String groupname) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xgroupDestroy(key, groupname);\n        }\n    }\n\n    @Override\n    public Long xgroupDelConsumer(String key, String groupname, String consumername) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xgroupDelConsumer(key, groupname, consumername);\n        }\n    }\n\n    @Override\n    public List<StreamPendingEntry> xpending(\n            String key,\n            String groupname,\n            StreamEntryID start,\n            StreamEntryID end,\n            int count,\n            String consumername) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xpending(key, groupname, start, end, count, consumername);\n        }\n    }\n\n    @Override\n    public long xdel(String key, StreamEntryID... ids) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xdel(key, ids);\n        }\n    }\n\n    @Override\n    public long xtrim(String key, long maxLen, boolean approximate) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xtrim(key, maxLen, approximate);\n        }\n    }\n\n    @Override\n    public List<StreamEntry> xclaim(\n            String key,\n            String group,\n            String consumername,\n            long minIdleTime,\n            long newIdleTime,\n            int retries,\n            boolean force,\n            StreamEntryID... ids) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xclaim(\n                    key, group, consumername, minIdleTime, newIdleTime, retries, force, ids);\n        }\n    }\n\n    @Override\n    public StreamInfo xinfoStream(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xinfoStream(key);\n        }\n    }\n\n    @Override\n    public List<StreamGroupInfo> xinfoGroup(String key) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xinfoGroup(key);\n        }\n    }\n\n    @Override\n    public List<StreamConsumersInfo> xinfoConsumers(String key, String group) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return jedis.xinfoConsumers(key, group);\n        }\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/main/java/com/netflix/conductor/redis/jedis/JedisStandalone.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\n\nimport redis.clients.jedis.BitPosParams;\nimport redis.clients.jedis.GeoCoordinate;\nimport redis.clients.jedis.GeoRadiusResponse;\nimport redis.clients.jedis.GeoUnit;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPool;\nimport redis.clients.jedis.ListPosition;\nimport redis.clients.jedis.ScanParams;\nimport redis.clients.jedis.ScanResult;\nimport redis.clients.jedis.SortingParams;\nimport redis.clients.jedis.StreamConsumersInfo;\nimport redis.clients.jedis.StreamEntry;\nimport redis.clients.jedis.StreamEntryID;\nimport redis.clients.jedis.StreamGroupInfo;\nimport redis.clients.jedis.StreamInfo;\nimport redis.clients.jedis.StreamPendingEntry;\nimport redis.clients.jedis.Tuple;\nimport redis.clients.jedis.commands.JedisCommands;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\n\n/** A {@link JedisCommands} implementation that delegates to {@link JedisPool}. */\npublic class JedisStandalone implements JedisCommands {\n\n    private final JedisPool jedisPool;\n\n    public JedisStandalone(JedisPool jedisPool) {\n        this.jedisPool = jedisPool;\n    }\n\n    private <R> R executeInJedis(Function<Jedis, R> function) {\n        try (Jedis jedis = jedisPool.getResource()) {\n            return function.apply(jedis);\n        }\n    }\n\n    @Override\n    public String set(String key, String value) {\n        return executeInJedis(jedis -> jedis.set(key, value));\n    }\n\n    @Override\n    public String set(String key, String value, SetParams params) {\n        return executeInJedis(jedis -> jedis.set(key, value, params));\n    }\n\n    @Override\n    public String get(String key) {\n        return executeInJedis(jedis -> jedis.get(key));\n    }\n\n    @Override\n    public Boolean exists(String key) {\n        return executeInJedis(jedis -> jedis.exists(key));\n    }\n\n    @Override\n    public Long persist(String key) {\n        return executeInJedis(jedis -> jedis.persist(key));\n    }\n\n    @Override\n    public String type(String key) {\n        return executeInJedis(jedis -> jedis.type(key));\n    }\n\n    @Override\n    public byte[] dump(String key) {\n        return executeInJedis(jedis -> jedis.dump(key));\n    }\n\n    @Override\n    public String restore(String key, int ttl, byte[] serializedValue) {\n        return executeInJedis(jedis -> jedis.restore(key, ttl, serializedValue));\n    }\n\n    @Override\n    public String restoreReplace(String key, int ttl, byte[] serializedValue) {\n        return executeInJedis(jedis -> jedis.restoreReplace(key, ttl, serializedValue));\n    }\n\n    @Override\n    public Long expire(String key, int seconds) {\n        return executeInJedis(jedis -> jedis.expire(key, seconds));\n    }\n\n    @Override\n    public Long pexpire(String key, long milliseconds) {\n        return executeInJedis(jedis -> jedis.pexpire(key, milliseconds));\n    }\n\n    @Override\n    public Long expireAt(String key, long unixTime) {\n        return executeInJedis(jedis -> jedis.expireAt(key, unixTime));\n    }\n\n    @Override\n    public Long pexpireAt(String key, long millisecondsTimestamp) {\n        return executeInJedis(jedis -> jedis.pexpireAt(key, millisecondsTimestamp));\n    }\n\n    @Override\n    public Long ttl(String key) {\n        return executeInJedis(jedis -> jedis.ttl(key));\n    }\n\n    @Override\n    public Long pttl(String key) {\n        return executeInJedis(jedis -> jedis.pttl(key));\n    }\n\n    @Override\n    public Long touch(String key) {\n        return executeInJedis(jedis -> jedis.touch(key));\n    }\n\n    @Override\n    public Boolean setbit(String key, long offset, boolean value) {\n        return executeInJedis(jedis -> jedis.setbit(key, offset, value));\n    }\n\n    @Override\n    public Boolean setbit(String key, long offset, String value) {\n        return executeInJedis(jedis -> jedis.setbit(key, offset, value));\n    }\n\n    @Override\n    public Boolean getbit(String key, long offset) {\n        return executeInJedis(jedis -> jedis.getbit(key, offset));\n    }\n\n    @Override\n    public Long setrange(String key, long offset, String value) {\n        return executeInJedis(jedis -> jedis.setrange(key, offset, value));\n    }\n\n    @Override\n    public String getrange(String key, long startOffset, long endOffset) {\n        return executeInJedis(jedis -> jedis.getrange(key, startOffset, endOffset));\n    }\n\n    @Override\n    public String getSet(String key, String value) {\n        return executeInJedis(jedis -> jedis.getSet(key, value));\n    }\n\n    @Override\n    public Long setnx(String key, String value) {\n        return executeInJedis(jedis -> jedis.setnx(key, value));\n    }\n\n    @Override\n    public String setex(String key, int seconds, String value) {\n        return executeInJedis(jedis -> jedis.setex(key, seconds, value));\n    }\n\n    @Override\n    public String psetex(String key, long milliseconds, String value) {\n        return executeInJedis(jedis -> jedis.psetex(key, milliseconds, value));\n    }\n\n    @Override\n    public Long decrBy(String key, long decrement) {\n        return executeInJedis(jedis -> jedis.decrBy(key, decrement));\n    }\n\n    @Override\n    public Long decr(String key) {\n        return executeInJedis(jedis -> jedis.decr(key));\n    }\n\n    @Override\n    public Long incrBy(String key, long increment) {\n        return executeInJedis(jedis -> jedis.incrBy(key, increment));\n    }\n\n    @Override\n    public Double incrByFloat(String key, double increment) {\n        return executeInJedis(jedis -> jedis.incrByFloat(key, increment));\n    }\n\n    @Override\n    public Long incr(String key) {\n        return executeInJedis(jedis -> jedis.incr(key));\n    }\n\n    @Override\n    public Long append(String key, String value) {\n        return executeInJedis(jedis -> jedis.append(key, value));\n    }\n\n    @Override\n    public String substr(String key, int start, int end) {\n        return executeInJedis(jedis -> jedis.substr(key, start, end));\n    }\n\n    @Override\n    public Long hset(String key, String field, String value) {\n        return executeInJedis(jedis -> jedis.hset(key, field, value));\n    }\n\n    @Override\n    public Long hset(String key, Map<String, String> hash) {\n        return executeInJedis(jedis -> jedis.hset(key, hash));\n    }\n\n    @Override\n    public String hget(String key, String field) {\n        return executeInJedis(jedis -> jedis.hget(key, field));\n    }\n\n    @Override\n    public Long hsetnx(String key, String field, String value) {\n        return executeInJedis(jedis -> jedis.hsetnx(key, field, value));\n    }\n\n    @Override\n    public String hmset(String key, Map<String, String> hash) {\n        return executeInJedis(jedis -> jedis.hmset(key, hash));\n    }\n\n    @Override\n    public List<String> hmget(String key, String... fields) {\n        return executeInJedis(jedis -> jedis.hmget(key, fields));\n    }\n\n    @Override\n    public Long hincrBy(String key, String field, long value) {\n        return executeInJedis(jedis -> jedis.hincrBy(key, field, value));\n    }\n\n    @Override\n    public Double hincrByFloat(String key, String field, double value) {\n        return executeInJedis(jedis -> jedis.hincrByFloat(key, field, value));\n    }\n\n    @Override\n    public Boolean hexists(String key, String field) {\n        return executeInJedis(jedis -> jedis.hexists(key, field));\n    }\n\n    @Override\n    public Long hdel(String key, String... field) {\n        return executeInJedis(jedis -> jedis.hdel(key, field));\n    }\n\n    @Override\n    public Long hlen(String key) {\n        return executeInJedis(jedis -> jedis.hlen(key));\n    }\n\n    @Override\n    public Set<String> hkeys(String key) {\n        return executeInJedis(jedis -> jedis.hkeys(key));\n    }\n\n    @Override\n    public List<String> hvals(String key) {\n        return executeInJedis(jedis -> jedis.hvals(key));\n    }\n\n    @Override\n    public Map<String, String> hgetAll(String key) {\n        return executeInJedis(jedis -> jedis.hgetAll(key));\n    }\n\n    @Override\n    public Long rpush(String key, String... string) {\n        return executeInJedis(jedis -> jedis.rpush(key));\n    }\n\n    @Override\n    public Long lpush(String key, String... string) {\n        return executeInJedis(jedis -> jedis.lpush(key, string));\n    }\n\n    @Override\n    public Long llen(String key) {\n        return executeInJedis(jedis -> jedis.llen(key));\n    }\n\n    @Override\n    public List<String> lrange(String key, long start, long stop) {\n        return executeInJedis(jedis -> jedis.lrange(key, start, stop));\n    }\n\n    @Override\n    public String ltrim(String key, long start, long stop) {\n        return executeInJedis(jedis -> jedis.ltrim(key, start, stop));\n    }\n\n    @Override\n    public String lindex(String key, long index) {\n        return executeInJedis(jedis -> jedis.lindex(key, index));\n    }\n\n    @Override\n    public String lset(String key, long index, String value) {\n        return executeInJedis(jedis -> jedis.lset(key, index, value));\n    }\n\n    @Override\n    public Long lrem(String key, long count, String value) {\n        return executeInJedis(jedis -> jedis.lrem(key, count, value));\n    }\n\n    @Override\n    public String lpop(String key) {\n        return executeInJedis(jedis -> jedis.lpop(key));\n    }\n\n    @Override\n    public String rpop(String key) {\n        return executeInJedis(jedis -> jedis.rpop(key));\n    }\n\n    @Override\n    public Long sadd(String key, String... member) {\n        return executeInJedis(jedis -> jedis.sadd(key, member));\n    }\n\n    @Override\n    public Set<String> smembers(String key) {\n        return executeInJedis(jedis -> jedis.smembers(key));\n    }\n\n    @Override\n    public Long srem(String key, String... member) {\n        return executeInJedis(jedis -> jedis.srem(key, member));\n    }\n\n    @Override\n    public String spop(String key) {\n        return executeInJedis(jedis -> jedis.spop(key));\n    }\n\n    @Override\n    public Set<String> spop(String key, long count) {\n        return executeInJedis(jedis -> jedis.spop(key, count));\n    }\n\n    @Override\n    public Long scard(String key) {\n        return executeInJedis(jedis -> jedis.scard(key));\n    }\n\n    @Override\n    public Boolean sismember(String key, String member) {\n        return executeInJedis(jedis -> jedis.sismember(key, member));\n    }\n\n    @Override\n    public String srandmember(String key) {\n        return executeInJedis(jedis -> jedis.srandmember(key));\n    }\n\n    @Override\n    public List<String> srandmember(String key, int count) {\n        return executeInJedis(jedis -> jedis.srandmember(key, count));\n    }\n\n    @Override\n    public Long strlen(String key) {\n        return executeInJedis(jedis -> jedis.strlen(key));\n    }\n\n    @Override\n    public Long zadd(String key, double score, String member) {\n        return executeInJedis(jedis -> jedis.zadd(key, score, member));\n    }\n\n    @Override\n    public Long zadd(String key, double score, String member, ZAddParams params) {\n        return executeInJedis(jedis -> jedis.zadd(key, score, member, params));\n    }\n\n    @Override\n    public Long zadd(String key, Map<String, Double> scoreMembers) {\n        return executeInJedis(jedis -> jedis.zadd(key, scoreMembers));\n    }\n\n    @Override\n    public Long zadd(String key, Map<String, Double> scoreMembers, ZAddParams params) {\n        return executeInJedis(jedis -> jedis.zadd(key, scoreMembers, params));\n    }\n\n    @Override\n    public Set<String> zrange(String key, long start, long stop) {\n        return executeInJedis(jedis -> jedis.zrange(key, start, stop));\n    }\n\n    @Override\n    public Long zrem(String key, String... members) {\n        return executeInJedis(jedis -> jedis.zrem(key, members));\n    }\n\n    @Override\n    public Double zincrby(String key, double increment, String member) {\n        return executeInJedis(jedis -> jedis.zincrby(key, increment, member));\n    }\n\n    @Override\n    public Double zincrby(String key, double increment, String member, ZIncrByParams params) {\n        return executeInJedis(jedis -> jedis.zincrby(key, increment, member, params));\n    }\n\n    @Override\n    public Long zrank(String key, String member) {\n        return executeInJedis(jedis -> jedis.zrank(key, member));\n    }\n\n    @Override\n    public Long zrevrank(String key, String member) {\n        return executeInJedis(jedis -> jedis.zrevrank(key, member));\n    }\n\n    @Override\n    public Set<String> zrevrange(String key, long start, long stop) {\n        return executeInJedis(jedis -> jedis.zrevrange(key, start, stop));\n    }\n\n    @Override\n    public Set<Tuple> zrangeWithScores(String key, long start, long stop) {\n        return executeInJedis(jedis -> jedis.zrangeWithScores(key, start, stop));\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeWithScores(String key, long start, long stop) {\n        return executeInJedis(jedis -> jedis.zrevrangeWithScores(key, start, stop));\n    }\n\n    @Override\n    public Long zcard(String key) {\n        return executeInJedis(jedis -> jedis.zcard(key));\n    }\n\n    @Override\n    public Double zscore(String key, String member) {\n        return executeInJedis(jedis -> jedis.zscore(key, member));\n    }\n\n    @Override\n    public Tuple zpopmax(String key) {\n        return executeInJedis(jedis -> jedis.zpopmax(key));\n    }\n\n    @Override\n    public Set<Tuple> zpopmax(String key, int count) {\n        return executeInJedis(jedis -> jedis.zpopmax(key, count));\n    }\n\n    @Override\n    public Tuple zpopmin(String key) {\n        return executeInJedis(jedis -> jedis.zpopmin(key));\n    }\n\n    @Override\n    public Set<Tuple> zpopmin(String key, int count) {\n        return executeInJedis(jedis -> jedis.zpopmin(key, count));\n    }\n\n    @Override\n    public List<String> sort(String key) {\n        return executeInJedis(jedis -> jedis.sort(key));\n    }\n\n    @Override\n    public List<String> sort(String key, SortingParams sortingParameters) {\n        return executeInJedis(jedis -> jedis.sort(key, sortingParameters));\n    }\n\n    @Override\n    public Long zcount(String key, double min, double max) {\n        return executeInJedis(jedis -> jedis.zcount(key, min, max));\n    }\n\n    @Override\n    public Long zcount(String key, String min, String max) {\n        return executeInJedis(jedis -> jedis.zcount(key, min, max));\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, double min, double max) {\n        return executeInJedis(jedis -> jedis.zrangeByScore(key, min, max));\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, String min, String max) {\n        return executeInJedis(jedis -> jedis.zrangeByScore(key, min, max));\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, double max, double min) {\n        return executeInJedis(jedis -> jedis.zrevrangeByScore(key, max, min));\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, double min, double max, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrangeByScore(key, min, max, offset, count));\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, String max, String min) {\n        return executeInJedis(jedis -> jedis.zrevrangeByScore(key, max, min));\n    }\n\n    @Override\n    public Set<String> zrangeByScore(String key, String min, String max, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrangeByScore(key, min, max, offset, count));\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, double max, double min, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrevrangeByScore(key, max, min, offset, count));\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(String key, double min, double max) {\n        return executeInJedis(jedis -> jedis.zrangeByScoreWithScores(key, min, max));\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(String key, double max, double min) {\n        return executeInJedis(jedis -> jedis.zrevrangeByScoreWithScores(key, max, min));\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            String key, double min, double max, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrangeByScoreWithScores(key, min, max, offset, count));\n    }\n\n    @Override\n    public Set<String> zrevrangeByScore(String key, String max, String min, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrevrangeByScore(key, max, min, offset, count));\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(String key, String min, String max) {\n        return executeInJedis(jedis -> jedis.zrangeByScoreWithScores(key, min, max));\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(String key, String max, String min) {\n        return executeInJedis(jedis -> jedis.zrevrangeByScoreWithScores(key, max, min));\n    }\n\n    @Override\n    public Set<Tuple> zrangeByScoreWithScores(\n            String key, String min, String max, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrangeByScoreWithScores(key, min, max, offset, count));\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            String key, double max, double min, int offset, int count) {\n        return executeInJedis(\n                jedis -> jedis.zrevrangeByScoreWithScores(key, max, min, offset, count));\n    }\n\n    @Override\n    public Set<Tuple> zrevrangeByScoreWithScores(\n            String key, String max, String min, int offset, int count) {\n        return executeInJedis(\n                jedis -> jedis.zrevrangeByScoreWithScores(key, max, min, offset, count));\n    }\n\n    @Override\n    public Long zremrangeByRank(String key, long start, long stop) {\n        return executeInJedis(jedis -> jedis.zremrangeByRank(key, start, stop));\n    }\n\n    @Override\n    public Long zremrangeByScore(String key, double min, double max) {\n        return executeInJedis(jedis -> jedis.zremrangeByScore(key, min, max));\n    }\n\n    @Override\n    public Long zremrangeByScore(String key, String min, String max) {\n        return executeInJedis(jedis -> jedis.zremrangeByScore(key, min, max));\n    }\n\n    @Override\n    public Long zlexcount(String key, String min, String max) {\n        return executeInJedis(jedis -> jedis.zlexcount(key, min, max));\n    }\n\n    @Override\n    public Set<String> zrangeByLex(String key, String min, String max) {\n        return executeInJedis(jedis -> jedis.zrangeByLex(key, min, max));\n    }\n\n    @Override\n    public Set<String> zrangeByLex(String key, String min, String max, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrangeByLex(key, min, max, offset, count));\n    }\n\n    @Override\n    public Set<String> zrevrangeByLex(String key, String max, String min) {\n        return executeInJedis(jedis -> jedis.zrevrangeByLex(key, max, min));\n    }\n\n    @Override\n    public Set<String> zrevrangeByLex(String key, String max, String min, int offset, int count) {\n        return executeInJedis(jedis -> jedis.zrevrangeByLex(key, max, min, offset, count));\n    }\n\n    @Override\n    public Long zremrangeByLex(String key, String min, String max) {\n        return executeInJedis(jedis -> jedis.zremrangeByLex(key, min, max));\n    }\n\n    @Override\n    public Long linsert(String key, ListPosition where, String pivot, String value) {\n        return executeInJedis(jedis -> jedis.linsert(key, where, pivot, value));\n    }\n\n    @Override\n    public Long lpushx(String key, String... string) {\n        return executeInJedis(jedis -> jedis.lpushx(key, string));\n    }\n\n    @Override\n    public Long rpushx(String key, String... string) {\n        return executeInJedis(jedis -> jedis.rpushx(key, string));\n    }\n\n    @Override\n    public List<String> blpop(int timeout, String key) {\n        return executeInJedis(jedis -> jedis.blpop(timeout, key));\n    }\n\n    @Override\n    public List<String> brpop(int timeout, String key) {\n        return executeInJedis(jedis -> jedis.brpop(timeout, key));\n    }\n\n    @Override\n    public Long del(String key) {\n        return executeInJedis(jedis -> jedis.del(key));\n    }\n\n    @Override\n    public Long unlink(String key) {\n        return executeInJedis(jedis -> jedis.unlink(key));\n    }\n\n    @Override\n    public String echo(String string) {\n        return executeInJedis(jedis -> jedis.echo(string));\n    }\n\n    @Override\n    public Long move(String key, int dbIndex) {\n        return executeInJedis(jedis -> jedis.move(key, dbIndex));\n    }\n\n    @Override\n    public Long bitcount(String key) {\n        return executeInJedis(jedis -> jedis.bitcount(key));\n    }\n\n    @Override\n    public Long bitcount(String key, long start, long end) {\n        return executeInJedis(jedis -> jedis.bitcount(key, start, end));\n    }\n\n    @Override\n    public Long bitpos(String key, boolean value) {\n        return executeInJedis(jedis -> jedis.bitpos(key, value));\n    }\n\n    @Override\n    public Long bitpos(String key, boolean value, BitPosParams params) {\n        return executeInJedis(jedis -> jedis.bitpos(key, value, params));\n    }\n\n    @Override\n    public ScanResult<Map.Entry<String, String>> hscan(String key, String cursor) {\n        return executeInJedis(jedis -> jedis.hscan(key, cursor));\n    }\n\n    @Override\n    public ScanResult<Map.Entry<String, String>> hscan(\n            String key, String cursor, ScanParams params) {\n        return executeInJedis(jedis -> jedis.hscan(key, cursor, params));\n    }\n\n    @Override\n    public ScanResult<String> sscan(String key, String cursor) {\n        return executeInJedis(jedis -> jedis.sscan(key, cursor));\n    }\n\n    @Override\n    public ScanResult<Tuple> zscan(String key, String cursor) {\n        return executeInJedis(jedis -> jedis.zscan(key, cursor));\n    }\n\n    @Override\n    public ScanResult<Tuple> zscan(String key, String cursor, ScanParams params) {\n        return executeInJedis(jedis -> jedis.zscan(key, cursor, params));\n    }\n\n    @Override\n    public ScanResult<String> sscan(String key, String cursor, ScanParams params) {\n        return executeInJedis(jedis -> jedis.sscan(key, cursor, params));\n    }\n\n    @Override\n    public Long pfadd(String key, String... elements) {\n        return executeInJedis(jedis -> jedis.pfadd(key, elements));\n    }\n\n    @Override\n    public long pfcount(String key) {\n        return executeInJedis(jedis -> jedis.pfcount(key));\n    }\n\n    @Override\n    public Long geoadd(String key, double longitude, double latitude, String member) {\n        return executeInJedis(jedis -> jedis.geoadd(key, longitude, latitude, member));\n    }\n\n    @Override\n    public Long geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap) {\n        return executeInJedis(jedis -> jedis.geoadd(key, memberCoordinateMap));\n    }\n\n    @Override\n    public Double geodist(String key, String member1, String member2) {\n        return executeInJedis(jedis -> jedis.geodist(key, member1, member2));\n    }\n\n    @Override\n    public Double geodist(String key, String member1, String member2, GeoUnit unit) {\n        return executeInJedis(jedis -> jedis.geodist(key, member1, member2, unit));\n    }\n\n    @Override\n    public List<String> geohash(String key, String... members) {\n        return executeInJedis(jedis -> jedis.geohash(key, members));\n    }\n\n    @Override\n    public List<GeoCoordinate> geopos(String key, String... members) {\n        return executeInJedis(jedis -> jedis.geopos(key, members));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadius(\n            String key, double longitude, double latitude, double radius, GeoUnit unit) {\n        return executeInJedis(jedis -> jedis.georadius(key, longitude, latitude, radius, unit));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusReadonly(\n            String key, double longitude, double latitude, double radius, GeoUnit unit) {\n        return executeInJedis(\n                jedis -> jedis.georadiusReadonly(key, longitude, latitude, radius, unit));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadius(\n            String key,\n            double longitude,\n            double latitude,\n            double radius,\n            GeoUnit unit,\n            GeoRadiusParam param) {\n        return executeInJedis(\n                jedis -> jedis.georadius(key, longitude, latitude, radius, unit, param));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusReadonly(\n            String key,\n            double longitude,\n            double latitude,\n            double radius,\n            GeoUnit unit,\n            GeoRadiusParam param) {\n        return executeInJedis(\n                jedis -> jedis.georadiusReadonly(key, longitude, latitude, radius, unit, param));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMember(\n            String key, String member, double radius, GeoUnit unit) {\n        return executeInJedis(jedis -> jedis.georadiusByMember(key, member, radius, unit));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMemberReadonly(\n            String key, String member, double radius, GeoUnit unit) {\n        return executeInJedis(jedis -> jedis.georadiusByMemberReadonly(key, member, radius, unit));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMember(\n            String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n        return executeInJedis(jedis -> jedis.georadiusByMember(key, member, radius, unit, param));\n    }\n\n    @Override\n    public List<GeoRadiusResponse> georadiusByMemberReadonly(\n            String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {\n        return executeInJedis(\n                jedis -> jedis.georadiusByMemberReadonly(key, member, radius, unit, param));\n    }\n\n    @Override\n    public List<Long> bitfield(String key, String... arguments) {\n        return executeInJedis(jedis -> jedis.bitfield(key, arguments));\n    }\n\n    @Override\n    public List<Long> bitfieldReadonly(String key, String... arguments) {\n        return executeInJedis(jedis -> jedis.bitfieldReadonly(key, arguments));\n    }\n\n    @Override\n    public Long hstrlen(String key, String field) {\n        return executeInJedis(jedis -> jedis.hstrlen(key, field));\n    }\n\n    @Override\n    public StreamEntryID xadd(String key, StreamEntryID id, Map<String, String> hash) {\n        return executeInJedis(jedis -> jedis.xadd(key, id, hash));\n    }\n\n    @Override\n    public StreamEntryID xadd(\n            String key,\n            StreamEntryID id,\n            Map<String, String> hash,\n            long maxLen,\n            boolean approximateLength) {\n        return executeInJedis(jedis -> jedis.xadd(key, id, hash, maxLen, approximateLength));\n    }\n\n    @Override\n    public Long xlen(String key) {\n        return executeInJedis(jedis -> jedis.xlen(key));\n    }\n\n    @Override\n    public List<StreamEntry> xrange(String key, StreamEntryID start, StreamEntryID end, int count) {\n        return executeInJedis(jedis -> jedis.xrange(key, start, end, count));\n    }\n\n    @Override\n    public List<StreamEntry> xrevrange(\n            String key, StreamEntryID end, StreamEntryID start, int count) {\n        return executeInJedis(jedis -> jedis.xrevrange(key, end, start, count));\n    }\n\n    @Override\n    public long xack(String key, String group, StreamEntryID... ids) {\n        return executeInJedis(jedis -> jedis.xack(key, group, ids));\n    }\n\n    @Override\n    public String xgroupCreate(String key, String groupname, StreamEntryID id, boolean makeStream) {\n        return executeInJedis(jedis -> jedis.xgroupCreate(key, groupname, id, makeStream));\n    }\n\n    @Override\n    public String xgroupSetID(String key, String groupname, StreamEntryID id) {\n        return executeInJedis(jedis -> jedis.xgroupSetID(key, groupname, id));\n    }\n\n    @Override\n    public long xgroupDestroy(String key, String groupname) {\n        return executeInJedis(jedis -> jedis.xgroupDestroy(key, groupname));\n    }\n\n    @Override\n    public Long xgroupDelConsumer(String key, String groupname, String consumername) {\n        return executeInJedis(jedis -> jedis.hsetnx(key, groupname, consumername));\n    }\n\n    @Override\n    public List<StreamPendingEntry> xpending(\n            String key,\n            String groupname,\n            StreamEntryID start,\n            StreamEntryID end,\n            int count,\n            String consumername) {\n        return executeInJedis(\n                jedis -> jedis.xpending(key, groupname, start, end, count, consumername));\n    }\n\n    @Override\n    public long xdel(String key, StreamEntryID... ids) {\n        return executeInJedis(jedis -> jedis.xdel(key, ids));\n    }\n\n    @Override\n    public long xtrim(String key, long maxLen, boolean approximate) {\n        return executeInJedis(jedis -> jedis.xtrim(key, maxLen, approximate));\n    }\n\n    @Override\n    public List<StreamEntry> xclaim(\n            String key,\n            String group,\n            String consumername,\n            long minIdleTime,\n            long newIdleTime,\n            int retries,\n            boolean force,\n            StreamEntryID... ids) {\n        return executeInJedis(\n                jedis ->\n                        jedis.xclaim(\n                                key,\n                                group,\n                                consumername,\n                                minIdleTime,\n                                newIdleTime,\n                                retries,\n                                force,\n                                ids));\n    }\n\n    @Override\n    public StreamInfo xinfoStream(String key) {\n        return executeInJedis(jedis -> jedis.xinfoStream(key));\n    }\n\n    @Override\n    public List<StreamGroupInfo> xinfoGroup(String key) {\n        return executeInJedis(jedis -> jedis.xinfoGroup(key));\n    }\n\n    @Override\n    public List<StreamConsumersInfo> xinfoConsumers(String key, String group) {\n        return executeInJedis(jedis -> jedis.xinfoConsumers(key, group));\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/config/utils/RedisQueuesShardingStrategyProviderTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.config.utils;\n\nimport java.util.Collections;\n\nimport org.junit.Test;\n\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.dynoqueue.RedisQueuesShardingStrategyProvider;\nimport com.netflix.dyno.queues.Message;\nimport com.netflix.dyno.queues.ShardSupplier;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class RedisQueuesShardingStrategyProviderTest {\n\n    @Test\n    public void testStrategy() {\n        ShardSupplier shardSupplier = mock(ShardSupplier.class);\n        doReturn(\"current\").when(shardSupplier).getCurrentShard();\n        RedisQueuesShardingStrategyProvider.LocalOnlyStrategy strat =\n                new RedisQueuesShardingStrategyProvider.LocalOnlyStrategy(shardSupplier);\n\n        assertEquals(\"current\", strat.getNextShard(Collections.emptyList(), new Message(\"a\", \"b\")));\n    }\n\n    @Test\n    public void testProvider() {\n        ShardSupplier shardSupplier = mock(ShardSupplier.class);\n        RedisProperties properties = mock(RedisProperties.class);\n        when(properties.getQueueShardingStrategy()).thenReturn(\"localOnly\");\n        RedisQueuesShardingStrategyProvider stratProvider =\n                new RedisQueuesShardingStrategyProvider(shardSupplier, properties);\n        assertTrue(\n                stratProvider.get()\n                        instanceof RedisQueuesShardingStrategyProvider.LocalOnlyStrategy);\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/dao/BaseDynoDAOTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mock;\n\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class BaseDynoDAOTest {\n\n    @Mock private JedisProxy jedisProxy;\n\n    @Mock private ObjectMapper objectMapper;\n\n    private RedisProperties properties;\n    private ConductorProperties conductorProperties;\n\n    private BaseDynoDAO baseDynoDAO;\n\n    @Before\n    public void setUp() {\n        properties = mock(RedisProperties.class);\n        conductorProperties = mock(ConductorProperties.class);\n        this.baseDynoDAO =\n                new BaseDynoDAO(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Test\n    public void testNsKey() {\n        assertEquals(\"\", baseDynoDAO.nsKey());\n\n        String[] keys = {\"key1\", \"key2\"};\n        assertEquals(\"key1.key2\", baseDynoDAO.nsKey(keys));\n\n        when(properties.getWorkflowNamespacePrefix()).thenReturn(\"test\");\n        assertEquals(\"test\", baseDynoDAO.nsKey());\n\n        assertEquals(\"test.key1.key2\", baseDynoDAO.nsKey(keys));\n\n        when(conductorProperties.getStack()).thenReturn(\"stack\");\n        assertEquals(\"test.stack.key1.key2\", baseDynoDAO.nsKey(keys));\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/dao/DynoQueueDAOTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\n\nimport com.netflix.conductor.dao.QueueDAO;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.dynoqueue.RedisQueuesShardingStrategyProvider;\nimport com.netflix.conductor.redis.jedis.JedisMock;\nimport com.netflix.dyno.connectionpool.Host;\nimport com.netflix.dyno.queues.ShardSupplier;\nimport com.netflix.dyno.queues.redis.RedisQueues;\nimport com.netflix.dyno.queues.redis.sharding.ShardingStrategy;\n\nimport redis.clients.jedis.commands.JedisCommands;\n\nimport static com.netflix.conductor.redis.dynoqueue.RedisQueuesShardingStrategyProvider.LOCAL_ONLY_STRATEGY;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class DynoQueueDAOTest {\n\n    private QueueDAO queueDAO;\n\n    @Before\n    public void init() {\n        RedisProperties properties = mock(RedisProperties.class);\n        when(properties.getQueueShardingStrategy()).thenReturn(LOCAL_ONLY_STRATEGY);\n        JedisCommands jedisMock = new JedisMock();\n        ShardSupplier shardSupplier =\n                new ShardSupplier() {\n\n                    @Override\n                    public Set<String> getQueueShards() {\n                        return new HashSet<>(Collections.singletonList(\"a\"));\n                    }\n\n                    @Override\n                    public String getCurrentShard() {\n                        return \"a\";\n                    }\n\n                    @Override\n                    public String getShardForHost(Host host) {\n                        return \"a\";\n                    }\n                };\n        ShardingStrategy shardingStrategy =\n                new RedisQueuesShardingStrategyProvider(shardSupplier, properties).get();\n        RedisQueues redisQueues =\n                new RedisQueues(\n                        jedisMock, jedisMock, \"\", shardSupplier, 60_000, 60_000, shardingStrategy);\n        queueDAO = new DynoQueueDAO(redisQueues);\n    }\n\n    @Rule public ExpectedException expected = ExpectedException.none();\n\n    @Test\n    public void test() {\n        String queueName = \"TestQueue\";\n        long offsetTimeInSecond = 0;\n\n        for (int i = 0; i < 10; i++) {\n            String messageId = \"msg\" + i;\n            queueDAO.push(queueName, messageId, offsetTimeInSecond);\n        }\n        int size = queueDAO.getSize(queueName);\n        assertEquals(10, size);\n        Map<String, Long> details = queueDAO.queuesDetail();\n        assertEquals(1, details.size());\n        assertEquals(10L, details.get(queueName).longValue());\n\n        for (int i = 0; i < 10; i++) {\n            String messageId = \"msg\" + i;\n            queueDAO.pushIfNotExists(queueName, messageId, offsetTimeInSecond);\n        }\n\n        List<String> popped = queueDAO.pop(queueName, 10, 100);\n        assertNotNull(popped);\n        assertEquals(10, popped.size());\n\n        Map<String, Map<String, Map<String, Long>>> verbose = queueDAO.queuesDetailVerbose();\n        assertEquals(1, verbose.size());\n        long shardSize = verbose.get(queueName).get(\"a\").get(\"size\");\n        long unackedSize = verbose.get(queueName).get(\"a\").get(\"uacked\");\n        assertEquals(0, shardSize);\n        assertEquals(10, unackedSize);\n\n        popped.forEach(messageId -> queueDAO.ack(queueName, messageId));\n\n        verbose = queueDAO.queuesDetailVerbose();\n        assertEquals(1, verbose.size());\n        shardSize = verbose.get(queueName).get(\"a\").get(\"size\");\n        unackedSize = verbose.get(queueName).get(\"a\").get(\"uacked\");\n        assertEquals(0, shardSize);\n        assertEquals(0, unackedSize);\n\n        popped = queueDAO.pop(queueName, 10, 100);\n        assertNotNull(popped);\n        assertEquals(0, popped.size());\n\n        for (int i = 0; i < 10; i++) {\n            String messageId = \"msg\" + i;\n            queueDAO.pushIfNotExists(queueName, messageId, offsetTimeInSecond);\n        }\n        size = queueDAO.getSize(queueName);\n        assertEquals(10, size);\n\n        for (int i = 0; i < 10; i++) {\n            String messageId = \"msg\" + i;\n            queueDAO.remove(queueName, messageId);\n        }\n\n        size = queueDAO.getSize(queueName);\n        assertEquals(0, size);\n\n        for (int i = 0; i < 10; i++) {\n            String messageId = \"msg\" + i;\n            queueDAO.pushIfNotExists(queueName, messageId, offsetTimeInSecond);\n        }\n        queueDAO.flush(queueName);\n        size = queueDAO.getSize(queueName);\n        assertEquals(0, size);\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/dao/RedisEventHandlerDAOTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.List;\nimport java.util.UUID;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action;\nimport com.netflix.conductor.common.metadata.events.EventHandler.Action.Type;\nimport com.netflix.conductor.common.metadata.events.EventHandler.StartWorkflow;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisMock;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport redis.clients.jedis.commands.JedisCommands;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class RedisEventHandlerDAOTest {\n\n    private RedisEventHandlerDAO redisEventHandlerDAO;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void init() {\n        ConductorProperties conductorProperties = mock(ConductorProperties.class);\n        RedisProperties properties = mock(RedisProperties.class);\n        JedisCommands jedisMock = new JedisMock();\n        JedisProxy jedisProxy = new JedisProxy(jedisMock);\n\n        redisEventHandlerDAO =\n                new RedisEventHandlerDAO(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Test\n    public void testEventHandlers() {\n        String event1 = \"SQS::arn:account090:sqstest1\";\n        String event2 = \"SQS::arn:account090:sqstest2\";\n\n        EventHandler eventHandler = new EventHandler();\n        eventHandler.setName(UUID.randomUUID().toString());\n        eventHandler.setActive(false);\n        Action action = new Action();\n        action.setAction(Type.start_workflow);\n        action.setStart_workflow(new StartWorkflow());\n        action.getStart_workflow().setName(\"test_workflow\");\n        eventHandler.getActions().add(action);\n        eventHandler.setEvent(event1);\n\n        redisEventHandlerDAO.addEventHandler(eventHandler);\n        List<EventHandler> allEventHandlers = redisEventHandlerDAO.getAllEventHandlers();\n        assertNotNull(allEventHandlers);\n        assertEquals(1, allEventHandlers.size());\n        assertEquals(eventHandler.getName(), allEventHandlers.get(0).getName());\n        assertEquals(eventHandler.getEvent(), allEventHandlers.get(0).getEvent());\n\n        List<EventHandler> byEvents = redisEventHandlerDAO.getEventHandlersForEvent(event1, true);\n        assertNotNull(byEvents);\n        assertEquals(0, byEvents.size()); // event is marked as in-active\n\n        eventHandler.setActive(true);\n        eventHandler.setEvent(event2);\n        redisEventHandlerDAO.updateEventHandler(eventHandler);\n\n        allEventHandlers = redisEventHandlerDAO.getAllEventHandlers();\n        assertNotNull(allEventHandlers);\n        assertEquals(1, allEventHandlers.size());\n\n        byEvents = redisEventHandlerDAO.getEventHandlersForEvent(event1, true);\n        assertNotNull(byEvents);\n        assertEquals(0, byEvents.size());\n\n        byEvents = redisEventHandlerDAO.getEventHandlersForEvent(event2, true);\n        assertNotNull(byEvents);\n        assertEquals(1, byEvents.size());\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/dao/RedisExecutionDAOTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.dao.ExecutionDAO;\nimport com.netflix.conductor.dao.ExecutionDAOTest;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisMock;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport redis.clients.jedis.commands.JedisCommands;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class RedisExecutionDAOTest extends ExecutionDAOTest {\n\n    private RedisExecutionDAO executionDAO;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void init() {\n        ConductorProperties conductorProperties = mock(ConductorProperties.class);\n        RedisProperties properties = mock(RedisProperties.class);\n        when(properties.getEventExecutionPersistenceTTL()).thenReturn(Duration.ofSeconds(5));\n        JedisCommands jedisMock = new JedisMock();\n        JedisProxy jedisProxy = new JedisProxy(jedisMock);\n\n        executionDAO =\n                new RedisExecutionDAO(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Test\n    public void testCorrelateTaskToWorkflowInDS() {\n        String workflowId = \"workflowId\";\n        String taskId = \"taskId1\";\n        String taskDefName = \"task1\";\n\n        TaskDef def = new TaskDef();\n        def.setName(\"task1\");\n        def.setConcurrentExecLimit(1);\n\n        TaskModel task = new TaskModel();\n        task.setTaskId(taskId);\n        task.setWorkflowInstanceId(workflowId);\n        task.setReferenceTaskName(\"ref_name\");\n        task.setTaskDefName(taskDefName);\n        task.setTaskType(taskDefName);\n        task.setStatus(TaskModel.Status.IN_PROGRESS);\n        List<TaskModel> tasks = executionDAO.createTasks(Collections.singletonList(task));\n        assertNotNull(tasks);\n        assertEquals(1, tasks.size());\n\n        executionDAO.correlateTaskToWorkflowInDS(taskId, workflowId);\n        tasks = executionDAO.getTasksForWorkflow(workflowId);\n        assertNotNull(tasks);\n        assertEquals(workflowId, tasks.get(0).getWorkflowInstanceId());\n        assertEquals(taskId, tasks.get(0).getTaskId());\n    }\n\n    @Override\n    protected ExecutionDAO getExecutionDAO() {\n        return executionDAO;\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/dao/RedisMetadataDAOTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef.RetryLogic;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef.TimeoutPolicy;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.core.exception.ConflictException;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisMock;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport redis.clients.jedis.commands.JedisCommands;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class RedisMetadataDAOTest {\n\n    private RedisMetadataDAO redisMetadataDAO;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void init() {\n        ConductorProperties conductorProperties = mock(ConductorProperties.class);\n        RedisProperties properties = mock(RedisProperties.class);\n        when(properties.getTaskDefCacheRefreshInterval()).thenReturn(Duration.ofSeconds(60));\n        JedisCommands jedisMock = new JedisMock();\n        JedisProxy jedisProxy = new JedisProxy(jedisMock);\n\n        redisMetadataDAO =\n                new RedisMetadataDAO(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Test(expected = ConflictException.class)\n    public void testDup() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"testDup\");\n        def.setVersion(1);\n\n        redisMetadataDAO.createWorkflowDef(def);\n        redisMetadataDAO.createWorkflowDef(def);\n    }\n\n    @Test\n    public void testWorkflowDefOperations() {\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n        def.setVersion(1);\n        def.setDescription(\"description\");\n        def.setCreatedBy(\"unit_test\");\n        def.setCreateTime(1L);\n        def.setOwnerApp(\"ownerApp\");\n        def.setUpdatedBy(\"unit_test2\");\n        def.setUpdateTime(2L);\n\n        redisMetadataDAO.createWorkflowDef(def);\n\n        List<WorkflowDef> all = redisMetadataDAO.getAllWorkflowDefs();\n        assertNotNull(all);\n        assertEquals(1, all.size());\n        assertEquals(\"test\", all.get(0).getName());\n        assertEquals(1, all.get(0).getVersion());\n\n        WorkflowDef found = redisMetadataDAO.getWorkflowDef(\"test\", 1).get();\n        assertEquals(def, found);\n\n        def.setVersion(2);\n        redisMetadataDAO.createWorkflowDef(def);\n\n        all = redisMetadataDAO.getAllWorkflowDefs();\n        assertNotNull(all);\n        assertEquals(2, all.size());\n        assertEquals(\"test\", all.get(0).getName());\n        assertEquals(1, all.get(0).getVersion());\n\n        found = redisMetadataDAO.getLatestWorkflowDef(def.getName()).get();\n        assertEquals(def.getName(), found.getName());\n        assertEquals(def.getVersion(), found.getVersion());\n        assertEquals(2, found.getVersion());\n\n        all = redisMetadataDAO.getAllVersions(def.getName());\n        assertNotNull(all);\n        assertEquals(2, all.size());\n        assertEquals(\"test\", all.get(0).getName());\n        assertEquals(\"test\", all.get(1).getName());\n        assertEquals(1, all.get(0).getVersion());\n        assertEquals(2, all.get(1).getVersion());\n\n        def.setDescription(\"updated\");\n        redisMetadataDAO.updateWorkflowDef(def);\n        found = redisMetadataDAO.getWorkflowDef(def.getName(), def.getVersion()).get();\n        assertEquals(def.getDescription(), found.getDescription());\n\n        List<String> allnames = redisMetadataDAO.findAll();\n        assertNotNull(allnames);\n        assertEquals(1, allnames.size());\n        assertEquals(def.getName(), allnames.get(0));\n\n        redisMetadataDAO.removeWorkflowDef(\"test\", 1);\n        Optional<WorkflowDef> deleted = redisMetadataDAO.getWorkflowDef(\"test\", 1);\n        assertFalse(deleted.isPresent());\n        redisMetadataDAO.removeWorkflowDef(\"test\", 2);\n        Optional<WorkflowDef> latestDef = redisMetadataDAO.getLatestWorkflowDef(\"test\");\n        assertFalse(latestDef.isPresent());\n\n        WorkflowDef[] workflowDefsArray = new WorkflowDef[3];\n        for (int i = 1; i <= 3; i++) {\n            workflowDefsArray[i - 1] = new WorkflowDef();\n            workflowDefsArray[i - 1].setName(\"test\");\n            workflowDefsArray[i - 1].setVersion(i);\n            workflowDefsArray[i - 1].setDescription(\"description\");\n            workflowDefsArray[i - 1].setCreatedBy(\"unit_test\");\n            workflowDefsArray[i - 1].setCreateTime(1L);\n            workflowDefsArray[i - 1].setOwnerApp(\"ownerApp\");\n            workflowDefsArray[i - 1].setUpdatedBy(\"unit_test2\");\n            workflowDefsArray[i - 1].setUpdateTime(2L);\n            redisMetadataDAO.createWorkflowDef(workflowDefsArray[i - 1]);\n        }\n        redisMetadataDAO.removeWorkflowDef(\"test\", 1);\n        redisMetadataDAO.removeWorkflowDef(\"test\", 2);\n        WorkflowDef workflow = redisMetadataDAO.getLatestWorkflowDef(\"test\").get();\n        assertEquals(workflow.getVersion(), 3);\n    }\n\n    @Test\n    public void testGetAllWorkflowDefsLatestVersions() {\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test1\");\n        def.setVersion(1);\n        def.setDescription(\"description\");\n        def.setCreatedBy(\"unit_test\");\n        def.setCreateTime(1L);\n        def.setOwnerApp(\"ownerApp\");\n        def.setUpdatedBy(\"unit_test2\");\n        def.setUpdateTime(2L);\n        redisMetadataDAO.createWorkflowDef(def);\n\n        def.setName(\"test2\");\n        redisMetadataDAO.createWorkflowDef(def);\n        def.setVersion(2);\n        redisMetadataDAO.createWorkflowDef(def);\n\n        def.setName(\"test3\");\n        def.setVersion(1);\n        redisMetadataDAO.createWorkflowDef(def);\n        def.setVersion(2);\n        redisMetadataDAO.createWorkflowDef(def);\n        def.setVersion(3);\n        redisMetadataDAO.createWorkflowDef(def);\n\n        // Placed the values in a map because they might not be stored in order of defName.\n        // To test, needed to confirm that the versions are correct for the definitions.\n        Map<String, WorkflowDef> allMap =\n                redisMetadataDAO.getAllWorkflowDefsLatestVersions().stream()\n                        .collect(Collectors.toMap(WorkflowDef::getName, Function.identity()));\n\n        assertNotNull(allMap);\n        assertEquals(3, allMap.size());\n        assertEquals(1, allMap.get(\"test1\").getVersion());\n        assertEquals(2, allMap.get(\"test2\").getVersion());\n        assertEquals(3, allMap.get(\"test3\").getVersion());\n    }\n\n    @Test(expected = NotFoundException.class)\n    public void removeInvalidWorkflowDef() {\n        redisMetadataDAO.removeWorkflowDef(\"hello\", 1);\n    }\n\n    @Test\n    public void testTaskDefOperations() {\n\n        TaskDef def = new TaskDef(\"taskA\");\n        def.setDescription(\"description\");\n        def.setCreatedBy(\"unit_test\");\n        def.setCreateTime(1L);\n        def.setInputKeys(Arrays.asList(\"a\", \"b\", \"c\"));\n        def.setOutputKeys(Arrays.asList(\"01\", \"o2\"));\n        def.setOwnerApp(\"ownerApp\");\n        def.setRetryCount(3);\n        def.setRetryDelaySeconds(100);\n        def.setRetryLogic(RetryLogic.FIXED);\n        def.setTimeoutPolicy(TimeoutPolicy.ALERT_ONLY);\n        def.setUpdatedBy(\"unit_test2\");\n        def.setUpdateTime(2L);\n        def.setRateLimitPerFrequency(50);\n        def.setRateLimitFrequencyInSeconds(1);\n\n        redisMetadataDAO.createTaskDef(def);\n\n        TaskDef found = redisMetadataDAO.getTaskDef(def.getName());\n        assertEquals(def, found);\n\n        def.setDescription(\"updated description\");\n        redisMetadataDAO.updateTaskDef(def);\n        found = redisMetadataDAO.getTaskDef(def.getName());\n        assertEquals(def, found);\n        assertEquals(\"updated description\", found.getDescription());\n\n        for (int i = 0; i < 9; i++) {\n            TaskDef tdf = new TaskDef(\"taskA\" + i);\n            redisMetadataDAO.createTaskDef(tdf);\n        }\n\n        List<TaskDef> all = redisMetadataDAO.getAllTaskDefs();\n        assertNotNull(all);\n        assertEquals(10, all.size());\n        Set<String> allnames = all.stream().map(TaskDef::getName).collect(Collectors.toSet());\n        assertEquals(10, allnames.size());\n        List<String> sorted = allnames.stream().sorted().collect(Collectors.toList());\n        assertEquals(def.getName(), sorted.get(0));\n\n        for (int i = 0; i < 9; i++) {\n            assertEquals(def.getName() + i, sorted.get(i + 1));\n        }\n\n        for (int i = 0; i < 9; i++) {\n            redisMetadataDAO.removeTaskDef(def.getName() + i);\n        }\n        all = redisMetadataDAO.getAllTaskDefs();\n        assertNotNull(all);\n        assertEquals(1, all.size());\n        assertEquals(def.getName(), all.get(0).getName());\n    }\n\n    @Test(expected = NotFoundException.class)\n    public void testRemoveTaskDef() {\n        redisMetadataDAO.removeTaskDef(\"test\" + UUID.randomUUID());\n    }\n\n    @Test\n    public void testDefaultsAreSetForResponseTimeout() {\n        TaskDef def = new TaskDef(\"taskA\");\n        def.setDescription(\"description\");\n        def.setCreatedBy(\"unit_test\");\n        def.setCreateTime(1L);\n        def.setInputKeys(Arrays.asList(\"a\", \"b\", \"c\"));\n        def.setOutputKeys(Arrays.asList(\"01\", \"o2\"));\n        def.setOwnerApp(\"ownerApp\");\n        def.setRetryCount(3);\n        def.setRetryDelaySeconds(100);\n        def.setRetryLogic(RetryLogic.FIXED);\n        def.setTimeoutPolicy(TimeoutPolicy.ALERT_ONLY);\n        def.setUpdatedBy(\"unit_test2\");\n        def.setUpdateTime(2L);\n        def.setRateLimitPerFrequency(50);\n        def.setRateLimitFrequencyInSeconds(1);\n        def.setResponseTimeoutSeconds(0);\n\n        redisMetadataDAO.createTaskDef(def);\n\n        TaskDef found = redisMetadataDAO.getTaskDef(def.getName());\n        assertEquals(found.getResponseTimeoutSeconds(), 3600);\n        found.setTimeoutSeconds(200);\n        found.setResponseTimeoutSeconds(0);\n        redisMetadataDAO.updateTaskDef(found);\n        TaskDef foundNew = redisMetadataDAO.getTaskDef(def.getName());\n        assertEquals(foundNew.getResponseTimeoutSeconds(), 199);\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/dao/RedisPollDataDAOTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport org.junit.Before;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.dao.PollDataDAO;\nimport com.netflix.conductor.dao.PollDataDAOTest;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisMock;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport redis.clients.jedis.commands.JedisCommands;\n\nimport static org.mockito.Mockito.mock;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class RedisPollDataDAOTest extends PollDataDAOTest {\n\n    private PollDataDAO redisPollDataDAO;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void init() {\n        ConductorProperties conductorProperties = mock(ConductorProperties.class);\n        RedisProperties properties = mock(RedisProperties.class);\n        JedisCommands jedisMock = new JedisMock();\n        JedisProxy jedisProxy = new JedisProxy(jedisMock);\n\n        redisPollDataDAO =\n                new RedisPollDataDAO(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Override\n    protected PollDataDAO getPollDataDAO() {\n        return redisPollDataDAO;\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/dao/RedisRateLimitDAOTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.dao;\n\nimport java.util.UUID;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.config.TestObjectMapperConfiguration;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.core.config.ConductorProperties;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.jedis.JedisMock;\nimport com.netflix.conductor.redis.jedis.JedisProxy;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport redis.clients.jedis.commands.JedisCommands;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.mock;\n\n@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})\n@RunWith(SpringRunner.class)\npublic class RedisRateLimitDAOTest {\n\n    private RedisRateLimitingDAO rateLimitingDao;\n\n    @Autowired private ObjectMapper objectMapper;\n\n    @Before\n    public void init() {\n        ConductorProperties conductorProperties = mock(ConductorProperties.class);\n        RedisProperties properties = mock(RedisProperties.class);\n        JedisCommands jedisMock = new JedisMock();\n        JedisProxy jedisProxy = new JedisProxy(jedisMock);\n\n        rateLimitingDao =\n                new RedisRateLimitingDAO(jedisProxy, objectMapper, conductorProperties, properties);\n    }\n\n    @Test\n    public void testExceedsRateLimitWhenNoRateLimitSet() {\n        TaskDef taskDef = new TaskDef(\"TestTaskDefinition\");\n        TaskModel task = new TaskModel();\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setTaskDefName(taskDef.getName());\n        assertFalse(rateLimitingDao.exceedsRateLimitPerFrequency(task, taskDef));\n    }\n\n    @Test\n    public void testExceedsRateLimitWithinLimit() {\n        TaskDef taskDef = new TaskDef(\"TestTaskDefinition\");\n        taskDef.setRateLimitFrequencyInSeconds(60);\n        taskDef.setRateLimitPerFrequency(20);\n        TaskModel task = new TaskModel();\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setTaskDefName(taskDef.getName());\n        assertFalse(rateLimitingDao.exceedsRateLimitPerFrequency(task, taskDef));\n    }\n\n    @Test\n    public void testExceedsRateLimitOutOfLimit() {\n        TaskDef taskDef = new TaskDef(\"TestTaskDefinition\");\n        taskDef.setRateLimitFrequencyInSeconds(60);\n        taskDef.setRateLimitPerFrequency(1);\n        TaskModel task = new TaskModel();\n        task.setTaskId(UUID.randomUUID().toString());\n        task.setTaskDefName(taskDef.getName());\n        assertFalse(rateLimitingDao.exceedsRateLimitPerFrequency(task, taskDef));\n        assertTrue(rateLimitingDao.exceedsRateLimitPerFrequency(task, taskDef));\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/jedis/ConfigurationHostSupplierTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.redis.config.RedisProperties;\nimport com.netflix.conductor.redis.dynoqueue.ConfigurationHostSupplier;\nimport com.netflix.dyno.connectionpool.Host;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class ConfigurationHostSupplierTest {\n\n    private RedisProperties properties;\n\n    private ConfigurationHostSupplier configurationHostSupplier;\n\n    @Before\n    public void setUp() {\n        properties = mock(RedisProperties.class);\n        configurationHostSupplier = new ConfigurationHostSupplier(properties);\n    }\n\n    @Test\n    public void getHost() {\n        when(properties.getHosts()).thenReturn(\"dyno1:8102:us-east-1c\");\n\n        List<Host> hosts = configurationHostSupplier.getHosts();\n        assertEquals(1, hosts.size());\n\n        Host firstHost = hosts.get(0);\n        assertEquals(\"dyno1\", firstHost.getHostName());\n        assertEquals(8102, firstHost.getPort());\n        assertEquals(\"us-east-1c\", firstHost.getRack());\n        assertTrue(firstHost.isUp());\n    }\n\n    @Test\n    public void getMultipleHosts() {\n        when(properties.getHosts()).thenReturn(\"dyno1:8102:us-east-1c;dyno2:8103:us-east-1c\");\n\n        List<Host> hosts = configurationHostSupplier.getHosts();\n        assertEquals(2, hosts.size());\n\n        Host firstHost = hosts.get(0);\n        assertEquals(\"dyno1\", firstHost.getHostName());\n        assertEquals(8102, firstHost.getPort());\n        assertEquals(\"us-east-1c\", firstHost.getRack());\n        assertTrue(firstHost.isUp());\n\n        Host secondHost = hosts.get(1);\n        assertEquals(\"dyno2\", secondHost.getHostName());\n        assertEquals(8103, secondHost.getPort());\n        assertEquals(\"us-east-1c\", secondHost.getRack());\n        assertTrue(secondHost.isUp());\n    }\n\n    @Test\n    public void getAuthenticatedHost() {\n        when(properties.getHosts()).thenReturn(\"redis1:6432:us-east-1c:password\");\n\n        List<Host> hosts = configurationHostSupplier.getHosts();\n        assertEquals(1, hosts.size());\n\n        Host firstHost = hosts.get(0);\n        assertEquals(\"redis1\", firstHost.getHostName());\n        assertEquals(6432, firstHost.getPort());\n        assertEquals(\"us-east-1c\", firstHost.getRack());\n        assertEquals(\"password\", firstHost.getPassword());\n        assertTrue(firstHost.isUp());\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/jedis/JedisClusterTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.AbstractMap;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\nimport redis.clients.jedis.GeoUnit;\nimport redis.clients.jedis.ListPosition;\nimport redis.clients.jedis.ScanParams;\nimport redis.clients.jedis.ScanResult;\nimport redis.clients.jedis.SortingParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class JedisClusterTest {\n\n    private final redis.clients.jedis.JedisCluster mockCluster =\n            mock(redis.clients.jedis.JedisCluster.class);\n    private final JedisCluster jedisCluster = new JedisCluster(mockCluster);\n\n    @Test\n    public void testSet() {\n        jedisCluster.set(\"key\", \"value\");\n        jedisCluster.set(\"key\", \"value\", SetParams.setParams());\n    }\n\n    @Test\n    public void testGet() {\n        jedisCluster.get(\"key\");\n    }\n\n    @Test\n    public void testExists() {\n        jedisCluster.exists(\"key\");\n    }\n\n    @Test\n    public void testPersist() {\n        jedisCluster.persist(\"key\");\n    }\n\n    @Test\n    public void testType() {\n        jedisCluster.type(\"key\");\n    }\n\n    @Test\n    public void testExpire() {\n        jedisCluster.expire(\"key\", 1337);\n    }\n\n    @Test\n    public void testPexpire() {\n        jedisCluster.pexpire(\"key\", 1337);\n    }\n\n    @Test\n    public void testExpireAt() {\n        jedisCluster.expireAt(\"key\", 1337);\n    }\n\n    @Test\n    public void testPexpireAt() {\n        jedisCluster.pexpireAt(\"key\", 1337);\n    }\n\n    @Test\n    public void testTtl() {\n        jedisCluster.ttl(\"key\");\n    }\n\n    @Test\n    public void testPttl() {\n        jedisCluster.pttl(\"key\");\n    }\n\n    @Test\n    public void testSetbit() {\n        jedisCluster.setbit(\"key\", 1337, \"value\");\n        jedisCluster.setbit(\"key\", 1337, true);\n    }\n\n    @Test\n    public void testGetbit() {\n        jedisCluster.getbit(\"key\", 1337);\n    }\n\n    @Test\n    public void testSetrange() {\n        jedisCluster.setrange(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testGetrange() {\n        jedisCluster.getrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testGetSet() {\n        jedisCluster.getSet(\"key\", \"value\");\n    }\n\n    @Test\n    public void testSetnx() {\n        jedisCluster.setnx(\"test\", \"value\");\n    }\n\n    @Test\n    public void testSetex() {\n        jedisCluster.setex(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testPsetex() {\n        jedisCluster.psetex(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testDecrBy() {\n        jedisCluster.decrBy(\"key\", 1337);\n    }\n\n    @Test\n    public void testDecr() {\n        jedisCluster.decr(\"key\");\n    }\n\n    @Test\n    public void testIncrBy() {\n        jedisCluster.incrBy(\"key\", 1337);\n    }\n\n    @Test\n    public void testIncrByFloat() {\n        jedisCluster.incrByFloat(\"key\", 1337);\n    }\n\n    @Test\n    public void testIncr() {\n        jedisCluster.incr(\"key\");\n    }\n\n    @Test\n    public void testAppend() {\n        jedisCluster.append(\"key\", \"value\");\n    }\n\n    @Test\n    public void testSubstr() {\n        jedisCluster.substr(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testHset() {\n        jedisCluster.hset(\"key\", \"field\", \"value\");\n    }\n\n    @Test\n    public void testHget() {\n        jedisCluster.hget(\"key\", \"field\");\n    }\n\n    @Test\n    public void testHsetnx() {\n        jedisCluster.hsetnx(\"key\", \"field\", \"value\");\n    }\n\n    @Test\n    public void testHmset() {\n        jedisCluster.hmset(\"key\", new HashMap<>());\n    }\n\n    @Test\n    public void testHmget() {\n        jedisCluster.hmget(\"key\", \"fields\");\n    }\n\n    @Test\n    public void testHincrBy() {\n        jedisCluster.hincrBy(\"key\", \"field\", 1337);\n    }\n\n    @Test\n    public void testHincrByFloat() {\n        jedisCluster.hincrByFloat(\"key\", \"field\", 1337);\n    }\n\n    @Test\n    public void testHexists() {\n        jedisCluster.hexists(\"key\", \"field\");\n    }\n\n    @Test\n    public void testHdel() {\n        jedisCluster.hdel(\"key\", \"field\");\n    }\n\n    @Test\n    public void testHlen() {\n        jedisCluster.hlen(\"key\");\n    }\n\n    @Test\n    public void testHkeys() {\n        jedisCluster.hkeys(\"key\");\n    }\n\n    @Test\n    public void testHvals() {\n        jedisCluster.hvals(\"key\");\n    }\n\n    @Test\n    public void testGgetAll() {\n        jedisCluster.hgetAll(\"key\");\n    }\n\n    @Test\n    public void testRpush() {\n        jedisCluster.rpush(\"key\", \"string\");\n    }\n\n    @Test\n    public void testLpush() {\n        jedisCluster.lpush(\"key\", \"string\");\n    }\n\n    @Test\n    public void testLlen() {\n        jedisCluster.llen(\"key\");\n    }\n\n    @Test\n    public void testLrange() {\n        jedisCluster.lrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testLtrim() {\n        jedisCluster.ltrim(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testLindex() {\n        jedisCluster.lindex(\"key\", 1337);\n    }\n\n    @Test\n    public void testLset() {\n        jedisCluster.lset(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testLrem() {\n        jedisCluster.lrem(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testLpop() {\n        jedisCluster.lpop(\"key\");\n    }\n\n    @Test\n    public void testRpop() {\n        jedisCluster.rpop(\"key\");\n    }\n\n    @Test\n    public void testSadd() {\n        jedisCluster.sadd(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSmembers() {\n        jedisCluster.smembers(\"key\");\n    }\n\n    @Test\n    public void testSrem() {\n        jedisCluster.srem(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSpop() {\n        jedisCluster.spop(\"key\");\n        jedisCluster.spop(\"key\", 1337);\n    }\n\n    @Test\n    public void testScard() {\n        jedisCluster.scard(\"key\");\n    }\n\n    @Test\n    public void testSismember() {\n        jedisCluster.sismember(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSrandmember() {\n        jedisCluster.srandmember(\"key\");\n        jedisCluster.srandmember(\"key\", 1337);\n    }\n\n    @Test\n    public void testStrlen() {\n        jedisCluster.strlen(\"key\");\n    }\n\n    @Test\n    public void testZadd() {\n        jedisCluster.zadd(\"key\", new HashMap<>());\n        jedisCluster.zadd(\"key\", new HashMap<>(), ZAddParams.zAddParams());\n        jedisCluster.zadd(\"key\", 1337, \"members\");\n        jedisCluster.zadd(\"key\", 1337, \"members\", ZAddParams.zAddParams());\n    }\n\n    @Test\n    public void testZrange() {\n        jedisCluster.zrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrem() {\n        jedisCluster.zrem(\"key\", \"member\");\n    }\n\n    @Test\n    public void testZincrby() {\n        jedisCluster.zincrby(\"key\", 1337, \"member\");\n        jedisCluster.zincrby(\"key\", 1337, \"member\", ZIncrByParams.zIncrByParams());\n    }\n\n    @Test\n    public void testZrank() {\n        jedisCluster.zrank(\"key\", \"member\");\n    }\n\n    @Test\n    public void testZrevrank() {\n        jedisCluster.zrevrank(\"key\", \"member\");\n    }\n\n    @Test\n    public void testZrevrange() {\n        jedisCluster.zrevrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrangeWithScores() {\n        jedisCluster.zrangeWithScores(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrevrangeWithScores() {\n        jedisCluster.zrevrangeWithScores(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZcard() {\n        jedisCluster.zcard(\"key\");\n    }\n\n    @Test\n    public void testZscore() {\n        jedisCluster.zscore(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSort() {\n        jedisCluster.sort(\"key\");\n        jedisCluster.sort(\"key\", new SortingParams());\n    }\n\n    @Test\n    public void testZcount() {\n        jedisCluster.zcount(\"key\", \"min\", \"max\");\n        jedisCluster.zcount(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrangeByScore() {\n        jedisCluster.zrangeByScore(\"key\", \"min\", \"max\");\n        jedisCluster.zrangeByScore(\"key\", 1337, 1338);\n        jedisCluster.zrangeByScore(\"key\", \"min\", \"max\", 1337, 1338);\n        jedisCluster.zrangeByScore(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZrevrangeByScore() {\n        jedisCluster.zrevrangeByScore(\"key\", \"max\", \"min\");\n        jedisCluster.zrevrangeByScore(\"key\", 1337, 1338);\n        jedisCluster.zrevrangeByScore(\"key\", \"max\", \"min\", 1337, 1338);\n        jedisCluster.zrevrangeByScore(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZrangeByScoreWithScores() {\n        jedisCluster.zrangeByScoreWithScores(\"key\", \"min\", \"max\");\n        jedisCluster.zrangeByScoreWithScores(\"key\", \"min\", \"max\", 1337, 1338);\n        jedisCluster.zrangeByScoreWithScores(\"key\", 1337, 1338);\n        jedisCluster.zrangeByScoreWithScores(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZrevrangeByScoreWithScores() {\n        jedisCluster.zrevrangeByScoreWithScores(\"key\", \"max\", \"min\");\n        jedisCluster.zrevrangeByScoreWithScores(\"key\", \"max\", \"min\", 1337, 1338);\n        jedisCluster.zrevrangeByScoreWithScores(\"key\", 1337, 1338);\n        jedisCluster.zrevrangeByScoreWithScores(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZremrangeByRank() {\n        jedisCluster.zremrangeByRank(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZremrangeByScore() {\n        jedisCluster.zremrangeByScore(\"key\", \"start\", \"end\");\n        jedisCluster.zremrangeByScore(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZlexcount() {\n        jedisCluster.zlexcount(\"key\", \"min\", \"max\");\n    }\n\n    @Test\n    public void testZrangeByLex() {\n        jedisCluster.zrangeByLex(\"key\", \"min\", \"max\");\n        jedisCluster.zrangeByLex(\"key\", \"min\", \"max\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrevrangeByLex() {\n        jedisCluster.zrevrangeByLex(\"key\", \"max\", \"min\");\n        jedisCluster.zrevrangeByLex(\"key\", \"max\", \"min\", 1337, 1338);\n    }\n\n    @Test\n    public void testZremrangeByLex() {\n        jedisCluster.zremrangeByLex(\"key\", \"min\", \"max\");\n    }\n\n    @Test\n    public void testLinsert() {\n        jedisCluster.linsert(\"key\", ListPosition.AFTER, \"pivot\", \"value\");\n    }\n\n    @Test\n    public void testLpushx() {\n        jedisCluster.lpushx(\"key\", \"string\");\n    }\n\n    @Test\n    public void testRpushx() {\n        jedisCluster.rpushx(\"key\", \"string\");\n    }\n\n    @Test\n    public void testBlpop() {\n        jedisCluster.blpop(1337, \"arg\");\n    }\n\n    @Test\n    public void testBrpop() {\n        jedisCluster.brpop(1337, \"arg\");\n    }\n\n    @Test\n    public void testDel() {\n        jedisCluster.del(\"key\");\n    }\n\n    @Test\n    public void testEcho() {\n        jedisCluster.echo(\"string\");\n    }\n\n    @Test(expected = UnsupportedOperationException.class)\n    public void testMove() {\n        jedisCluster.move(\"key\", 1337);\n    }\n\n    @Test\n    public void testBitcount() {\n        jedisCluster.bitcount(\"key\");\n        jedisCluster.bitcount(\"key\", 1337, 1338);\n    }\n\n    @Test(expected = UnsupportedOperationException.class)\n    public void testBitpos() {\n        jedisCluster.bitpos(\"key\", true);\n    }\n\n    @Test\n    public void testHscan() {\n        jedisCluster.hscan(\"key\", \"cursor\");\n\n        ScanResult<Entry<byte[], byte[]>> scanResult =\n                new ScanResult<>(\n                        \"cursor\".getBytes(),\n                        Arrays.asList(\n                                new AbstractMap.SimpleEntry<>(\"key1\".getBytes(), \"val1\".getBytes()),\n                                new AbstractMap.SimpleEntry<>(\n                                        \"key2\".getBytes(), \"val2\".getBytes())));\n\n        when(mockCluster.hscan(Mockito.any(), Mockito.any(), Mockito.any(ScanParams.class)))\n                .thenReturn(scanResult);\n        ScanResult<Map.Entry<String, String>> result =\n                jedisCluster.hscan(\"key\", \"cursor\", new ScanParams());\n\n        assertEquals(\"cursor\", result.getCursor());\n        assertEquals(2, result.getResult().size());\n        assertEquals(\"val1\", result.getResult().get(0).getValue());\n    }\n\n    @Test\n    public void testSscan() {\n        jedisCluster.sscan(\"key\", \"cursor\");\n\n        ScanResult<byte[]> scanResult =\n                new ScanResult<>(\n                        \"sscursor\".getBytes(), Arrays.asList(\"val1\".getBytes(), \"val2\".getBytes()));\n\n        when(mockCluster.sscan(Mockito.any(), Mockito.any(), Mockito.any(ScanParams.class)))\n                .thenReturn(scanResult);\n\n        ScanResult<String> result = jedisCluster.sscan(\"key\", \"cursor\", new ScanParams());\n        assertEquals(\"sscursor\", result.getCursor());\n        assertEquals(2, result.getResult().size());\n        assertEquals(\"val1\", result.getResult().get(0));\n    }\n\n    @Test\n    public void testZscan() {\n        jedisCluster.zscan(\"key\", \"cursor\");\n        jedisCluster.zscan(\"key\", \"cursor\", new ScanParams());\n    }\n\n    @Test\n    public void testPfadd() {\n        jedisCluster.pfadd(\"key\", \"elements\");\n    }\n\n    @Test\n    public void testPfcount() {\n        jedisCluster.pfcount(\"key\");\n    }\n\n    @Test\n    public void testGeoadd() {\n        jedisCluster.geoadd(\"key\", new HashMap<>());\n        jedisCluster.geoadd(\"key\", 1337, 1338, \"member\");\n    }\n\n    @Test\n    public void testGeodist() {\n        jedisCluster.geodist(\"key\", \"member1\", \"member2\");\n        jedisCluster.geodist(\"key\", \"member1\", \"member2\", GeoUnit.KM);\n    }\n\n    @Test\n    public void testGeohash() {\n        jedisCluster.geohash(\"key\", \"members\");\n    }\n\n    @Test\n    public void testGeopos() {\n        jedisCluster.geopos(\"key\", \"members\");\n    }\n\n    @Test\n    public void testGeoradius() {\n        jedisCluster.georadius(\"key\", 1337, 1338, 32, GeoUnit.KM);\n        jedisCluster.georadius(\"key\", 1337, 1338, 32, GeoUnit.KM, GeoRadiusParam.geoRadiusParam());\n    }\n\n    @Test\n    public void testGeoradiusByMember() {\n        jedisCluster.georadiusByMember(\"key\", \"member\", 1337, GeoUnit.KM);\n        jedisCluster.georadiusByMember(\n                \"key\", \"member\", 1337, GeoUnit.KM, GeoRadiusParam.geoRadiusParam());\n    }\n\n    @Test\n    public void testBitfield() {\n        jedisCluster.bitfield(\"key\", \"arguments\");\n    }\n}\n"
  },
  {
    "path": "redis-persistence/src/test/java/com/netflix/conductor/redis/jedis/JedisSentinelTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.redis.jedis;\n\nimport java.util.HashMap;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport redis.clients.jedis.GeoUnit;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisSentinelPool;\nimport redis.clients.jedis.ListPosition;\nimport redis.clients.jedis.ScanParams;\nimport redis.clients.jedis.SortingParams;\nimport redis.clients.jedis.params.GeoRadiusParam;\nimport redis.clients.jedis.params.SetParams;\nimport redis.clients.jedis.params.ZAddParams;\nimport redis.clients.jedis.params.ZIncrByParams;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class JedisSentinelTest {\n\n    private final Jedis jedis = mock(Jedis.class);\n    private final JedisSentinelPool jedisPool = mock(JedisSentinelPool.class);\n    private final JedisSentinel jedisSentinel = new JedisSentinel(jedisPool);\n\n    @Before\n    public void init() {\n        when(this.jedisPool.getResource()).thenReturn(this.jedis);\n    }\n\n    @Test\n    public void testSet() {\n        jedisSentinel.set(\"key\", \"value\");\n        jedisSentinel.set(\"key\", \"value\", SetParams.setParams());\n    }\n\n    @Test\n    public void testGet() {\n        jedisSentinel.get(\"key\");\n    }\n\n    @Test\n    public void testExists() {\n        jedisSentinel.exists(\"key\");\n    }\n\n    @Test\n    public void testPersist() {\n        jedisSentinel.persist(\"key\");\n    }\n\n    @Test\n    public void testType() {\n        jedisSentinel.type(\"key\");\n    }\n\n    @Test\n    public void testExpire() {\n        jedisSentinel.expire(\"key\", 1337);\n    }\n\n    @Test\n    public void testPexpire() {\n        jedisSentinel.pexpire(\"key\", 1337);\n    }\n\n    @Test\n    public void testExpireAt() {\n        jedisSentinel.expireAt(\"key\", 1337);\n    }\n\n    @Test\n    public void testPexpireAt() {\n        jedisSentinel.pexpireAt(\"key\", 1337);\n    }\n\n    @Test\n    public void testTtl() {\n        jedisSentinel.ttl(\"key\");\n    }\n\n    @Test\n    public void testPttl() {\n        jedisSentinel.pttl(\"key\");\n    }\n\n    @Test\n    public void testSetbit() {\n        jedisSentinel.setbit(\"key\", 1337, \"value\");\n        jedisSentinel.setbit(\"key\", 1337, true);\n    }\n\n    @Test\n    public void testGetbit() {\n        jedisSentinel.getbit(\"key\", 1337);\n    }\n\n    @Test\n    public void testSetrange() {\n        jedisSentinel.setrange(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testGetrange() {\n        jedisSentinel.getrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testGetSet() {\n        jedisSentinel.getSet(\"key\", \"value\");\n    }\n\n    @Test\n    public void testSetnx() {\n        jedisSentinel.setnx(\"test\", \"value\");\n    }\n\n    @Test\n    public void testSetex() {\n        jedisSentinel.setex(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testPsetex() {\n        jedisSentinel.psetex(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testDecrBy() {\n        jedisSentinel.decrBy(\"key\", 1337);\n    }\n\n    @Test\n    public void testDecr() {\n        jedisSentinel.decr(\"key\");\n    }\n\n    @Test\n    public void testIncrBy() {\n        jedisSentinel.incrBy(\"key\", 1337);\n    }\n\n    @Test\n    public void testIncrByFloat() {\n        jedisSentinel.incrByFloat(\"key\", 1337);\n    }\n\n    @Test\n    public void testIncr() {\n        jedisSentinel.incr(\"key\");\n    }\n\n    @Test\n    public void testAppend() {\n        jedisSentinel.append(\"key\", \"value\");\n    }\n\n    @Test\n    public void testSubstr() {\n        jedisSentinel.substr(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testHset() {\n        jedisSentinel.hset(\"key\", \"field\", \"value\");\n    }\n\n    @Test\n    public void testHget() {\n        jedisSentinel.hget(\"key\", \"field\");\n    }\n\n    @Test\n    public void testHsetnx() {\n        jedisSentinel.hsetnx(\"key\", \"field\", \"value\");\n    }\n\n    @Test\n    public void testHmset() {\n        jedisSentinel.hmset(\"key\", new HashMap<>());\n    }\n\n    @Test\n    public void testHmget() {\n        jedisSentinel.hmget(\"key\", \"fields\");\n    }\n\n    @Test\n    public void testHincrBy() {\n        jedisSentinel.hincrBy(\"key\", \"field\", 1337);\n    }\n\n    @Test\n    public void testHincrByFloat() {\n        jedisSentinel.hincrByFloat(\"key\", \"field\", 1337);\n    }\n\n    @Test\n    public void testHexists() {\n        jedisSentinel.hexists(\"key\", \"field\");\n    }\n\n    @Test\n    public void testHdel() {\n        jedisSentinel.hdel(\"key\", \"field\");\n    }\n\n    @Test\n    public void testHlen() {\n        jedisSentinel.hlen(\"key\");\n    }\n\n    @Test\n    public void testHkeys() {\n        jedisSentinel.hkeys(\"key\");\n    }\n\n    @Test\n    public void testHvals() {\n        jedisSentinel.hvals(\"key\");\n    }\n\n    @Test\n    public void testGgetAll() {\n        jedisSentinel.hgetAll(\"key\");\n    }\n\n    @Test\n    public void testRpush() {\n        jedisSentinel.rpush(\"key\", \"string\");\n    }\n\n    @Test\n    public void testLpush() {\n        jedisSentinel.lpush(\"key\", \"string\");\n    }\n\n    @Test\n    public void testLlen() {\n        jedisSentinel.llen(\"key\");\n    }\n\n    @Test\n    public void testLrange() {\n        jedisSentinel.lrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testLtrim() {\n        jedisSentinel.ltrim(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testLindex() {\n        jedisSentinel.lindex(\"key\", 1337);\n    }\n\n    @Test\n    public void testLset() {\n        jedisSentinel.lset(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testLrem() {\n        jedisSentinel.lrem(\"key\", 1337, \"value\");\n    }\n\n    @Test\n    public void testLpop() {\n        jedisSentinel.lpop(\"key\");\n    }\n\n    @Test\n    public void testRpop() {\n        jedisSentinel.rpop(\"key\");\n    }\n\n    @Test\n    public void testSadd() {\n        jedisSentinel.sadd(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSmembers() {\n        jedisSentinel.smembers(\"key\");\n    }\n\n    @Test\n    public void testSrem() {\n        jedisSentinel.srem(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSpop() {\n        jedisSentinel.spop(\"key\");\n        jedisSentinel.spop(\"key\", 1337);\n    }\n\n    @Test\n    public void testScard() {\n        jedisSentinel.scard(\"key\");\n    }\n\n    @Test\n    public void testSismember() {\n        jedisSentinel.sismember(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSrandmember() {\n        jedisSentinel.srandmember(\"key\");\n        jedisSentinel.srandmember(\"key\", 1337);\n    }\n\n    @Test\n    public void testStrlen() {\n        jedisSentinel.strlen(\"key\");\n    }\n\n    @Test\n    public void testZadd() {\n        jedisSentinel.zadd(\"key\", new HashMap<>());\n        jedisSentinel.zadd(\"key\", new HashMap<>(), ZAddParams.zAddParams());\n        jedisSentinel.zadd(\"key\", 1337, \"members\");\n        jedisSentinel.zadd(\"key\", 1337, \"members\", ZAddParams.zAddParams());\n    }\n\n    @Test\n    public void testZrange() {\n        jedisSentinel.zrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrem() {\n        jedisSentinel.zrem(\"key\", \"member\");\n    }\n\n    @Test\n    public void testZincrby() {\n        jedisSentinel.zincrby(\"key\", 1337, \"member\");\n        jedisSentinel.zincrby(\"key\", 1337, \"member\", ZIncrByParams.zIncrByParams());\n    }\n\n    @Test\n    public void testZrank() {\n        jedisSentinel.zrank(\"key\", \"member\");\n    }\n\n    @Test\n    public void testZrevrank() {\n        jedisSentinel.zrevrank(\"key\", \"member\");\n    }\n\n    @Test\n    public void testZrevrange() {\n        jedisSentinel.zrevrange(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrangeWithScores() {\n        jedisSentinel.zrangeWithScores(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrevrangeWithScores() {\n        jedisSentinel.zrevrangeWithScores(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZcard() {\n        jedisSentinel.zcard(\"key\");\n    }\n\n    @Test\n    public void testZscore() {\n        jedisSentinel.zscore(\"key\", \"member\");\n    }\n\n    @Test\n    public void testSort() {\n        jedisSentinel.sort(\"key\");\n        jedisSentinel.sort(\"key\", new SortingParams());\n    }\n\n    @Test\n    public void testZcount() {\n        jedisSentinel.zcount(\"key\", \"min\", \"max\");\n        jedisSentinel.zcount(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrangeByScore() {\n        jedisSentinel.zrangeByScore(\"key\", \"min\", \"max\");\n        jedisSentinel.zrangeByScore(\"key\", 1337, 1338);\n        jedisSentinel.zrangeByScore(\"key\", \"min\", \"max\", 1337, 1338);\n        jedisSentinel.zrangeByScore(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZrevrangeByScore() {\n        jedisSentinel.zrevrangeByScore(\"key\", \"max\", \"min\");\n        jedisSentinel.zrevrangeByScore(\"key\", 1337, 1338);\n        jedisSentinel.zrevrangeByScore(\"key\", \"max\", \"min\", 1337, 1338);\n        jedisSentinel.zrevrangeByScore(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZrangeByScoreWithScores() {\n        jedisSentinel.zrangeByScoreWithScores(\"key\", \"min\", \"max\");\n        jedisSentinel.zrangeByScoreWithScores(\"key\", \"min\", \"max\", 1337, 1338);\n        jedisSentinel.zrangeByScoreWithScores(\"key\", 1337, 1338);\n        jedisSentinel.zrangeByScoreWithScores(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZrevrangeByScoreWithScores() {\n        jedisSentinel.zrevrangeByScoreWithScores(\"key\", \"max\", \"min\");\n        jedisSentinel.zrevrangeByScoreWithScores(\"key\", \"max\", \"min\", 1337, 1338);\n        jedisSentinel.zrevrangeByScoreWithScores(\"key\", 1337, 1338);\n        jedisSentinel.zrevrangeByScoreWithScores(\"key\", 1337, 1338, 1339, 1340);\n    }\n\n    @Test\n    public void testZremrangeByRank() {\n        jedisSentinel.zremrangeByRank(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZremrangeByScore() {\n        jedisSentinel.zremrangeByScore(\"key\", \"start\", \"end\");\n        jedisSentinel.zremrangeByScore(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testZlexcount() {\n        jedisSentinel.zlexcount(\"key\", \"min\", \"max\");\n    }\n\n    @Test\n    public void testZrangeByLex() {\n        jedisSentinel.zrangeByLex(\"key\", \"min\", \"max\");\n        jedisSentinel.zrangeByLex(\"key\", \"min\", \"max\", 1337, 1338);\n    }\n\n    @Test\n    public void testZrevrangeByLex() {\n        jedisSentinel.zrevrangeByLex(\"key\", \"max\", \"min\");\n        jedisSentinel.zrevrangeByLex(\"key\", \"max\", \"min\", 1337, 1338);\n    }\n\n    @Test\n    public void testZremrangeByLex() {\n        jedisSentinel.zremrangeByLex(\"key\", \"min\", \"max\");\n    }\n\n    @Test\n    public void testLinsert() {\n        jedisSentinel.linsert(\"key\", ListPosition.AFTER, \"pivot\", \"value\");\n    }\n\n    @Test\n    public void testLpushx() {\n        jedisSentinel.lpushx(\"key\", \"string\");\n    }\n\n    @Test\n    public void testRpushx() {\n        jedisSentinel.rpushx(\"key\", \"string\");\n    }\n\n    @Test\n    public void testBlpop() {\n        jedisSentinel.blpop(1337, \"arg\");\n    }\n\n    @Test\n    public void testBrpop() {\n        jedisSentinel.brpop(1337, \"arg\");\n    }\n\n    @Test\n    public void testDel() {\n        jedisSentinel.del(\"key\");\n    }\n\n    @Test\n    public void testEcho() {\n        jedisSentinel.echo(\"string\");\n    }\n\n    @Test\n    public void testMove() {\n        jedisSentinel.move(\"key\", 1337);\n    }\n\n    @Test\n    public void testBitcount() {\n        jedisSentinel.bitcount(\"key\");\n        jedisSentinel.bitcount(\"key\", 1337, 1338);\n    }\n\n    @Test\n    public void testBitpos() {\n        jedisSentinel.bitpos(\"key\", true);\n    }\n\n    @Test\n    public void testHscan() {\n        jedisSentinel.hscan(\"key\", \"cursor\");\n        jedisSentinel.hscan(\"key\", \"cursor\", new ScanParams());\n    }\n\n    @Test\n    public void testSscan() {\n        jedisSentinel.sscan(\"key\", \"cursor\");\n        jedisSentinel.sscan(\"key\", \"cursor\", new ScanParams());\n    }\n\n    @Test\n    public void testZscan() {\n        jedisSentinel.zscan(\"key\", \"cursor\");\n        jedisSentinel.zscan(\"key\", \"cursor\", new ScanParams());\n    }\n\n    @Test\n    public void testPfadd() {\n        jedisSentinel.pfadd(\"key\", \"elements\");\n    }\n\n    @Test\n    public void testPfcount() {\n        jedisSentinel.pfcount(\"key\");\n    }\n\n    @Test\n    public void testGeoadd() {\n        jedisSentinel.geoadd(\"key\", new HashMap<>());\n        jedisSentinel.geoadd(\"key\", 1337, 1338, \"member\");\n    }\n\n    @Test\n    public void testGeodist() {\n        jedisSentinel.geodist(\"key\", \"member1\", \"member2\");\n        jedisSentinel.geodist(\"key\", \"member1\", \"member2\", GeoUnit.KM);\n    }\n\n    @Test\n    public void testGeohash() {\n        jedisSentinel.geohash(\"key\", \"members\");\n    }\n\n    @Test\n    public void testGeopos() {\n        jedisSentinel.geopos(\"key\", \"members\");\n    }\n\n    @Test\n    public void testGeoradius() {\n        jedisSentinel.georadius(\"key\", 1337, 1338, 32, GeoUnit.KM);\n        jedisSentinel.georadius(\"key\", 1337, 1338, 32, GeoUnit.KM, GeoRadiusParam.geoRadiusParam());\n    }\n\n    @Test\n    public void testGeoradiusByMember() {\n        jedisSentinel.georadiusByMember(\"key\", \"member\", 1337, GeoUnit.KM);\n        jedisSentinel.georadiusByMember(\n                \"key\", \"member\", 1337, GeoUnit.KM, GeoRadiusParam.geoRadiusParam());\n    }\n\n    @Test\n    public void testBitfield() {\n        jedisSentinel.bitfield(\"key\", \"arguments\");\n    }\n}\n"
  },
  {
    "path": "rest/build.gradle",
    "content": "dependencies {\n\n    implementation project(':conductor-common')\n    implementation project(':conductor-core')\n\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n\n    implementation \"com.netflix.runtime:health-api:${revHealth}\"\n\n    implementation \"org.springdoc:springdoc-openapi-ui:${revOpenapi}\"\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/config/RequestMappingConstants.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.config;\n\npublic interface RequestMappingConstants {\n\n    String API_PREFIX = \"/api/\";\n\n    String ADMIN = API_PREFIX + \"admin\";\n    String EVENT = API_PREFIX + \"event\";\n    String METADATA = API_PREFIX + \"metadata\";\n    String QUEUE = API_PREFIX + \"queue\";\n    String TASKS = API_PREFIX + \"tasks\";\n    String WORKFLOW_BULK = API_PREFIX + \"workflow/bulk\";\n    String WORKFLOW = API_PREFIX + \"workflow\";\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/config/RestConfiguration.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport static org.springframework.http.MediaType.APPLICATION_JSON;\nimport static org.springframework.http.MediaType.TEXT_PLAIN;\n\n@Configuration\npublic class RestConfiguration implements WebMvcConfigurer {\n\n    /**\n     * Disable all 3 (Accept header, url parameter, path extension) strategies of content\n     * negotiation and only allow <code>application/json</code> and <code>text/plain</code> types.\n     * <br>\n     *\n     * <p>Any \"mapping\" that is annotated with <code>produces=TEXT_PLAIN_VALUE</code> will be sent\n     * as <code>text/plain</code> all others as <code>application/json</code>.<br>\n     * More details on Spring MVC content negotiation can be found at <a\n     * href=\"https://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc\">https://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc</a>\n     * <br>\n     */\n    @Override\n    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {\n        configurer\n                .favorParameter(false)\n                .favorPathExtension(false)\n                .ignoreAcceptHeader(true)\n                .defaultContentType(APPLICATION_JSON, TEXT_PLAIN);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/AdminResource.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.service.AdminService;\n\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport static com.netflix.conductor.rest.config.RequestMappingConstants.ADMIN;\n\nimport static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;\n\n@RestController\n@RequestMapping(ADMIN)\npublic class AdminResource {\n\n    private final AdminService adminService;\n\n    public AdminResource(AdminService adminService) {\n        this.adminService = adminService;\n    }\n\n    @Operation(summary = \"Get all the configuration parameters\")\n    @GetMapping(\"/config\")\n    public Map<String, Object> getAllConfig() {\n        return adminService.getAllConfig();\n    }\n\n    @GetMapping(\"/task/{tasktype}\")\n    @Operation(summary = \"Get the list of pending tasks for a given task type\")\n    public List<Task> view(\n            @PathVariable(\"tasktype\") String taskType,\n            @RequestParam(value = \"start\", defaultValue = \"0\", required = false) int start,\n            @RequestParam(value = \"count\", defaultValue = \"100\", required = false) int count) {\n        return adminService.getListOfPendingTask(taskType, start, count);\n    }\n\n    @PostMapping(value = \"/sweep/requeue/{workflowId}\", produces = TEXT_PLAIN_VALUE)\n    @Operation(summary = \"Queue up all the running workflows for sweep\")\n    public String requeueSweep(@PathVariable(\"workflowId\") String workflowId) {\n        return adminService.requeueSweep(workflowId);\n    }\n\n    @PostMapping(value = \"/consistency/verifyAndRepair/{workflowId}\", produces = TEXT_PLAIN_VALUE)\n    @Operation(summary = \"Verify and repair workflow consistency\")\n    public String verifyAndRepairWorkflowConsistency(\n            @PathVariable(\"workflowId\") String workflowId) {\n        return String.valueOf(adminService.verifyAndRepairWorkflowConsistency(workflowId));\n    }\n\n    @GetMapping(\"/queues\")\n    @Operation(summary = \"Get registered queues\")\n    public Map<String, ?> getEventQueues(\n            @RequestParam(value = \"verbose\", defaultValue = \"false\", required = false)\n                    boolean verbose) {\n        return adminService.getEventQueues(verbose);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/ApplicationExceptionMapper.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.servlet.http.HttpServletRequest;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport com.netflix.conductor.common.validation.ErrorResponse;\nimport com.netflix.conductor.core.exception.ConflictException;\nimport com.netflix.conductor.core.exception.NotFoundException;\nimport com.netflix.conductor.core.exception.TransientException;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.metrics.Monitors;\n\nimport com.fasterxml.jackson.databind.exc.InvalidFormatException;\n\n@RestControllerAdvice\n@Order(ValidationExceptionMapper.ORDER + 1)\npublic class ApplicationExceptionMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationExceptionMapper.class);\n\n    private final String host = Utils.getServerId();\n\n    private static final Map<Class<? extends Throwable>, HttpStatus> EXCEPTION_STATUS_MAP =\n            new HashMap<>();\n\n    static {\n        EXCEPTION_STATUS_MAP.put(NotFoundException.class, HttpStatus.NOT_FOUND);\n        EXCEPTION_STATUS_MAP.put(ConflictException.class, HttpStatus.CONFLICT);\n        EXCEPTION_STATUS_MAP.put(IllegalArgumentException.class, HttpStatus.BAD_REQUEST);\n        EXCEPTION_STATUS_MAP.put(InvalidFormatException.class, HttpStatus.INTERNAL_SERVER_ERROR);\n    }\n\n    @ExceptionHandler(Throwable.class)\n    public ResponseEntity<ErrorResponse> handleAll(HttpServletRequest request, Throwable th) {\n        logException(request, th);\n\n        HttpStatus status =\n                EXCEPTION_STATUS_MAP.getOrDefault(th.getClass(), HttpStatus.INTERNAL_SERVER_ERROR);\n\n        ErrorResponse errorResponse = new ErrorResponse();\n        errorResponse.setInstance(host);\n        errorResponse.setStatus(status.value());\n        errorResponse.setMessage(th.getMessage());\n        errorResponse.setRetryable(\n                th instanceof TransientException); // set it to true for TransientException\n\n        Monitors.error(\"error\", String.valueOf(status.value()));\n\n        return new ResponseEntity<>(errorResponse, status);\n    }\n\n    private void logException(HttpServletRequest request, Throwable exception) {\n        LOGGER.error(\n                \"Error {} url: '{}'\",\n                exception.getClass().getSimpleName(),\n                request.getRequestURI(),\n                exception);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/EventResource.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.List;\n\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.PutMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.service.EventService;\n\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport static com.netflix.conductor.rest.config.RequestMappingConstants.EVENT;\n\n@RestController\n@RequestMapping(EVENT)\npublic class EventResource {\n\n    private final EventService eventService;\n\n    public EventResource(EventService eventService) {\n        this.eventService = eventService;\n    }\n\n    @PostMapping\n    @Operation(summary = \"Add a new event handler.\")\n    public void addEventHandler(@RequestBody EventHandler eventHandler) {\n        eventService.addEventHandler(eventHandler);\n    }\n\n    @PutMapping\n    @Operation(summary = \"Update an existing event handler.\")\n    public void updateEventHandler(@RequestBody EventHandler eventHandler) {\n        eventService.updateEventHandler(eventHandler);\n    }\n\n    @DeleteMapping(\"/{name}\")\n    @Operation(summary = \"Remove an event handler\")\n    public void removeEventHandlerStatus(@PathVariable(\"name\") String name) {\n        eventService.removeEventHandlerStatus(name);\n    }\n\n    @GetMapping\n    @Operation(summary = \"Get all the event handlers\")\n    public List<EventHandler> getEventHandlers() {\n        return eventService.getEventHandlers();\n    }\n\n    @GetMapping(\"/{event}\")\n    @Operation(summary = \"Get event handlers for a given event\")\n    public List<EventHandler> getEventHandlersForEvent(\n            @PathVariable(\"event\") String event,\n            @RequestParam(value = \"activeOnly\", defaultValue = \"true\", required = false)\n                    boolean activeOnly) {\n        return eventService.getEventHandlersForEvent(event, activeOnly);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/HealthCheckResource.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.Collections;\n\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.runtime.health.api.HealthCheckStatus;\n\n@RestController\n@RequestMapping(\"/health\")\npublic class HealthCheckResource {\n\n    // SBMTODO: Move this Spring boot health check\n    @GetMapping\n    public HealthCheckStatus doCheck() throws Exception {\n        return HealthCheckStatus.create(true, Collections.emptyList());\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/MetadataResource.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.PutMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDefSummary;\nimport com.netflix.conductor.common.model.BulkResponse;\nimport com.netflix.conductor.service.MetadataService;\n\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport static com.netflix.conductor.rest.config.RequestMappingConstants.METADATA;\n\n@RestController\n@RequestMapping(value = METADATA)\npublic class MetadataResource {\n\n    private final MetadataService metadataService;\n\n    public MetadataResource(MetadataService metadataService) {\n        this.metadataService = metadataService;\n    }\n\n    @PostMapping(\"/workflow\")\n    @Operation(summary = \"Create a new workflow definition\")\n    public void create(@RequestBody WorkflowDef workflowDef) {\n        metadataService.registerWorkflowDef(workflowDef);\n    }\n\n    @PostMapping(\"/workflow/validate\")\n    @Operation(summary = \"Validates a new workflow definition\")\n    public void validate(@RequestBody WorkflowDef workflowDef) {\n        metadataService.validateWorkflowDef(workflowDef);\n    }\n\n    @PutMapping(\"/workflow\")\n    @Operation(summary = \"Create or update workflow definition\")\n    public BulkResponse update(@RequestBody List<WorkflowDef> workflowDefs) {\n        return metadataService.updateWorkflowDef(workflowDefs);\n    }\n\n    @Operation(summary = \"Retrieves workflow definition along with blueprint\")\n    @GetMapping(\"/workflow/{name}\")\n    public WorkflowDef get(\n            @PathVariable(\"name\") String name,\n            @RequestParam(value = \"version\", required = false) Integer version) {\n        return metadataService.getWorkflowDef(name, version);\n    }\n\n    @Operation(summary = \"Retrieves all workflow definition along with blueprint\")\n    @GetMapping(\"/workflow\")\n    public List<WorkflowDef> getAll() {\n        return metadataService.getWorkflowDefs();\n    }\n\n    @Operation(summary = \"Returns workflow names and versions only (no definition bodies)\")\n    @GetMapping(\"/workflow/names-and-versions\")\n    public Map<String, ? extends Iterable<WorkflowDefSummary>> getWorkflowNamesAndVersions() {\n        return metadataService.getWorkflowNamesAndVersions();\n    }\n\n    @Operation(summary = \"Returns only the latest version of all workflow definitions\")\n    @GetMapping(\"/workflow/latest-versions\")\n    public List<WorkflowDef> getAllWorkflowsWithLatestVersions() {\n        return metadataService.getWorkflowDefsLatestVersions();\n    }\n\n    @DeleteMapping(\"/workflow/{name}/{version}\")\n    @Operation(\n            summary =\n                    \"Removes workflow definition. It does not remove workflows associated with the definition.\")\n    public void unregisterWorkflowDef(\n            @PathVariable(\"name\") String name, @PathVariable(\"version\") Integer version) {\n        metadataService.unregisterWorkflowDef(name, version);\n    }\n\n    @PostMapping(\"/taskdefs\")\n    @Operation(summary = \"Create new task definition(s)\")\n    public void registerTaskDef(@RequestBody List<TaskDef> taskDefs) {\n        metadataService.registerTaskDef(taskDefs);\n    }\n\n    @PutMapping(\"/taskdefs\")\n    @Operation(summary = \"Update an existing task\")\n    public void registerTaskDef(@RequestBody TaskDef taskDef) {\n        metadataService.updateTaskDef(taskDef);\n    }\n\n    @GetMapping(value = \"/taskdefs\")\n    @Operation(summary = \"Gets all task definition\")\n    public List<TaskDef> getTaskDefs() {\n        return metadataService.getTaskDefs();\n    }\n\n    @GetMapping(\"/taskdefs/{tasktype}\")\n    @Operation(summary = \"Gets the task definition\")\n    public TaskDef getTaskDef(@PathVariable(\"tasktype\") String taskType) {\n        return metadataService.getTaskDef(taskType);\n    }\n\n    @DeleteMapping(\"/taskdefs/{tasktype}\")\n    @Operation(summary = \"Remove a task definition\")\n    public void unregisterTaskDef(@PathVariable(\"tasktype\") String taskType) {\n        metadataService.unregisterTaskDef(taskType);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/QueueAdminResource.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.Map;\n\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.conductor.core.events.queue.DefaultEventQueueProcessor;\nimport com.netflix.conductor.model.TaskModel.Status;\n\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport static com.netflix.conductor.rest.config.RequestMappingConstants.QUEUE;\n\n@RestController\n@RequestMapping(QUEUE)\npublic class QueueAdminResource {\n\n    private final DefaultEventQueueProcessor defaultEventQueueProcessor;\n\n    public QueueAdminResource(DefaultEventQueueProcessor defaultEventQueueProcessor) {\n        this.defaultEventQueueProcessor = defaultEventQueueProcessor;\n    }\n\n    @Operation(summary = \"Get the queue length\")\n    @GetMapping(value = \"/size\")\n    public Map<String, Long> size() {\n        return defaultEventQueueProcessor.size();\n    }\n\n    @Operation(summary = \"Get Queue Names\")\n    @GetMapping(value = \"/\")\n    public Map<Status, String> names() {\n        return defaultEventQueueProcessor.queues();\n    }\n\n    @Operation(summary = \"Publish a message in queue to mark a wait task as completed.\")\n    @PostMapping(value = \"/update/{workflowId}/{taskRefName}/{status}\")\n    public void update(\n            @PathVariable(\"workflowId\") String workflowId,\n            @PathVariable(\"taskRefName\") String taskRefName,\n            @PathVariable(\"status\") Status status,\n            @RequestBody Map<String, Object> output)\n            throws Exception {\n        defaultEventQueueProcessor.updateByTaskRefName(workflowId, taskRefName, output, status);\n    }\n\n    @Operation(summary = \"Publish a message in queue to mark a wait task (by taskId) as completed.\")\n    @PostMapping(\"/update/{workflowId}/task/{taskId}/{status}\")\n    public void updateByTaskId(\n            @PathVariable(\"workflowId\") String workflowId,\n            @PathVariable(\"taskId\") String taskId,\n            @PathVariable(\"status\") Status status,\n            @RequestBody Map<String, Object> output)\n            throws Exception {\n        defaultEventQueueProcessor.updateByTaskId(workflowId, taskId, output, status);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/TaskResource.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.service.TaskService;\n\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport static com.netflix.conductor.rest.config.RequestMappingConstants.TASKS;\n\nimport static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;\n\n@RestController\n@RequestMapping(value = TASKS)\npublic class TaskResource {\n\n    private final TaskService taskService;\n\n    public TaskResource(TaskService taskService) {\n        this.taskService = taskService;\n    }\n\n    @GetMapping(\"/poll/{tasktype}\")\n    @Operation(summary = \"Poll for a task of a certain type\")\n    public ResponseEntity<Task> poll(\n            @PathVariable(\"tasktype\") String taskType,\n            @RequestParam(value = \"workerid\", required = false) String workerId,\n            @RequestParam(value = \"domain\", required = false) String domain) {\n        // for backwards compatibility with 2.x client which expects a 204 when no Task is found\n        return Optional.ofNullable(taskService.poll(taskType, workerId, domain))\n                .map(ResponseEntity::ok)\n                .orElse(ResponseEntity.noContent().build());\n    }\n\n    @GetMapping(\"/poll/batch/{tasktype}\")\n    @Operation(summary = \"Batch poll for a task of a certain type\")\n    public ResponseEntity<List<Task>> batchPoll(\n            @PathVariable(\"tasktype\") String taskType,\n            @RequestParam(value = \"workerid\", required = false) String workerId,\n            @RequestParam(value = \"domain\", required = false) String domain,\n            @RequestParam(value = \"count\", defaultValue = \"1\") int count,\n            @RequestParam(value = \"timeout\", defaultValue = \"100\") int timeout) {\n        // for backwards compatibility with 2.x client which expects a 204 when no Task is found\n        return Optional.ofNullable(\n                        taskService.batchPoll(taskType, workerId, domain, count, timeout))\n                .map(ResponseEntity::ok)\n                .orElse(ResponseEntity.noContent().build());\n    }\n\n    @PostMapping(produces = TEXT_PLAIN_VALUE)\n    @Operation(summary = \"Update a task\")\n    public String updateTask(@RequestBody TaskResult taskResult) {\n        return taskService.updateTask(taskResult);\n    }\n\n    @PostMapping(\"/{taskId}/log\")\n    @Operation(summary = \"Log Task Execution Details\")\n    public void log(@PathVariable(\"taskId\") String taskId, @RequestBody String log) {\n        taskService.log(taskId, log);\n    }\n\n    @GetMapping(\"/{taskId}/log\")\n    @Operation(summary = \"Get Task Execution Logs\")\n    public List<TaskExecLog> getTaskLogs(@PathVariable(\"taskId\") String taskId) {\n        return taskService.getTaskLogs(taskId);\n    }\n\n    @GetMapping(\"/{taskId}\")\n    @Operation(summary = \"Get task by Id\")\n    public ResponseEntity<Task> getTask(@PathVariable(\"taskId\") String taskId) {\n        // for backwards compatibility with 2.x client which expects a 204 when no Task is found\n        return Optional.ofNullable(taskService.getTask(taskId))\n                .map(ResponseEntity::ok)\n                .orElse(ResponseEntity.noContent().build());\n    }\n\n    @GetMapping(\"/queue/sizes\")\n    @Operation(summary = \"Deprecated. Please use /tasks/queue/size endpoint\")\n    @Deprecated\n    public Map<String, Integer> size(\n            @RequestParam(value = \"taskType\", required = false) List<String> taskTypes) {\n        return taskService.getTaskQueueSizes(taskTypes);\n    }\n\n    @GetMapping(\"/queue/size\")\n    @Operation(summary = \"Get queue size for a task type.\")\n    public Integer taskDepth(\n            @RequestParam(\"taskType\") String taskType,\n            @RequestParam(value = \"domain\", required = false) String domain,\n            @RequestParam(value = \"isolationGroupId\", required = false) String isolationGroupId,\n            @RequestParam(value = \"executionNamespace\", required = false)\n                    String executionNamespace) {\n        return taskService.getTaskQueueSize(taskType, domain, executionNamespace, isolationGroupId);\n    }\n\n    @GetMapping(\"/queue/all/verbose\")\n    @Operation(summary = \"Get the details about each queue\")\n    public Map<String, Map<String, Map<String, Long>>> allVerbose() {\n        return taskService.allVerbose();\n    }\n\n    @GetMapping(\"/queue/all\")\n    @Operation(summary = \"Get the details about each queue\")\n    public Map<String, Long> all() {\n        return taskService.getAllQueueDetails();\n    }\n\n    @GetMapping(\"/queue/polldata\")\n    @Operation(summary = \"Get the last poll data for a given task type\")\n    public List<PollData> getPollData(@RequestParam(\"taskType\") String taskType) {\n        return taskService.getPollData(taskType);\n    }\n\n    @GetMapping(\"/queue/polldata/all\")\n    @Operation(summary = \"Get the last poll data for all task types\")\n    public List<PollData> getAllPollData() {\n        return taskService.getAllPollData();\n    }\n\n    @PostMapping(value = \"/queue/requeue/{taskType}\", produces = TEXT_PLAIN_VALUE)\n    @Operation(summary = \"Requeue pending tasks\")\n    public String requeuePendingTask(@PathVariable(\"taskType\") String taskType) {\n        return taskService.requeuePendingTask(taskType);\n    }\n\n    @Operation(\n            summary = \"Search for tasks based in payload and other parameters\",\n            description =\n                    \"use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC.\"\n                            + \" If order is not specified, defaults to ASC\")\n    @GetMapping(value = \"/search\")\n    public SearchResult<TaskSummary> search(\n            @RequestParam(value = \"start\", defaultValue = \"0\", required = false) int start,\n            @RequestParam(value = \"size\", defaultValue = \"100\", required = false) int size,\n            @RequestParam(value = \"sort\", required = false) String sort,\n            @RequestParam(value = \"freeText\", defaultValue = \"*\", required = false) String freeText,\n            @RequestParam(value = \"query\", required = false) String query) {\n        return taskService.search(start, size, sort, freeText, query);\n    }\n\n    @Operation(\n            summary = \"Search for tasks based in payload and other parameters\",\n            description =\n                    \"use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC.\"\n                            + \" If order is not specified, defaults to ASC\")\n    @GetMapping(value = \"/search-v2\")\n    public SearchResult<Task> searchV2(\n            @RequestParam(value = \"start\", defaultValue = \"0\", required = false) int start,\n            @RequestParam(value = \"size\", defaultValue = \"100\", required = false) int size,\n            @RequestParam(value = \"sort\", required = false) String sort,\n            @RequestParam(value = \"freeText\", defaultValue = \"*\", required = false) String freeText,\n            @RequestParam(value = \"query\", required = false) String query) {\n        return taskService.searchV2(start, size, sort, freeText, query);\n    }\n\n    @Operation(summary = \"Get the external uri where the task payload is to be stored\")\n    @GetMapping({\"/externalstoragelocation\", \"external-storage-location\"})\n    public ExternalStorageLocation getExternalStorageLocation(\n            @RequestParam(\"path\") String path,\n            @RequestParam(\"operation\") String operation,\n            @RequestParam(\"payloadType\") String payloadType) {\n        return taskService.getExternalStorageLocation(path, operation, payloadType);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/ValidationExceptionMapper.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.validation.ConstraintViolation;\nimport javax.validation.ConstraintViolationException;\nimport javax.validation.ValidationException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport com.netflix.conductor.common.validation.ErrorResponse;\nimport com.netflix.conductor.common.validation.ValidationError;\nimport com.netflix.conductor.core.utils.Utils;\nimport com.netflix.conductor.metrics.Monitors;\n\n/** This class converts Hibernate {@link ValidationException} into http response. */\n@RestControllerAdvice\n@Order(ValidationExceptionMapper.ORDER)\npublic class ValidationExceptionMapper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationExceptionMapper.class);\n\n    public static final int ORDER = Ordered.HIGHEST_PRECEDENCE;\n\n    private final String host = Utils.getServerId();\n\n    @ExceptionHandler(ValidationException.class)\n    public ResponseEntity<ErrorResponse> toResponse(\n            HttpServletRequest request, ValidationException exception) {\n        logException(request, exception);\n\n        HttpStatus httpStatus;\n\n        if (exception instanceof ConstraintViolationException) {\n            httpStatus = HttpStatus.BAD_REQUEST;\n        } else {\n            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;\n            Monitors.error(\"error\", \"error\");\n        }\n\n        return new ResponseEntity<>(toErrorResponse(exception), httpStatus);\n    }\n\n    private ErrorResponse toErrorResponse(ValidationException ve) {\n        if (ve instanceof ConstraintViolationException) {\n            return constraintViolationExceptionToErrorResponse((ConstraintViolationException) ve);\n        } else {\n            ErrorResponse result = new ErrorResponse();\n            result.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());\n            result.setMessage(ve.getMessage());\n            result.setInstance(host);\n            return result;\n        }\n    }\n\n    private ErrorResponse constraintViolationExceptionToErrorResponse(\n            ConstraintViolationException exception) {\n        ErrorResponse errorResponse = new ErrorResponse();\n        errorResponse.setStatus(HttpStatus.BAD_REQUEST.value());\n        errorResponse.setMessage(\"Validation failed, check below errors for detail.\");\n\n        List<ValidationError> validationErrors = new ArrayList<>();\n\n        exception\n                .getConstraintViolations()\n                .forEach(\n                        e ->\n                                validationErrors.add(\n                                        new ValidationError(\n                                                getViolationPath(e),\n                                                e.getMessage(),\n                                                getViolationInvalidValue(e.getInvalidValue()))));\n\n        errorResponse.setValidationErrors(validationErrors);\n        return errorResponse;\n    }\n\n    private String getViolationPath(final ConstraintViolation<?> violation) {\n        final String propertyPath = violation.getPropertyPath().toString();\n        return !\"\".equals(propertyPath) ? propertyPath : \"\";\n    }\n\n    private String getViolationInvalidValue(final Object invalidValue) {\n        if (invalidValue == null) {\n            return null;\n        }\n\n        if (invalidValue.getClass().isArray()) {\n            if (invalidValue instanceof Object[]) {\n                // not helpful to return object array, skip it.\n                return null;\n            } else if (invalidValue instanceof boolean[]) {\n                return Arrays.toString((boolean[]) invalidValue);\n            } else if (invalidValue instanceof byte[]) {\n                return Arrays.toString((byte[]) invalidValue);\n            } else if (invalidValue instanceof char[]) {\n                return Arrays.toString((char[]) invalidValue);\n            } else if (invalidValue instanceof double[]) {\n                return Arrays.toString((double[]) invalidValue);\n            } else if (invalidValue instanceof float[]) {\n                return Arrays.toString((float[]) invalidValue);\n            } else if (invalidValue instanceof int[]) {\n                return Arrays.toString((int[]) invalidValue);\n            } else if (invalidValue instanceof long[]) {\n                return Arrays.toString((long[]) invalidValue);\n            } else if (invalidValue instanceof short[]) {\n                return Arrays.toString((short[]) invalidValue);\n            }\n        }\n\n        // It is only helpful to return invalid value of primitive types\n        if (invalidValue.getClass().getName().startsWith(\"java.lang.\")) {\n            return invalidValue.toString();\n        }\n\n        return null;\n    }\n\n    private void logException(HttpServletRequest request, ValidationException exception) {\n        LOGGER.error(\n                \"Error {} url: '{}'\",\n                exception.getClass().getSimpleName(),\n                request.getRequestURI(),\n                exception);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/WorkflowBulkResource.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.List;\n\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.PutMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.conductor.common.model.BulkResponse;\nimport com.netflix.conductor.service.WorkflowBulkService;\n\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport static com.netflix.conductor.rest.config.RequestMappingConstants.WORKFLOW_BULK;\n\n/** Synchronous Bulk APIs to process the workflows in batches */\n@RestController\n@RequestMapping(WORKFLOW_BULK)\npublic class WorkflowBulkResource {\n\n    private final WorkflowBulkService workflowBulkService;\n\n    public WorkflowBulkResource(WorkflowBulkService workflowBulkService) {\n        this.workflowBulkService = workflowBulkService;\n    }\n\n    /**\n     * Pause the list of workflows.\n     *\n     * @param workflowIds - list of workflow Ids to perform pause operation on\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    @PutMapping(\"/pause\")\n    @Operation(summary = \"Pause the list of workflows\")\n    public BulkResponse pauseWorkflow(@RequestBody List<String> workflowIds) {\n        return workflowBulkService.pauseWorkflow(workflowIds);\n    }\n\n    /**\n     * Resume the list of workflows.\n     *\n     * @param workflowIds - list of workflow Ids to perform resume operation on\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    @PutMapping(\"/resume\")\n    @Operation(summary = \"Resume the list of workflows\")\n    public BulkResponse resumeWorkflow(@RequestBody List<String> workflowIds) {\n        return workflowBulkService.resumeWorkflow(workflowIds);\n    }\n\n    /**\n     * Restart the list of workflows.\n     *\n     * @param workflowIds - list of workflow Ids to perform restart operation on\n     * @param useLatestDefinitions if true, use latest workflow and task definitions upon restart\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    @PostMapping(\"/restart\")\n    @Operation(summary = \"Restart the list of completed workflow\")\n    public BulkResponse restart(\n            @RequestBody List<String> workflowIds,\n            @RequestParam(value = \"useLatestDefinitions\", defaultValue = \"false\", required = false)\n                    boolean useLatestDefinitions) {\n        return workflowBulkService.restart(workflowIds, useLatestDefinitions);\n    }\n\n    /**\n     * Retry the last failed task for each workflow from the list.\n     *\n     * @param workflowIds - list of workflow Ids to perform retry operation on\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    @PostMapping(\"/retry\")\n    @Operation(summary = \"Retry the last failed task for each workflow from the list\")\n    public BulkResponse retry(@RequestBody List<String> workflowIds) {\n        return workflowBulkService.retry(workflowIds);\n    }\n\n    /**\n     * Terminate workflows execution.\n     *\n     * @param workflowIds - list of workflow Ids to perform terminate operation on\n     * @param reason - description to be specified for the terminated workflow for future\n     *     references.\n     * @return bulk response object containing a list of succeeded workflows and a list of failed\n     *     ones with errors\n     */\n    @PostMapping(\"/terminate\")\n    @Operation(summary = \"Terminate workflows execution\")\n    public BulkResponse terminate(\n            @RequestBody List<String> workflowIds,\n            @RequestParam(value = \"reason\", required = false) String reason) {\n        return workflowBulkService.terminate(workflowIds, reason);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/controllers/WorkflowResource.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.PutMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.SkipTaskRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.run.*;\nimport com.netflix.conductor.service.WorkflowService;\nimport com.netflix.conductor.service.WorkflowTestService;\n\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport static com.netflix.conductor.rest.config.RequestMappingConstants.WORKFLOW;\n\nimport static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;\nimport static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;\n\n@RestController\n@RequestMapping(WORKFLOW)\npublic class WorkflowResource {\n\n    private final WorkflowService workflowService;\n\n    private final WorkflowTestService workflowTestService;\n\n    public WorkflowResource(\n            WorkflowService workflowService, WorkflowTestService workflowTestService) {\n        this.workflowService = workflowService;\n        this.workflowTestService = workflowTestService;\n    }\n\n    @PostMapping(produces = TEXT_PLAIN_VALUE)\n    @Operation(\n            summary =\n                    \"Start a new workflow with StartWorkflowRequest, which allows task to be executed in a domain\")\n    public String startWorkflow(@RequestBody StartWorkflowRequest request) {\n        return workflowService.startWorkflow(request);\n    }\n\n    @PostMapping(value = \"/{name}\", produces = TEXT_PLAIN_VALUE)\n    @Operation(\n            summary =\n                    \"Start a new workflow. Returns the ID of the workflow instance that can be later used for tracking\")\n    public String startWorkflow(\n            @PathVariable(\"name\") String name,\n            @RequestParam(value = \"version\", required = false) Integer version,\n            @RequestParam(value = \"correlationId\", required = false) String correlationId,\n            @RequestParam(value = \"priority\", defaultValue = \"0\", required = false) int priority,\n            @RequestBody Map<String, Object> input) {\n        return workflowService.startWorkflow(name, version, correlationId, priority, input);\n    }\n\n    @GetMapping(\"/{name}/correlated/{correlationId}\")\n    @Operation(summary = \"Lists workflows for the given correlation id\")\n    public List<Workflow> getWorkflows(\n            @PathVariable(\"name\") String name,\n            @PathVariable(\"correlationId\") String correlationId,\n            @RequestParam(value = \"includeClosed\", defaultValue = \"false\", required = false)\n                    boolean includeClosed,\n            @RequestParam(value = \"includeTasks\", defaultValue = \"false\", required = false)\n                    boolean includeTasks) {\n        return workflowService.getWorkflows(name, correlationId, includeClosed, includeTasks);\n    }\n\n    @PostMapping(value = \"/{name}/correlated\")\n    @Operation(summary = \"Lists workflows for the given correlation id list\")\n    public Map<String, List<Workflow>> getWorkflows(\n            @PathVariable(\"name\") String name,\n            @RequestParam(value = \"includeClosed\", defaultValue = \"false\", required = false)\n                    boolean includeClosed,\n            @RequestParam(value = \"includeTasks\", defaultValue = \"false\", required = false)\n                    boolean includeTasks,\n            @RequestBody List<String> correlationIds) {\n        return workflowService.getWorkflows(name, includeClosed, includeTasks, correlationIds);\n    }\n\n    @GetMapping(\"/{workflowId}\")\n    @Operation(summary = \"Gets the workflow by workflow id\")\n    public Workflow getExecutionStatus(\n            @PathVariable(\"workflowId\") String workflowId,\n            @RequestParam(value = \"includeTasks\", defaultValue = \"true\", required = false)\n                    boolean includeTasks) {\n        return workflowService.getExecutionStatus(workflowId, includeTasks);\n    }\n\n    @DeleteMapping(\"/{workflowId}/remove\")\n    @Operation(summary = \"Removes the workflow from the system\")\n    public void delete(\n            @PathVariable(\"workflowId\") String workflowId,\n            @RequestParam(value = \"archiveWorkflow\", defaultValue = \"true\", required = false)\n                    boolean archiveWorkflow) {\n        workflowService.deleteWorkflow(workflowId, archiveWorkflow);\n    }\n\n    @GetMapping(\"/running/{name}\")\n    @Operation(summary = \"Retrieve all the running workflows\")\n    public List<String> getRunningWorkflow(\n            @PathVariable(\"name\") String workflowName,\n            @RequestParam(value = \"version\", defaultValue = \"1\", required = false) int version,\n            @RequestParam(value = \"startTime\", required = false) Long startTime,\n            @RequestParam(value = \"endTime\", required = false) Long endTime) {\n        return workflowService.getRunningWorkflows(workflowName, version, startTime, endTime);\n    }\n\n    @PutMapping(\"/decide/{workflowId}\")\n    @Operation(summary = \"Starts the decision task for a workflow\")\n    public void decide(@PathVariable(\"workflowId\") String workflowId) {\n        workflowService.decideWorkflow(workflowId);\n    }\n\n    @PutMapping(\"/{workflowId}/pause\")\n    @Operation(summary = \"Pauses the workflow\")\n    public void pauseWorkflow(@PathVariable(\"workflowId\") String workflowId) {\n        workflowService.pauseWorkflow(workflowId);\n    }\n\n    @PutMapping(\"/{workflowId}/resume\")\n    @Operation(summary = \"Resumes the workflow\")\n    public void resumeWorkflow(@PathVariable(\"workflowId\") String workflowId) {\n        workflowService.resumeWorkflow(workflowId);\n    }\n\n    @PutMapping(\"/{workflowId}/skiptask/{taskReferenceName}\")\n    @Operation(summary = \"Skips a given task from a current running workflow\")\n    public void skipTaskFromWorkflow(\n            @PathVariable(\"workflowId\") String workflowId,\n            @PathVariable(\"taskReferenceName\") String taskReferenceName,\n            SkipTaskRequest skipTaskRequest) {\n        workflowService.skipTaskFromWorkflow(workflowId, taskReferenceName, skipTaskRequest);\n    }\n\n    @PostMapping(value = \"/{workflowId}/rerun\", produces = TEXT_PLAIN_VALUE)\n    @Operation(summary = \"Reruns the workflow from a specific task\")\n    public String rerun(\n            @PathVariable(\"workflowId\") String workflowId,\n            @RequestBody RerunWorkflowRequest request) {\n        return workflowService.rerunWorkflow(workflowId, request);\n    }\n\n    @PostMapping(\"/{workflowId}/restart\")\n    @Operation(summary = \"Restarts a completed workflow\")\n    @ResponseStatus(\n            value = HttpStatus.NO_CONTENT) // for backwards compatibility with 2.x client which\n    // expects a 204 for this request\n    public void restart(\n            @PathVariable(\"workflowId\") String workflowId,\n            @RequestParam(value = \"useLatestDefinitions\", defaultValue = \"false\", required = false)\n                    boolean useLatestDefinitions) {\n        workflowService.restartWorkflow(workflowId, useLatestDefinitions);\n    }\n\n    @PostMapping(\"/{workflowId}/retry\")\n    @Operation(summary = \"Retries the last failed task\")\n    @ResponseStatus(\n            value = HttpStatus.NO_CONTENT) // for backwards compatibility with 2.x client which\n    // expects a 204 for this request\n    public void retry(\n            @PathVariable(\"workflowId\") String workflowId,\n            @RequestParam(\n                            value = \"resumeSubworkflowTasks\",\n                            defaultValue = \"false\",\n                            required = false)\n                    boolean resumeSubworkflowTasks) {\n        workflowService.retryWorkflow(workflowId, resumeSubworkflowTasks);\n    }\n\n    @PostMapping(\"/{workflowId}/resetcallbacks\")\n    @Operation(summary = \"Resets callback times of all non-terminal SIMPLE tasks to 0\")\n    @ResponseStatus(\n            value = HttpStatus.NO_CONTENT) // for backwards compatibility with 2.x client which\n    // expects a 204 for this request\n    public void resetWorkflow(@PathVariable(\"workflowId\") String workflowId) {\n        workflowService.resetWorkflow(workflowId);\n    }\n\n    @DeleteMapping(\"/{workflowId}\")\n    @Operation(summary = \"Terminate workflow execution\")\n    public void terminate(\n            @PathVariable(\"workflowId\") String workflowId,\n            @RequestParam(value = \"reason\", required = false) String reason) {\n        workflowService.terminateWorkflow(workflowId, reason);\n    }\n\n    @Operation(\n            summary = \"Search for workflows based on payload and other parameters\",\n            description =\n                    \"use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC.\"\n                            + \" If order is not specified, defaults to ASC.\")\n    @GetMapping(value = \"/search\")\n    public SearchResult<WorkflowSummary> search(\n            @RequestParam(value = \"start\", defaultValue = \"0\", required = false) int start,\n            @RequestParam(value = \"size\", defaultValue = \"100\", required = false) int size,\n            @RequestParam(value = \"sort\", required = false) String sort,\n            @RequestParam(value = \"freeText\", defaultValue = \"*\", required = false) String freeText,\n            @RequestParam(value = \"query\", required = false) String query) {\n        return workflowService.searchWorkflows(start, size, sort, freeText, query);\n    }\n\n    @Operation(\n            summary = \"Search for workflows based on payload and other parameters\",\n            description =\n                    \"use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC.\"\n                            + \" If order is not specified, defaults to ASC.\")\n    @GetMapping(value = \"/search-v2\")\n    public SearchResult<Workflow> searchV2(\n            @RequestParam(value = \"start\", defaultValue = \"0\", required = false) int start,\n            @RequestParam(value = \"size\", defaultValue = \"100\", required = false) int size,\n            @RequestParam(value = \"sort\", required = false) String sort,\n            @RequestParam(value = \"freeText\", defaultValue = \"*\", required = false) String freeText,\n            @RequestParam(value = \"query\", required = false) String query) {\n        return workflowService.searchWorkflowsV2(start, size, sort, freeText, query);\n    }\n\n    @Operation(\n            summary = \"Search for workflows based on task parameters\",\n            description =\n                    \"use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC.\"\n                            + \" If order is not specified, defaults to ASC\")\n    @GetMapping(value = \"/search-by-tasks\")\n    public SearchResult<WorkflowSummary> searchWorkflowsByTasks(\n            @RequestParam(value = \"start\", defaultValue = \"0\", required = false) int start,\n            @RequestParam(value = \"size\", defaultValue = \"100\", required = false) int size,\n            @RequestParam(value = \"sort\", required = false) String sort,\n            @RequestParam(value = \"freeText\", defaultValue = \"*\", required = false) String freeText,\n            @RequestParam(value = \"query\", required = false) String query) {\n        return workflowService.searchWorkflowsByTasks(start, size, sort, freeText, query);\n    }\n\n    @Operation(\n            summary = \"Search for workflows based on task parameters\",\n            description =\n                    \"use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC.\"\n                            + \" If order is not specified, defaults to ASC\")\n    @GetMapping(value = \"/search-by-tasks-v2\")\n    public SearchResult<Workflow> searchWorkflowsByTasksV2(\n            @RequestParam(value = \"start\", defaultValue = \"0\", required = false) int start,\n            @RequestParam(value = \"size\", defaultValue = \"100\", required = false) int size,\n            @RequestParam(value = \"sort\", required = false) String sort,\n            @RequestParam(value = \"freeText\", defaultValue = \"*\", required = false) String freeText,\n            @RequestParam(value = \"query\", required = false) String query) {\n        return workflowService.searchWorkflowsByTasksV2(start, size, sort, freeText, query);\n    }\n\n    @Operation(\n            summary =\n                    \"Get the uri and path of the external storage where the workflow payload is to be stored\")\n    @GetMapping({\"/externalstoragelocation\", \"external-storage-location\"})\n    public ExternalStorageLocation getExternalStorageLocation(\n            @RequestParam(\"path\") String path,\n            @RequestParam(\"operation\") String operation,\n            @RequestParam(\"payloadType\") String payloadType) {\n        return workflowService.getExternalStorageLocation(path, operation, payloadType);\n    }\n\n    @PostMapping(value = \"test\", produces = APPLICATION_JSON_VALUE)\n    @Operation(summary = \"Test workflow execution using mock data\")\n    public Workflow testWorkflow(@RequestBody WorkflowTestRequest request) {\n        return workflowTestService.testWorkflow(request);\n    }\n}\n"
  },
  {
    "path": "rest/src/main/java/com/netflix/conductor/rest/startup/KitchenSinkInitializer.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.startup;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.core.io.Resource;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.FileCopyUtils;\nimport org.springframework.util.LinkedMultiValueMap;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.web.client.RestTemplate;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\n\nimport static org.springframework.http.HttpHeaders.CONTENT_TYPE;\nimport static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;\n\n@Component\npublic class KitchenSinkInitializer {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(KitchenSinkInitializer.class);\n\n    private final RestTemplate restTemplate;\n\n    @Value(\"${loadSample:false}\")\n    private boolean loadSamples;\n\n    @Value(\"${server.port:8080}\")\n    private int port;\n\n    @Value(\"classpath:./kitchensink/kitchensink.json\")\n    private Resource kitchenSink;\n\n    @Value(\"classpath:./kitchensink/sub_flow_1.json\")\n    private Resource subFlow;\n\n    @Value(\"classpath:./kitchensink/kitchenSink-ephemeralWorkflowWithStoredTasks.json\")\n    private Resource ephemeralWorkflowWithStoredTasks;\n\n    @Value(\"classpath:./kitchensink/kitchenSink-ephemeralWorkflowWithEphemeralTasks.json\")\n    private Resource ephemeralWorkflowWithEphemeralTasks;\n\n    public KitchenSinkInitializer(RestTemplateBuilder restTemplateBuilder) {\n        this.restTemplate = restTemplateBuilder.build();\n    }\n\n    @EventListener(ApplicationReadyEvent.class)\n    public void setupKitchenSink() {\n        try {\n            if (loadSamples) {\n                LOGGER.info(\"Loading Kitchen Sink examples\");\n                createKitchenSink();\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"Error initializing kitchen sink\", e);\n        }\n    }\n\n    private void createKitchenSink() throws Exception {\n        List<TaskDef> taskDefs = new LinkedList<>();\n        TaskDef taskDef;\n        for (int i = 0; i < 40; i++) {\n            taskDef = new TaskDef(\"task_\" + i, \"task_\" + i, 1, 0);\n            taskDef.setOwnerEmail(\"example@email.com\");\n            taskDefs.add(taskDef);\n        }\n\n        taskDef = new TaskDef(\"search_elasticsearch\", \"search_elasticsearch\", 1, 0);\n        taskDef.setOwnerEmail(\"example@email.com\");\n        taskDefs.add(taskDef);\n\n        restTemplate.postForEntity(url(\"/api/metadata/taskdefs\"), taskDefs, Object.class);\n\n        /*\n         * Kitchensink example (stored workflow with stored tasks)\n         */\n        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();\n        headers.add(CONTENT_TYPE, APPLICATION_JSON_VALUE);\n        HttpEntity<String> request = new HttpEntity<>(readToString(kitchenSink), headers);\n        restTemplate.postForEntity(url(\"/api/metadata/workflow/\"), request, Map.class);\n\n        request = new HttpEntity<>(readToString(subFlow), headers);\n        restTemplate.postForEntity(url(\"/api/metadata/workflow/\"), request, Map.class);\n\n        restTemplate.postForEntity(\n                url(\"/api/workflow/kitchensink\"),\n                Collections.singletonMap(\"task2Name\", \"task_5\"),\n                String.class);\n        LOGGER.info(\"Kitchen sink workflow is created!\");\n\n        /*\n         * Kitchensink example with ephemeral workflow and stored tasks\n         */\n        request = new HttpEntity<>(readToString(ephemeralWorkflowWithStoredTasks), headers);\n        restTemplate.postForEntity(url(\"/api/workflow/\"), request, String.class);\n        LOGGER.info(\"Ephemeral Kitchen sink workflow with stored tasks is created!\");\n\n        /*\n         * Kitchensink example with ephemeral workflow and ephemeral tasks\n         */\n        request = new HttpEntity<>(readToString(ephemeralWorkflowWithEphemeralTasks), headers);\n        restTemplate.postForEntity(url(\"/api/workflow/\"), request, String.class);\n        LOGGER.info(\"Ephemeral Kitchen sink workflow with ephemeral tasks is created!\");\n    }\n\n    private String readToString(Resource resource) throws IOException {\n        return FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));\n    }\n\n    private String url(String path) {\n        return \"http://localhost:\" + port + path;\n    }\n}\n"
  },
  {
    "path": "rest/src/main/resources/kitchensink/kitchenSink-ephemeralWorkflowWithEphemeralTasks.json",
    "content": "{\n  \"name\": \"kitchenSink-ephemeralWorkflowWithEphemeralTasks\",\n  \"workflowDef\": {\n    \"name\": \"ephemeralKitchenSinkEphemeralTasks\",\n    \"description\": \"Kitchensink ephemeral workflow with ephemeral tasks\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"task_10001\",\n        \"taskReferenceName\": \"task_10001\",\n        \"inputParameters\": {\n          \"mod\": \"${workflow.input.mod}\",\n          \"oddEven\": \"${workflow.input.oddEven}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"taskDefinition\": {\n          \"ownerApp\": null,\n          \"createTime\": null,\n          \"updateTime\": null,\n          \"createdBy\": null,\n          \"updatedBy\": null,\n          \"name\": \"task_10001\",\n          \"description\": \"task_10001\",\n          \"retryCount\": 1,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 3600,\n          \"concurrentExecLimit\": null,\n          \"inputTemplate\": {}\n        }\n      },\n      {\n        \"name\": \"event_task\",\n        \"taskReferenceName\": \"event_0\",\n        \"inputParameters\": {\n          \"mod\": \"${workflow.input.mod}\",\n          \"oddEven\": \"${workflow.input.oddEven}\"\n        },\n        \"type\": \"EVENT\",\n        \"sink\": \"conductor\"\n      },\n      {\n        \"name\": \"dyntask\",\n        \"taskReferenceName\": \"task_2\",\n        \"inputParameters\": {\n          \"taskToExecute\": \"${workflow.input.task2Name}\"\n        },\n        \"type\": \"DYNAMIC\",\n        \"dynamicTaskNameParam\": \"taskToExecute\"\n      },\n      {\n        \"name\": \"oddEvenDecision\",\n        \"taskReferenceName\": \"oddEvenDecision\",\n        \"inputParameters\": {\n          \"oddEven\": \"${task_2.output.oddEven}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"oddEven\",\n        \"decisionCases\": {\n          \"0\": [\n            {\n              \"name\": \"task_10004\",\n              \"taskReferenceName\": \"task_10004\",\n              \"inputParameters\": {\n                \"mod\": \"${task_2.output.mod}\",\n                \"oddEven\": \"${task_2.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"taskDefinition\": {\n                \"ownerApp\": null,\n                \"createTime\": null,\n                \"updateTime\": null,\n                \"createdBy\": null,\n                \"updatedBy\": null,\n                \"name\": \"task_10004\",\n                \"description\": \"task_10004\",\n                \"retryCount\": 1,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [],\n                \"outputKeys\": [],\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 3600,\n                \"concurrentExecLimit\": null,\n                \"inputTemplate\": {}\n              }\n            },\n            {\n              \"name\": \"dynamic_fanout\",\n              \"taskReferenceName\": \"fanout1\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"${task_10004.output.dynamicTasks}\",\n                \"input\": \"${task_10004.output.inputs}\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"input\"\n            },\n            {\n              \"name\": \"dynamic_join\",\n              \"taskReferenceName\": \"join1\",\n              \"type\": \"JOIN\"\n            }\n          ],\n          \"1\": [\n            {\n              \"name\": \"fork_join\",\n              \"taskReferenceName\": \"forkx\",\n              \"type\": \"FORK_JOIN\",\n              \"forkTasks\": [\n                [\n                  {\n                    \"name\": \"task_100010\",\n                    \"taskReferenceName\": \"task_100010\",\n                    \"type\": \"SIMPLE\",\n                    \"taskDefinition\": {\n                      \"ownerApp\": null,\n                      \"createTime\": null,\n                      \"updateTime\": null,\n                      \"createdBy\": null,\n                      \"updatedBy\": null,\n                      \"name\": \"task_100010\",\n                      \"description\": \"task_100010\",\n                      \"retryCount\": 1,\n                      \"timeoutSeconds\": 0,\n                      \"inputKeys\": [],\n                      \"outputKeys\": [],\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 3600,\n                      \"concurrentExecLimit\": null,\n                      \"inputTemplate\": {}\n                    }\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf3\",\n                    \"inputParameters\": {\n                      \"mod\": \"${task_10001.output.mod}\",\n                      \"oddEven\": \"${task_10001.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ],\n                [\n                  {\n                    \"name\": \"task_100011\",\n                    \"taskReferenceName\": \"task_100011\",\n                    \"type\": \"SIMPLE\",\n                    \"taskDefinition\": {\n                      \"ownerApp\": null,\n                      \"createTime\": null,\n                      \"updateTime\": null,\n                      \"createdBy\": null,\n                      \"updatedBy\": null,\n                      \"name\": \"task_100011\",\n                      \"description\": \"task_100011\",\n                      \"retryCount\": 1,\n                      \"timeoutSeconds\": 0,\n                      \"inputKeys\": [],\n                      \"outputKeys\": [],\n                      \"timeoutPolicy\": \"TIME_OUT_WF\",\n                      \"retryLogic\": \"FIXED\",\n                      \"retryDelaySeconds\": 60,\n                      \"responseTimeoutSeconds\": 3600,\n                      \"concurrentExecLimit\": null,\n                      \"inputTemplate\": {}\n                    }\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf4\",\n                    \"inputParameters\": {\n                      \"mod\": \"${task_10001.output.mod}\",\n                      \"oddEven\": \"${task_10001.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ]\n              ]\n            },\n            {\n              \"name\": \"join\",\n              \"taskReferenceName\": \"join2\",\n              \"type\": \"JOIN\",\n              \"joinOn\": [\n                \"wf3\",\n                \"wf4\"\n              ]\n            }\n          ]\n        }\n      },\n      {\n        \"name\": \"search_elasticsearch\",\n        \"taskReferenceName\": \"get_es_1\",\n        \"inputParameters\": {\n          \"http_request\": {\n            \"uri\": \"http://localhost:9200/conductor/_search?size=10\",\n            \"method\": \"GET\"\n          }\n        },\n        \"type\": \"HTTP\"\n      },\n      {\n        \"name\": \"task_100030\",\n        \"taskReferenceName\": \"task_100030\",\n        \"inputParameters\": {\n          \"statuses\": \"${get_es_1.output..status}\",\n          \"workflowIds\": \"${get_es_1.output..workflowId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"taskDefinition\": {\n          \"ownerApp\": null,\n          \"createTime\": null,\n          \"updateTime\": null,\n          \"createdBy\": null,\n          \"updatedBy\": null,\n          \"name\": \"task_100030\",\n          \"description\": \"task_100030\",\n          \"retryCount\": 1,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 3600,\n          \"concurrentExecLimit\": null,\n          \"inputTemplate\": {}\n        }\n      }\n    ],\n    \"outputParameters\": {\n      \"statues\": \"${get_es_1.output..status}\",\n      \"workflowIds\": \"${get_es_1.output..workflowId}\"\n    },\n    \"schemaVersion\": 2,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  \"input\": {\n    \"task2Name\": \"task_10005\"\n  }\n}\n"
  },
  {
    "path": "rest/src/main/resources/kitchensink/kitchenSink-ephemeralWorkflowWithStoredTasks.json",
    "content": "{\n  \"name\": \"kitchenSink-ephemeralWorkflowWithStoredTasks\",\n  \"workflowDef\": {\n    \"name\": \"ephemeralKitchenSinkStoredTasks\",\n    \"description\": \"kitchensink workflow definition\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"task_1\",\n        \"taskReferenceName\": \"task_1\",\n        \"inputParameters\": {\n          \"mod\": \"${workflow.input.mod}\",\n          \"oddEven\": \"${workflow.input.oddEven}\"\n        },\n        \"type\": \"SIMPLE\"\n      },\n      {\n        \"name\": \"event_task\",\n        \"taskReferenceName\": \"event_0\",\n        \"inputParameters\": {\n          \"mod\": \"${workflow.input.mod}\",\n          \"oddEven\": \"${workflow.input.oddEven}\"\n        },\n        \"type\": \"EVENT\",\n        \"sink\": \"conductor\"\n      },\n      {\n        \"name\": \"dyntask\",\n        \"taskReferenceName\": \"task_2\",\n        \"inputParameters\": {\n          \"taskToExecute\": \"${workflow.input.task2Name}\"\n        },\n        \"type\": \"DYNAMIC\",\n        \"dynamicTaskNameParam\": \"taskToExecute\"\n      },\n      {\n        \"name\": \"oddEvenDecision\",\n        \"taskReferenceName\": \"oddEvenDecision\",\n        \"inputParameters\": {\n          \"oddEven\": \"${task_2.output.oddEven}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"oddEven\",\n        \"decisionCases\": {\n          \"0\": [\n            {\n              \"name\": \"task_4\",\n              \"taskReferenceName\": \"task_4\",\n              \"inputParameters\": {\n                \"mod\": \"${task_2.output.mod}\",\n                \"oddEven\": \"${task_2.output.oddEven}\"\n              },\n              \"type\": \"SIMPLE\"\n            },\n            {\n              \"name\": \"dynamic_fanout\",\n              \"taskReferenceName\": \"fanout1\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"${task_4.output.dynamicTasks}\",\n                \"input\": \"${task_4.output.inputs}\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"input\"\n            },\n            {\n              \"name\": \"dynamic_join\",\n              \"taskReferenceName\": \"join1\",\n              \"type\": \"JOIN\"\n            }\n          ],\n          \"1\": [\n            {\n              \"name\": \"fork_join\",\n              \"taskReferenceName\": \"forkx\",\n              \"type\": \"FORK_JOIN\",\n              \"forkTasks\": [\n                [\n                  {\n                    \"name\": \"task_10\",\n                    \"taskReferenceName\": \"task_10\",\n                    \"type\": \"SIMPLE\"\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf3\",\n                    \"inputParameters\": {\n                      \"mod\": \"${task_1.output.mod}\",\n                      \"oddEven\": \"${task_1.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ],\n                [\n                  {\n                    \"name\": \"task_11\",\n                    \"taskReferenceName\": \"task_11\",\n                    \"type\": \"SIMPLE\"\n                  },\n                  {\n                    \"name\": \"sub_workflow_x\",\n                    \"taskReferenceName\": \"wf4\",\n                    \"inputParameters\": {\n                      \"mod\": \"${task_1.output.mod}\",\n                      \"oddEven\": \"${task_1.output.oddEven}\"\n                    },\n                    \"type\": \"SUB_WORKFLOW\",\n                    \"subWorkflowParam\": {\n                      \"name\": \"sub_flow_1\",\n                      \"version\": 1\n                    }\n                  }\n                ]\n              ]\n            },\n            {\n              \"name\": \"join\",\n              \"taskReferenceName\": \"join2\",\n              \"type\": \"JOIN\",\n              \"joinOn\": [\n                \"wf3\",\n                \"wf4\"\n              ]\n            }\n          ]\n        }\n      },\n      {\n        \"name\": \"search_elasticsearch\",\n        \"taskReferenceName\": \"get_es_1\",\n        \"inputParameters\": {\n          \"http_request\": {\n            \"uri\": \"http://localhost:9200/conductor/_search?size=10\",\n            \"method\": \"GET\"\n          }\n        },\n        \"type\": \"HTTP\"\n      },\n      {\n        \"name\": \"task_30\",\n        \"taskReferenceName\": \"task_30\",\n        \"inputParameters\": {\n          \"statuses\": \"${get_es_1.output..status}\",\n          \"workflowIds\": \"${get_es_1.output..workflowId}\"\n        },\n        \"type\": \"SIMPLE\"\n      }\n    ],\n    \"outputParameters\": {\n      \"statues\": \"${get_es_1.output..status}\",\n      \"workflowIds\": \"${get_es_1.output..workflowId}\"\n    },\n    \"schemaVersion\": 2,\n    \"ownerEmail\": \"example@email.com\"\n  },\n  \"input\": {\n    \"task2Name\": \"task_5\"\n  }\n}\n"
  },
  {
    "path": "rest/src/main/resources/kitchensink/kitchensink.json",
    "content": "{\n  \"name\": \"kitchensink\",\n  \"description\": \"kitchensink workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"task_1\",\n      \"taskReferenceName\": \"task_1\",\n      \"inputParameters\": {\n        \"mod\": \"${workflow.input.mod}\",\n        \"oddEven\": \"${workflow.input.oddEven}\"\n      },\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"event_task\",\n      \"taskReferenceName\": \"event_0\",\n      \"inputParameters\": {\n        \"mod\": \"${workflow.input.mod}\",\n        \"oddEven\": \"${workflow.input.oddEven}\"\n      },\n      \"type\": \"EVENT\",\n      \"sink\": \"conductor\"\n    },\n    {\n      \"name\": \"dyntask\",\n      \"taskReferenceName\": \"task_2\",\n      \"inputParameters\": {\n        \"taskToExecute\": \"${workflow.input.task2Name}\"\n      },\n      \"type\": \"DYNAMIC\",\n      \"dynamicTaskNameParam\": \"taskToExecute\"\n    },\n    {\n      \"name\": \"oddEvenDecision\",\n      \"taskReferenceName\": \"oddEvenDecision\",\n      \"inputParameters\": {\n        \"oddEven\": \"${task_2.output.oddEven}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"oddEven\",\n      \"decisionCases\": {\n        \"0\": [\n          {\n            \"name\": \"task_4\",\n            \"taskReferenceName\": \"task_4\",\n            \"inputParameters\": {\n              \"mod\": \"${task_2.output.mod}\",\n              \"oddEven\": \"${task_2.output.oddEven}\"\n            },\n            \"type\": \"SIMPLE\"\n          },\n          {\n            \"name\": \"dynamic_fanout\",\n            \"taskReferenceName\": \"fanout1\",\n            \"inputParameters\": {\n              \"dynamicTasks\": \"${task_4.output.dynamicTasks}\",\n              \"input\": \"${task_4.output.inputs}\"\n            },\n            \"type\": \"FORK_JOIN_DYNAMIC\",\n            \"dynamicForkTasksParam\": \"dynamicTasks\",\n            \"dynamicForkTasksInputParamName\": \"input\"\n          },\n          {\n            \"name\": \"dynamic_join\",\n            \"taskReferenceName\": \"join1\",\n            \"type\": \"JOIN\"\n          }\n        ],\n        \"1\": [\n          {\n            \"name\": \"fork_join\",\n            \"taskReferenceName\": \"forkx\",\n            \"type\": \"FORK_JOIN\",\n            \"forkTasks\": [\n              [\n                {\n                  \"name\": \"task_10\",\n                  \"taskReferenceName\": \"task_10\",\n                  \"type\": \"SIMPLE\"\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf3\",\n                  \"inputParameters\": {\n                    \"mod\": \"${task_1.output.mod}\",\n                    \"oddEven\": \"${task_1.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ],\n              [\n                {\n                  \"name\": \"task_11\",\n                  \"taskReferenceName\": \"task_11\",\n                  \"type\": \"SIMPLE\"\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf4\",\n                  \"inputParameters\": {\n                    \"mod\": \"${task_1.output.mod}\",\n                    \"oddEven\": \"${task_1.output.oddEven}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ]\n            ]\n          },\n          {\n            \"name\": \"join\",\n            \"taskReferenceName\": \"join2\",\n            \"type\": \"JOIN\",\n            \"joinOn\": [\n              \"wf3\",\n              \"wf4\"\n            ]\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"search_elasticsearch\",\n      \"taskReferenceName\": \"get_es_1\",\n      \"inputParameters\": {\n        \"http_request\": {\n          \"uri\": \"http://localhost:9200/conductor/_search?size=10\",\n          \"method\": \"GET\"\n        }\n      },\n      \"type\": \"HTTP\"\n    },\n    {\n      \"name\": \"task_30\",\n      \"taskReferenceName\": \"task_30\",\n      \"inputParameters\": {\n        \"statuses\": \"${get_es_1.output..status}\",\n        \"workflowIds\": \"${get_es_1.output..workflowId}\"\n      },\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"outputParameters\": {\n    \"statues\": \"${get_es_1.output..status}\",\n    \"workflowIds\": \"${get_es_1.output..workflowId}\"\n  },\n  \"ownerEmail\": \"example@email.com\",\n  \"schemaVersion\": 2\n}\n"
  },
  {
    "path": "rest/src/main/resources/kitchensink/sub_flow_1.json",
    "content": "{\n  \"name\": \"sub_flow_1\",\n  \"description\": \"A Simple sub-workflow with 2 tasks\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"task_5\",\n      \"taskReferenceName\": \"task_5\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"task_6\",\n      \"taskReferenceName\": \"task_6\",\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"ownerEmail\": \"example@email.com\"\n}"
  },
  {
    "path": "rest/src/main/resources/kitchensink/wf1.json",
    "content": "{\n  \"createTime\": 1477681181098,\n  \"updateTime\": 1478835878290,\n  \"name\": \"main_workflow\",\n  \"description\": \"Kitchensink workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"task_1\",\n      \"taskReferenceName\": \"task_1\",\n      \"inputParameters\": {\n        \"mod\": \"workflow.input.mod\",\n        \"oddEven\": \"workflow.input.oddEven\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"dyntask\",\n      \"taskReferenceName\": \"task_2\",\n      \"inputParameters\": {\n        \"taskToExecute\": \"workflow.input.task2Name\"\n      },\n      \"type\": \"DYNAMIC\",\n      \"dynamicTaskNameParam\": \"taskToExecute\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"task_3\",\n      \"taskReferenceName\": \"task_3\",\n      \"inputParameters\": {\n        \"mod\": \"task_2.output.mod\",\n        \"oddEven\": \"task_2.output.oddEven\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"oddEvenDecision\",\n      \"taskReferenceName\": \"oddEvenDecision\",\n      \"inputParameters\": {\n        \"oddEven\": \"task_3.output.oddEven\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"oddEven\",\n      \"decisionCases\": {\n        \"0\": [\n          {\n            \"name\": \"task_4\",\n            \"taskReferenceName\": \"task_4\",\n            \"inputParameters\": {\n              \"mod\": \"task_3.output.mod\",\n              \"oddEven\": \"task_3.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"dynamic_fanout\",\n            \"taskReferenceName\": \"fanout1\",\n            \"inputParameters\": {\n              \"dynamicTasks\": \"task_4.output.dynamicTasks\",\n              \"input\": \"task_4.output.inputs\"\n            },\n            \"type\": \"FORK_JOIN_DYNAMIC\",\n            \"dynamicForkTasksParam\": \"dynamicTasks\",\n            \"dynamicForkTasksInputParamName\": \"input\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"dynamic_join\",\n            \"taskReferenceName\": \"join1\",\n            \"type\": \"JOIN\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"task_5\",\n            \"taskReferenceName\": \"task_5\",\n            \"inputParameters\": {\n              \"mod\": \"task_4.output.mod\",\n              \"oddEven\": \"task_4.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"task_6\",\n            \"taskReferenceName\": \"task_6\",\n            \"inputParameters\": {\n              \"mod\": \"task_5.output.mod\",\n              \"oddEven\": \"task_5.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          }\n        ],\n        \"1\": [\n          {\n            \"name\": \"task_7\",\n            \"taskReferenceName\": \"task_7\",\n            \"inputParameters\": {\n              \"mod\": \"task_3.output.mod\",\n              \"oddEven\": \"task_3.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"task_8\",\n            \"taskReferenceName\": \"task_8\",\n            \"inputParameters\": {\n              \"mod\": \"task_7.output.mod\",\n              \"oddEven\": \"task_7.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"task_9\",\n            \"taskReferenceName\": \"task_9\",\n            \"inputParameters\": {\n              \"mod\": \"task_8.output.mod\",\n              \"oddEven\": \"task_8.output.oddEven\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"modDecision\",\n            \"taskReferenceName\": \"modDecision\",\n            \"inputParameters\": {\n              \"mod\": \"task_8.output.mod\"\n            },\n            \"type\": \"DECISION\",\n            \"caseValueParam\": \"mod\",\n            \"decisionCases\": {\n              \"0\": [\n                {\n                  \"name\": \"task_12\",\n                  \"taskReferenceName\": \"task_12\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_9.output.mod\",\n                    \"oddEven\": \"task_9.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                },\n                {\n                  \"name\": \"task_13\",\n                  \"taskReferenceName\": \"task_13\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_12.output.mod\",\n                    \"oddEven\": \"task_12.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf1\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_12.output.mod\",\n                    \"oddEven\": \"task_12.output.oddEven\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true,\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ],\n              \"1\": [\n                {\n                  \"name\": \"task_15\",\n                  \"taskReferenceName\": \"task_15\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_9.output.mod\",\n                    \"oddEven\": \"task_9.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                },\n                {\n                  \"name\": \"task_16\",\n                  \"taskReferenceName\": \"task_16\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_15.output.mod\",\n                    \"oddEven\": \"task_15.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf2\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_12.output.mod\",\n                    \"oddEven\": \"task_12.output.oddEven\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true,\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                }\n              ],\n              \"4\": [\n                {\n                  \"name\": \"task_18\",\n                  \"taskReferenceName\": \"task_18\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_9.output.mod\",\n                    \"oddEven\": \"task_9.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                },\n                {\n                  \"name\": \"task_19\",\n                  \"taskReferenceName\": \"task_19\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_18.output.mod\",\n                    \"oddEven\": \"task_18.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                }\n              ],\n              \"5\": [\n                {\n                  \"name\": \"task_21\",\n                  \"taskReferenceName\": \"task_21\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_9.output.mod\",\n                    \"oddEven\": \"task_9.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                },\n                {\n                  \"name\": \"sub_workflow_x\",\n                  \"taskReferenceName\": \"wf3\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_12.output.mod\",\n                    \"oddEven\": \"task_12.output.oddEven\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true,\n                  \"subWorkflowParam\": {\n                    \"name\": \"sub_flow_1\",\n                    \"version\": 1\n                  }\n                },\n                {\n                  \"name\": \"task_22\",\n                  \"taskReferenceName\": \"task_22\",\n                  \"inputParameters\": {\n                    \"mod\": \"task_21.output.mod\",\n                    \"oddEven\": \"task_21.output.oddEven\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"callbackFromWorker\": true\n                }\n              ]\n            },\n            \"defaultCase\": [\n              {\n                \"name\": \"task_24\",\n                \"taskReferenceName\": \"task_24\",\n                \"inputParameters\": {\n                  \"mod\": \"task_9.output.mod\",\n                  \"oddEven\": \"task_9.output.oddEven\"\n                },\n                \"type\": \"SIMPLE\",\n                \"startDelay\": 0,\n                \"callbackFromWorker\": true\n              },\n              {\n                \"name\": \"sub_workflow_x\",\n                \"taskReferenceName\": \"wf4\",\n                \"inputParameters\": {\n                  \"mod\": \"task_12.output.mod\",\n                  \"oddEven\": \"task_12.output.oddEven\"\n                },\n                \"type\": \"SUB_WORKFLOW\",\n                \"startDelay\": 0,\n                \"callbackFromWorker\": true,\n                \"subWorkflowParam\": {\n                  \"name\": \"sub_flow_1\",\n                  \"version\": 1\n                }\n              },\n              {\n                \"name\": \"task_25\",\n                \"taskReferenceName\": \"task_25\",\n                \"inputParameters\": {\n                  \"mod\": \"task_24.output.mod\",\n                  \"oddEven\": \"task_24.output.oddEven\"\n                },\n                \"type\": \"SIMPLE\",\n                \"startDelay\": 0,\n                \"callbackFromWorker\": true\n              }\n            ],\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          }\n        ]\n      },\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"task_28\",\n      \"taskReferenceName\": \"task_28\",\n      \"inputParameters\": {\n        \"mod\": \"task_3.output.mod\",\n        \"oddEven\": \"task_3.output.oddEven\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"task_29\",\n      \"taskReferenceName\": \"task_29\",\n      \"inputParameters\": {\n        \"mod\": \"task_28.output.mod\",\n        \"oddEven\": \"task_28.output.oddEven\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"task_30\",\n      \"taskReferenceName\": \"task_30\",\n      \"inputParameters\": {\n        \"mod\": \"task_29.output.mod\",\n        \"oddEven\": \"task_29.output.oddEven\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    }\n  ],\n  \"schemaVersion\": 1\n}"
  },
  {
    "path": "rest/src/main/resources/kitchensink/wf2.json",
    "content": "{\n  \"createTime\": 1477681181098,\n  \"updateTime\": 1478837752600,\n  \"name\": \"sub_flow_1\",\n  \"description\": \"sub workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"task_5\",\n      \"taskReferenceName\": \"task_5\",\n      \"inputParameters\": {\n        \"mod\": \"${workflow.input.mod}\",\n        \"oddEven\": \"${workflow.input.oddEven}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"task_28\",\n      \"taskReferenceName\": \"task_28\",\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"fork_join\",\n      \"taskReferenceName\": \"forkx\",\n      \"type\": \"FORK_JOIN\",\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"task_10\",\n            \"taskReferenceName\": \"task_10\",\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"task_11\",\n            \"taskReferenceName\": \"task_11\",\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          }\n        ],\n        [\n          {\n            \"name\": \"task_20\",\n            \"taskReferenceName\": \"task_20\",\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          },\n          {\n            \"name\": \"task_21\",\n            \"taskReferenceName\": \"task_21\",\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"callbackFromWorker\": true\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"join\",\n      \"type\": \"JOIN\",\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"task_21\",\n        \"task_11\"\n      ],\n      \"callbackFromWorker\": true\n    },\n    {\n      \"name\": \"task_30\",\n      \"taskReferenceName\": \"task_30\",\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"callbackFromWorker\": true\n    }\n  ],\n  \"outputParameters\": {\n    \"mod\": \"${workflow.input.mod}\",\n    \"oddEven\": \"${workflow.input.oddEven}\"\n  },\n  \"schemaVersion\": 2\n}"
  },
  {
    "path": "rest/src/main/resources/static/index.html",
    "content": "<!--\n\n    Copyright 2020 Netflix, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n-->\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Netflix Conductor</title>\n    <!-- CSS -->\n    <style type=\"text/css\">\n        body {\n            margin: 0px auto;\n            width: 100%;\n        }\n\n        div.container {\n            margin: 0px auto;\n            font-family: sans-serif;\n            text-align: center;\n        }\n\n        div.header1 {\n            font-size: 300%;\n        }\n\n        div {\n            padding: 10px 10px 10px 10px;\n        }\n    </style>\n</head>\n\n<body>\n<div class=\"container\">\n    <div class=\"header1\">\n        <img src='logo.png' alt=\"Conductor Logo\">\n    </div>\n    <br/><br/>\n    <div>\n        <a href=\"/swagger-ui.html\">Swagger Documentation</a>\n    </div>\n    <div>\n        <a href=\"https://conductor.netflix.com\" target=\"_blank\">User Guide</a>\n    </div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "rest/src/test/java/com/netflix/conductor/rest/controllers/AdminResourceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mock;\n\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.service.AdminService;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class AdminResourceTest {\n\n    @Mock private AdminService mockAdminService;\n\n    @Mock private AdminResource adminResource;\n\n    @Before\n    public void before() {\n        this.mockAdminService = mock(AdminService.class);\n        this.adminResource = new AdminResource(mockAdminService);\n    }\n\n    @Test\n    public void testGetAllConfig() {\n        Map<String, Object> configs = new HashMap<>();\n        configs.put(\"config1\", \"test\");\n        when(mockAdminService.getAllConfig()).thenReturn(configs);\n        assertEquals(configs, adminResource.getAllConfig());\n    }\n\n    @Test\n    public void testView() {\n        Task task = new Task();\n        task.setReferenceTaskName(\"test\");\n        List<Task> listOfTask = new ArrayList<>();\n        listOfTask.add(task);\n        when(mockAdminService.getListOfPendingTask(anyString(), anyInt(), anyInt()))\n                .thenReturn(listOfTask);\n        assertEquals(listOfTask, adminResource.view(\"testTask\", 0, 100));\n    }\n\n    @Test\n    public void testRequeueSweep() {\n        String workflowId = \"w123\";\n        when(mockAdminService.requeueSweep(anyString())).thenReturn(workflowId);\n        assertEquals(workflowId, adminResource.requeueSweep(workflowId));\n    }\n\n    @Test\n    public void testGetEventQueues() {\n        adminResource.getEventQueues(false);\n        verify(mockAdminService, times(1)).getEventQueues(anyBoolean());\n    }\n}\n"
  },
  {
    "path": "rest/src/test/java/com/netflix/conductor/rest/controllers/EventResourceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mock;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.service.EventService;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class EventResourceTest {\n\n    private EventResource eventResource;\n\n    @Mock private EventService mockEventService;\n\n    @Before\n    public void setUp() {\n        this.mockEventService = mock(EventService.class);\n        this.eventResource = new EventResource(this.mockEventService);\n    }\n\n    @Test\n    public void testAddEventHandler() {\n        EventHandler eventHandler = new EventHandler();\n        eventResource.addEventHandler(eventHandler);\n        verify(mockEventService, times(1)).addEventHandler(any(EventHandler.class));\n    }\n\n    @Test\n    public void testUpdateEventHandler() {\n        EventHandler eventHandler = new EventHandler();\n        eventResource.updateEventHandler(eventHandler);\n        verify(mockEventService, times(1)).updateEventHandler(any(EventHandler.class));\n    }\n\n    @Test\n    public void testRemoveEventHandlerStatus() {\n        eventResource.removeEventHandlerStatus(\"testEvent\");\n        verify(mockEventService, times(1)).removeEventHandlerStatus(anyString());\n    }\n\n    @Test\n    public void testGetEventHandlersForEvent() {\n        EventHandler eventHandler = new EventHandler();\n        eventResource.addEventHandler(eventHandler);\n        List<EventHandler> listOfEventHandler = new ArrayList<>();\n        listOfEventHandler.add(eventHandler);\n        when(mockEventService.getEventHandlersForEvent(anyString(), anyBoolean()))\n                .thenReturn(listOfEventHandler);\n        assertEquals(listOfEventHandler, eventResource.getEventHandlersForEvent(\"testEvent\", true));\n    }\n\n    @Test\n    public void testGetEventHandlers() {\n        EventHandler eventHandler = new EventHandler();\n        eventResource.addEventHandler(eventHandler);\n        List<EventHandler> listOfEventHandler = new ArrayList<>();\n        listOfEventHandler.add(eventHandler);\n        when(mockEventService.getEventHandlers()).thenReturn(listOfEventHandler);\n        assertEquals(listOfEventHandler, eventResource.getEventHandlers());\n    }\n}\n"
  },
  {
    "path": "rest/src/test/java/com/netflix/conductor/rest/controllers/MetadataResourceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.service.MetadataService;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyList;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class MetadataResourceTest {\n\n    private MetadataResource metadataResource;\n\n    private MetadataService mockMetadataService;\n\n    @Before\n    public void before() {\n        this.mockMetadataService = mock(MetadataService.class);\n        this.metadataResource = new MetadataResource(this.mockMetadataService);\n    }\n\n    @Test\n    public void testCreateWorkflow() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        metadataResource.create(workflowDef);\n        verify(mockMetadataService, times(1)).registerWorkflowDef(any(WorkflowDef.class));\n    }\n\n    @Test\n    public void testValidateWorkflow() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        metadataResource.validate(workflowDef);\n        verify(mockMetadataService, times(1)).validateWorkflowDef(any(WorkflowDef.class));\n    }\n\n    @Test\n    public void testUpdateWorkflow() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        List<WorkflowDef> listOfWorkflowDef = new ArrayList<>();\n        listOfWorkflowDef.add(workflowDef);\n        metadataResource.update(listOfWorkflowDef);\n        verify(mockMetadataService, times(1)).updateWorkflowDef(anyList());\n    }\n\n    @Test\n    public void testGetWorkflowDef() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test\");\n        workflowDef.setVersion(1);\n        workflowDef.setDescription(\"test\");\n\n        when(mockMetadataService.getWorkflowDef(anyString(), any())).thenReturn(workflowDef);\n        assertEquals(workflowDef, metadataResource.get(\"test\", 1));\n    }\n\n    @Test\n    public void testGetAllWorkflowDef() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test\");\n        workflowDef.setVersion(1);\n        workflowDef.setDescription(\"test\");\n\n        List<WorkflowDef> listOfWorkflowDef = new ArrayList<>();\n        listOfWorkflowDef.add(workflowDef);\n\n        when(mockMetadataService.getWorkflowDefs()).thenReturn(listOfWorkflowDef);\n        assertEquals(listOfWorkflowDef, metadataResource.getAll());\n    }\n\n    @Test\n    public void testGetAllWorkflowDefLatestVersions() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test\");\n        workflowDef.setVersion(1);\n        workflowDef.setDescription(\"test\");\n\n        List<WorkflowDef> listOfWorkflowDef = new ArrayList<>();\n        listOfWorkflowDef.add(workflowDef);\n\n        when(mockMetadataService.getWorkflowDefsLatestVersions()).thenReturn(listOfWorkflowDef);\n        assertEquals(listOfWorkflowDef, metadataResource.getAllWorkflowsWithLatestVersions());\n    }\n\n    @Test\n    public void testUnregisterWorkflowDef() throws Exception {\n        metadataResource.unregisterWorkflowDef(\"test\", 1);\n        verify(mockMetadataService, times(1)).unregisterWorkflowDef(anyString(), any());\n    }\n\n    @Test\n    public void testRegisterListOfTaskDef() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test\");\n        taskDef.setDescription(\"desc\");\n        List<TaskDef> listOfTaskDefs = new ArrayList<>();\n        listOfTaskDefs.add(taskDef);\n\n        metadataResource.registerTaskDef(listOfTaskDefs);\n        verify(mockMetadataService, times(1)).registerTaskDef(listOfTaskDefs);\n    }\n\n    @Test\n    public void testRegisterTaskDef() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test\");\n        taskDef.setDescription(\"desc\");\n        metadataResource.registerTaskDef(taskDef);\n        verify(mockMetadataService, times(1)).updateTaskDef(taskDef);\n    }\n\n    @Test\n    public void testGetAllTaskDefs() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test\");\n        taskDef.setDescription(\"desc\");\n        List<TaskDef> listOfTaskDefs = new ArrayList<>();\n        listOfTaskDefs.add(taskDef);\n\n        when(mockMetadataService.getTaskDefs()).thenReturn(listOfTaskDefs);\n        assertEquals(listOfTaskDefs, metadataResource.getTaskDefs());\n    }\n\n    @Test\n    public void testGetTaskDef() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"test\");\n        taskDef.setDescription(\"desc\");\n\n        when(mockMetadataService.getTaskDef(anyString())).thenReturn(taskDef);\n        assertEquals(taskDef, metadataResource.getTaskDef(\"test\"));\n    }\n\n    @Test\n    public void testUnregisterTaskDef() {\n        metadataResource.unregisterTaskDef(\"test\");\n        verify(mockMetadataService, times(1)).unregisterTaskDef(anyString());\n    }\n}\n"
  },
  {
    "path": "rest/src/test/java/com/netflix/conductor/rest/controllers/TaskResourceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.springframework.http.ResponseEntity;\n\nimport com.netflix.conductor.common.metadata.tasks.PollData;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.TaskExecLog;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.service.TaskService;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyList;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class TaskResourceTest {\n\n    private TaskService mockTaskService;\n\n    private TaskResource taskResource;\n\n    @Before\n    public void before() {\n        this.mockTaskService = mock(TaskService.class);\n        this.taskResource = new TaskResource(this.mockTaskService);\n    }\n\n    @Test\n    public void testPoll() {\n        Task task = new Task();\n        task.setTaskType(\"SIMPLE\");\n        task.setWorkerId(\"123\");\n        task.setDomain(\"test\");\n\n        when(mockTaskService.poll(anyString(), anyString(), anyString())).thenReturn(task);\n        assertEquals(ResponseEntity.ok(task), taskResource.poll(\"SIMPLE\", \"123\", \"test\"));\n    }\n\n    @Test\n    public void testBatchPoll() {\n        Task task = new Task();\n        task.setTaskType(\"SIMPLE\");\n        task.setWorkerId(\"123\");\n        task.setDomain(\"test\");\n        List<Task> listOfTasks = new ArrayList<>();\n        listOfTasks.add(task);\n\n        when(mockTaskService.batchPoll(anyString(), anyString(), anyString(), anyInt(), anyInt()))\n                .thenReturn(listOfTasks);\n        assertEquals(\n                ResponseEntity.ok(listOfTasks),\n                taskResource.batchPoll(\"SIMPLE\", \"123\", \"test\", 1, 100));\n    }\n\n    @Test\n    public void testUpdateTask() {\n        TaskResult taskResult = new TaskResult();\n        taskResult.setStatus(TaskResult.Status.COMPLETED);\n        taskResult.setTaskId(\"123\");\n        when(mockTaskService.updateTask(any(TaskResult.class))).thenReturn(\"123\");\n        assertEquals(\"123\", taskResource.updateTask(taskResult));\n    }\n\n    @Test\n    public void testLog() {\n        taskResource.log(\"123\", \"test log\");\n        verify(mockTaskService, times(1)).log(anyString(), anyString());\n    }\n\n    @Test\n    public void testGetTaskLogs() {\n        List<TaskExecLog> listOfLogs = new ArrayList<>();\n        listOfLogs.add(new TaskExecLog(\"test log\"));\n        when(mockTaskService.getTaskLogs(anyString())).thenReturn(listOfLogs);\n        assertEquals(listOfLogs, taskResource.getTaskLogs(\"123\"));\n    }\n\n    @Test\n    public void testGetTask() {\n        Task task = new Task();\n        task.setTaskType(\"SIMPLE\");\n        task.setWorkerId(\"123\");\n        task.setDomain(\"test\");\n        task.setStatus(Task.Status.IN_PROGRESS);\n        when(mockTaskService.getTask(anyString())).thenReturn(task);\n        ResponseEntity<Task> entity = taskResource.getTask(\"123\");\n        assertNotNull(entity);\n        assertEquals(task, entity.getBody());\n    }\n\n    @Test\n    public void testSize() {\n        Map<String, Integer> map = new HashMap<>();\n        map.put(\"test1\", 1);\n        map.put(\"test2\", 2);\n\n        List<String> list = new ArrayList<>();\n        list.add(\"test1\");\n        list.add(\"test2\");\n\n        when(mockTaskService.getTaskQueueSizes(anyList())).thenReturn(map);\n        assertEquals(map, taskResource.size(list));\n    }\n\n    @Test\n    public void testAllVerbose() {\n        Map<String, Long> map = new HashMap<>();\n        map.put(\"queue1\", 1L);\n        map.put(\"queue2\", 2L);\n\n        Map<String, Map<String, Long>> mapOfMap = new HashMap<>();\n        mapOfMap.put(\"queue\", map);\n\n        Map<String, Map<String, Map<String, Long>>> queueSizeMap = new HashMap<>();\n        queueSizeMap.put(\"queue\", mapOfMap);\n\n        when(mockTaskService.allVerbose()).thenReturn(queueSizeMap);\n        assertEquals(queueSizeMap, taskResource.allVerbose());\n    }\n\n    @Test\n    public void testQueueDetails() {\n        Map<String, Long> map = new HashMap<>();\n        map.put(\"queue1\", 1L);\n        map.put(\"queue2\", 2L);\n\n        when(mockTaskService.getAllQueueDetails()).thenReturn(map);\n        assertEquals(map, taskResource.all());\n    }\n\n    @Test\n    public void testGetPollData() {\n        PollData pollData = new PollData(\"queue\", \"test\", \"w123\", 100);\n        List<PollData> listOfPollData = new ArrayList<>();\n        listOfPollData.add(pollData);\n\n        when(mockTaskService.getPollData(anyString())).thenReturn(listOfPollData);\n        assertEquals(listOfPollData, taskResource.getPollData(\"w123\"));\n    }\n\n    @Test\n    public void testGetAllPollData() {\n        PollData pollData = new PollData(\"queue\", \"test\", \"w123\", 100);\n        List<PollData> listOfPollData = new ArrayList<>();\n        listOfPollData.add(pollData);\n\n        when(mockTaskService.getAllPollData()).thenReturn(listOfPollData);\n        assertEquals(listOfPollData, taskResource.getAllPollData());\n    }\n\n    @Test\n    public void testRequeueTaskType() {\n        when(mockTaskService.requeuePendingTask(anyString())).thenReturn(\"1\");\n        assertEquals(\"1\", taskResource.requeuePendingTask(\"SIMPLE\"));\n    }\n\n    @Test\n    public void testSearch() {\n        Task task = new Task();\n        task.setTaskType(\"SIMPLE\");\n        task.setWorkerId(\"123\");\n        task.setDomain(\"test\");\n        task.setStatus(Task.Status.IN_PROGRESS);\n        TaskSummary taskSummary = new TaskSummary(task);\n        List<TaskSummary> listOfTaskSummary = Collections.singletonList(taskSummary);\n        SearchResult<TaskSummary> searchResult = new SearchResult<>(100, listOfTaskSummary);\n\n        when(mockTaskService.search(0, 100, \"asc\", \"*\", \"*\")).thenReturn(searchResult);\n        assertEquals(searchResult, taskResource.search(0, 100, \"asc\", \"*\", \"*\"));\n    }\n\n    @Test\n    public void testSearchV2() {\n        Task task = new Task();\n        task.setTaskType(\"SIMPLE\");\n        task.setWorkerId(\"123\");\n        task.setDomain(\"test\");\n        task.setStatus(Task.Status.IN_PROGRESS);\n        List<Task> listOfTasks = Collections.singletonList(task);\n        SearchResult<Task> searchResult = new SearchResult<>(100, listOfTasks);\n\n        when(mockTaskService.searchV2(0, 100, \"asc\", \"*\", \"*\")).thenReturn(searchResult);\n        assertEquals(searchResult, taskResource.searchV2(0, 100, \"asc\", \"*\", \"*\"));\n    }\n\n    @Test\n    public void testGetExternalStorageLocation() {\n        ExternalStorageLocation externalStorageLocation = mock(ExternalStorageLocation.class);\n        when(mockTaskService.getExternalStorageLocation(\"path\", \"operation\", \"payloadType\"))\n                .thenReturn(externalStorageLocation);\n        assertEquals(\n                externalStorageLocation,\n                taskResource.getExternalStorageLocation(\"path\", \"operation\", \"payloadType\"));\n    }\n}\n"
  },
  {
    "path": "rest/src/test/java/com/netflix/conductor/rest/controllers/WorkflowResourceTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.rest.controllers;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mock;\n\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.service.WorkflowService;\nimport com.netflix.conductor.service.WorkflowTestService;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyList;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.isNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\npublic class WorkflowResourceTest {\n\n    @Mock private WorkflowService mockWorkflowService;\n\n    @Mock private WorkflowTestService mockWorkflowTestService;\n\n    private WorkflowResource workflowResource;\n\n    @Before\n    public void before() {\n        this.mockWorkflowService = mock(WorkflowService.class);\n        this.mockWorkflowTestService = mock(WorkflowTestService.class);\n        this.workflowResource =\n                new WorkflowResource(this.mockWorkflowService, this.mockWorkflowTestService);\n    }\n\n    @Test\n    public void testStartWorkflow() {\n        StartWorkflowRequest startWorkflowRequest = new StartWorkflowRequest();\n        startWorkflowRequest.setName(\"w123\");\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"1\", \"abc\");\n        startWorkflowRequest.setInput(input);\n        String workflowID = \"w112\";\n        when(mockWorkflowService.startWorkflow(any(StartWorkflowRequest.class)))\n                .thenReturn(workflowID);\n        assertEquals(\"w112\", workflowResource.startWorkflow(startWorkflowRequest));\n    }\n\n    @Test\n    public void testStartWorkflowParam() {\n        Map<String, Object> input = new HashMap<>();\n        input.put(\"1\", \"abc\");\n        String workflowID = \"w112\";\n        when(mockWorkflowService.startWorkflow(\n                        anyString(), anyInt(), anyString(), anyInt(), anyMap()))\n                .thenReturn(workflowID);\n        assertEquals(\"w112\", workflowResource.startWorkflow(\"test1\", 1, \"c123\", 0, input));\n    }\n\n    @Test\n    public void getWorkflows() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"123\");\n        ArrayList<Workflow> listOfWorkflows =\n                new ArrayList<>() {\n                    {\n                        add(workflow);\n                    }\n                };\n        when(mockWorkflowService.getWorkflows(anyString(), anyString(), anyBoolean(), anyBoolean()))\n                .thenReturn(listOfWorkflows);\n        assertEquals(listOfWorkflows, workflowResource.getWorkflows(\"test1\", \"123\", true, true));\n    }\n\n    @Test\n    public void testGetWorklfowsMultipleCorrelationId() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"c123\");\n\n        List<Workflow> workflowArrayList =\n                new ArrayList<>() {\n                    {\n                        add(workflow);\n                    }\n                };\n\n        List<String> correlationIdList =\n                new ArrayList<>() {\n                    {\n                        add(\"c123\");\n                    }\n                };\n\n        Map<String, List<Workflow>> workflowMap = new HashMap<>();\n        workflowMap.put(\"c123\", workflowArrayList);\n\n        when(mockWorkflowService.getWorkflows(anyString(), anyBoolean(), anyBoolean(), anyList()))\n                .thenReturn(workflowMap);\n        assertEquals(\n                workflowMap, workflowResource.getWorkflows(\"test\", true, true, correlationIdList));\n    }\n\n    @Test\n    public void testGetExecutionStatus() {\n        Workflow workflow = new Workflow();\n        workflow.setCorrelationId(\"c123\");\n\n        when(mockWorkflowService.getExecutionStatus(anyString(), anyBoolean()))\n                .thenReturn(workflow);\n        assertEquals(workflow, workflowResource.getExecutionStatus(\"w123\", true));\n    }\n\n    @Test\n    public void testDelete() {\n        workflowResource.delete(\"w123\", true);\n        verify(mockWorkflowService, times(1)).deleteWorkflow(anyString(), anyBoolean());\n    }\n\n    @Test\n    public void testGetRunningWorkflow() {\n        List<String> listOfWorklfows =\n                new ArrayList<>() {\n                    {\n                        add(\"w123\");\n                    }\n                };\n        when(mockWorkflowService.getRunningWorkflows(anyString(), anyInt(), anyLong(), anyLong()))\n                .thenReturn(listOfWorklfows);\n        assertEquals(listOfWorklfows, workflowResource.getRunningWorkflow(\"w123\", 1, 12L, 13L));\n    }\n\n    @Test\n    public void testDecide() {\n        workflowResource.decide(\"w123\");\n        verify(mockWorkflowService, times(1)).decideWorkflow(anyString());\n    }\n\n    @Test\n    public void testPauseWorkflow() {\n        workflowResource.pauseWorkflow(\"w123\");\n        verify(mockWorkflowService, times(1)).pauseWorkflow(anyString());\n    }\n\n    @Test\n    public void testResumeWorkflow() {\n        workflowResource.resumeWorkflow(\"test\");\n        verify(mockWorkflowService, times(1)).resumeWorkflow(anyString());\n    }\n\n    @Test\n    public void testSkipTaskFromWorkflow() {\n        workflowResource.skipTaskFromWorkflow(\"test\", \"testTask\", null);\n        verify(mockWorkflowService, times(1))\n                .skipTaskFromWorkflow(anyString(), anyString(), isNull());\n    }\n\n    @Test\n    public void testRerun() {\n        RerunWorkflowRequest request = new RerunWorkflowRequest();\n        workflowResource.rerun(\"test\", request);\n        verify(mockWorkflowService, times(1))\n                .rerunWorkflow(anyString(), any(RerunWorkflowRequest.class));\n    }\n\n    @Test\n    public void restart() {\n        workflowResource.restart(\"w123\", false);\n        verify(mockWorkflowService, times(1)).restartWorkflow(anyString(), anyBoolean());\n    }\n\n    @Test\n    public void testRetry() {\n        workflowResource.retry(\"w123\", false);\n        verify(mockWorkflowService, times(1)).retryWorkflow(anyString(), anyBoolean());\n    }\n\n    @Test\n    public void testResetWorkflow() {\n        workflowResource.resetWorkflow(\"w123\");\n        verify(mockWorkflowService, times(1)).resetWorkflow(anyString());\n    }\n\n    @Test\n    public void testTerminate() {\n        workflowResource.terminate(\"w123\", \"test\");\n        verify(mockWorkflowService, times(1)).terminateWorkflow(anyString(), anyString());\n    }\n\n    @Test\n    public void testSearch() {\n        workflowResource.search(0, 100, \"asc\", \"*\", \"*\");\n        verify(mockWorkflowService, times(1))\n                .searchWorkflows(anyInt(), anyInt(), anyString(), anyString(), anyString());\n    }\n\n    @Test\n    public void testSearchV2() {\n        workflowResource.searchV2(0, 100, \"asc\", \"*\", \"*\");\n        verify(mockWorkflowService).searchWorkflowsV2(0, 100, \"asc\", \"*\", \"*\");\n    }\n\n    @Test\n    public void testSearchWorkflowsByTasks() {\n        workflowResource.searchWorkflowsByTasks(0, 100, \"asc\", \"*\", \"*\");\n        verify(mockWorkflowService, times(1))\n                .searchWorkflowsByTasks(anyInt(), anyInt(), anyString(), anyString(), anyString());\n    }\n\n    @Test\n    public void testSearchWorkflowsByTasksV2() {\n        workflowResource.searchWorkflowsByTasksV2(0, 100, \"asc\", \"*\", \"*\");\n        verify(mockWorkflowService).searchWorkflowsByTasksV2(0, 100, \"asc\", \"*\", \"*\");\n    }\n\n    @Test\n    public void testGetExternalStorageLocation() {\n        workflowResource.getExternalStorageLocation(\"path\", \"operation\", \"payloadType\");\n        verify(mockWorkflowService).getExternalStorageLocation(\"path\", \"operation\", \"payloadType\");\n    }\n}\n"
  },
  {
    "path": "server/build.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\nplugins {\n    id 'org.springframework.boot'\n}\n\ndependencies {\n\n    implementation project(':conductor-rest')\n    implementation project(':conductor-core')\n    implementation project(':conductor-redis-persistence')\n    implementation project(':conductor-cassandra-persistence')\n    implementation project(':conductor-es6-persistence')\n    implementation project(':conductor-grpc-server')\n    implementation project(':conductor-redis-lock')\n    implementation project(':conductor-redis-concurrency-limit')\n    implementation project(':conductor-http-task')\n    implementation project(':conductor-json-jq-task')\n    implementation project(':conductor-awss3-storage')\n    implementation project(':conductor-awssqs-event-queue')\n\n    implementation 'org.springframework.boot:spring-boot-starter'\n    implementation 'org.springframework.boot:spring-boot-starter-validation'\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n    implementation 'org.springframework.retry:spring-retry'\n\n    implementation 'org.springframework.boot:spring-boot-starter-log4j2'\n    implementation 'org.apache.logging.log4j:log4j-web'\n\n    implementation 'org.springframework.boot:spring-boot-starter-actuator'\n    implementation \"io.orkes.queues:orkes-conductor-queues:${revOrkesQueues}\"\n\n    implementation \"org.springdoc:springdoc-openapi-ui:${revOpenapi}\"\n\n    runtimeOnly \"org.glassfish.jaxb:jaxb-runtime:${revJAXB}\"\n\n    testImplementation project(':conductor-rest')\n    testImplementation project(':conductor-common')\n    testImplementation \"io.grpc:grpc-testing:${revGrpc}\"\n    testImplementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n    testImplementation \"io.grpc:grpc-protobuf:${revGrpc}\"\n    testImplementation \"io.grpc:grpc-stub:${revGrpc}\"\n}\n\njar {\n    enabled = true\n}\n\nbootJar {\n    mainClass = 'com.netflix.conductor.Conductor'\n    classifier = 'boot'\n}\n\n// https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#integrating-with-actuator.build-info\n// This will configure a BuildInfo task named bootBuildInfo\nspringBoot {\n    buildInfo()\n}\n\ncompileJava.dependsOn bootBuildInfo\n"
  },
  {
    "path": "server/src/main/java/com/netflix/conductor/Conductor.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor;\n\nimport java.io.IOException;\nimport java.util.Properties;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.core.io.FileSystemResource;\n\n// Prevents from the datasource beans to be loaded, AS they are needed only for specific databases.\n// In case that SQL database is selected this class will be imported back in the appropriate\n// database persistence module.\n@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)\n@ComponentScan(basePackages = {\"com.netflix.conductor\", \"io.orkes.conductor\"})\npublic class Conductor {\n\n    private static final Logger log = LoggerFactory.getLogger(Conductor.class);\n\n    public static void main(String[] args) throws IOException {\n        loadExternalConfig();\n\n        SpringApplication.run(Conductor.class, args);\n    }\n\n    /**\n     * Reads properties from the location specified in <code>CONDUCTOR_CONFIG_FILE</code> and sets\n     * them as system properties so they override the default properties.\n     *\n     * <p>Spring Boot property hierarchy is documented here,\n     * https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config\n     *\n     * @throws IOException if file can't be read.\n     */\n    private static void loadExternalConfig() throws IOException {\n        String configFile = System.getProperty(\"CONDUCTOR_CONFIG_FILE\");\n        if (StringUtils.isNotBlank(configFile)) {\n            FileSystemResource resource = new FileSystemResource(configFile);\n            if (resource.exists()) {\n                Properties properties = new Properties();\n                properties.load(resource.getInputStream());\n                properties.forEach(\n                        (key, value) -> System.setProperty((String) key, (String) value));\n                log.info(\"Loaded {} properties from {}\", properties.size(), configFile);\n            } else {\n                log.warn(\"Ignoring {} since it does not exist\", configFile);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "server/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"properties\": [\n    {\n      \"name\": \"conductor.db.type\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"The type of database to be used while running the Conductor application.\"\n    },\n    {\n      \"name\": \"conductor.indexing.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enable indexing to elasticsearch. If set to false, a no-op implementation will be used.\"\n    },\n    {\n      \"name\": \"conductor.grpc-server.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enable the gRPC server.\"\n    }\n  ],\n  \"hints\": [\n    {\n      \"name\": \"conductor.db.type\",\n      \"values\": [\n        {\n          \"value\": \"memory\",\n          \"description\": \"Use in-memory redis as the database implementation.\"\n        },\n        {\n          \"value\": \"cassandra\",\n          \"description\": \"Use cassandra as the database implementation.\"\n        },\n        {\n          \"value\": \"mysql\",\n          \"description\": \"Use MySQL as the database implementation.\"\n        },\n        {\n          \"value\": \"postgres\",\n          \"description\": \"Use Postgres as the database implementation.\"\n        },\n        {\n          \"value\": \"dynomite\",\n          \"description\": \"Use Dynomite as the database implementation.\"\n        },\n        {\n          \"value\": \"redis_cluster\",\n          \"description\": \"Use Redis Cluster configuration as the database implementation.\"\n        },\n        {\n          \"value\": \"redis_sentinel\",\n          \"description\": \"Use Redis Sentinel configuration as the database implementation.\"\n        },\n        {\n          \"value\": \"redis_standalone\",\n          \"description\": \"Use Redis Standalone configuration as the database implementation.\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "server/src/main/resources/application.properties",
    "content": "#\n#  Copyright 2021 Netflix, Inc.\n#  <p>\n#  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n#  the License. You may obtain a copy of the License at\n#  <p>\n#  http://www.apache.org/licenses/LICENSE-2.0\n#  <p>\n#  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n#  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n#  specific language governing permissions and limitations under the License.\n#\n\nspring.application.name=conductor\nspringdoc.api-docs.path=/api-docs\nloadSample=true\n\nconductor.db.type=memory\nconductor.queue.type=redis_standalone\n\nconductor.indexing.enabled=false\n\n#Redis configuration details.\n#format is host:port:rack separated by semicolon\n#Auth is supported. Password is taken from host[0]. format: host:port:rack:password\n#conductor.redis.hosts=host1:port:rack;host2:port:rack:host3:port:rack\nconductor.redis.hosts=localhost:6379:us-east-1c\n\n#namespace for the keys stored in Dynomite/Redis\nconductor.redis.workflowNamespacePrefix=\n\n#namespace prefix for the dyno queues\nconductor.redis.queueNamespacePrefix=\n\n#no. of threads allocated to dyno-queues\nqueues.dynomite.threads=10\n\n# By default with dynomite, we want the repair service enabled\nconductor.workflow-repair-service.enabled=true\n\n#non-quorum port used to connect to local redis.  Used by dyno-queues\nconductor.redis.queuesNonQuorumPort=22122\n\n# For a single node dynomite or redis server, make sure the value below is set to same as rack specified in the \"workflow.dynomite.cluster.hosts\" property.\nconductor.redis.availabilityZone=us-east-1c\n#conductor.redis.maxIdleConnections=8\n#conductor.redis.minIdleConnections=5\n#conductor.redis.minEvictableIdleTimeMillis = 1800000\n#conductor.redis.timeBetweenEvictionRunsMillis = -1L\n#conductor.redis.testWhileIdle = false\n#conductor.redis.numTestsPerEvictionRun = 3\n\n#Transport address to elasticsearch\nconductor.elasticsearch.url=localhost:9300\n\n#Name of the elasticsearch cluster\nconductor.elasticsearch.indexName=conductor\n\n#Elasticsearch major release version.\nconductor.elasticsearch.version=6\n#conductor.elasticsearch.version=7\n\n# Default event queue type to listen on for wait task\nconductor.default-event-queue.type=sqs\n\n#zookeeper\n# conductor.zookeeper-lock.connectionString=host1.2181,host2:2181,host3:2181\n# conductor.zookeeper-lock.sessionTimeoutMs\n# conductor.zookeeper-lock.connectionTimeoutMs\n# conductor.zookeeper-lock.namespace\n\n#disable locking during workflow execution\nconductor.app.workflow-execution-lock-enabled=false\nconductor.workflow-execution-lock.type=noop_lock\n\n#Redis cluster settings for locking module\n# conductor.redis-lock.serverType=single\n#Comma separated list of server nodes\n# conductor.redis-lock.serverAddress=redis://127.0.0.1:6379\n#Redis sentinel master name\n# conductor.redis-lock.serverMasterName=master\n# conductor.redis-lock.namespace\n\n#Following properties set for using AMQP events and tasks with conductor:\n#(To enable support of AMQP queues)\n#conductor.event-queues.amqp.enabled=true\n\n# Here are the settings with default values:\n#conductor.event-queues.amqp.hosts=<rabbitmq serverip>\n#conductor.event-queues.amqp.username=<username>\n#conductor.event-queues.amqp.password=<password>\n\n#conductor.event-queues.amqp.virtualHost=/\n#conductor.event-queues.amqp.port=5672\n#conductor.event-queues.amqp.useNio=false\n#conductor.event-queues.amqp.batchSize=1\n#conductor.event-queues.amqp.pollTimeDuration=100ms\n#conductor.event-queues.amqp.queueType=classic\n#conductor.event-queues.amqp.sequentialMsgProcessing=true\n#conductor.event-queues.amqp.connectionTimeoutInMilliSecs=180000\n#conductor.event-queues.amqp.networkRecoveryIntervalInMilliSecs=5000\n#conductor.event-queues.amqp.requestHeartbeatTimeoutInSecs=30\n#conductor.event-queues.amqp.handshakeTimeoutInMilliSecs=180000\n#conductor.event-queues.amqp.maxChannelCount=5000\n#conductor.event-queues.amqp.limit=50\n#conductor.event-queues.amqp.duration=1000\n#conductor.event-queues.amqp.retryType=REGULARINTERVALS\n\n#conductor.event-queues.amqp.useExchange=true( exchange or queue)\n#conductor.event-queues.amqp.listenerQueuePrefix=myqueue\n# Use durable queue ?\n#conductor.event-queues.amqp.durable=false\n# Use exclusive queue ?\n#conductor.event-queues.amqp.exclusive=false\n# Enable support of priorities on queue. Set the max priority on message.\n# Setting is ignored if the value is lower or equals to 0\n#conductor.event-queues.amqp.maxPriority=-1\n\n# To enable Workflow/Task Summary Input/Output JSON Serialization, use the following:\n# conductor.app.summary-input-output-json-serialization.enabled=true\n\n# Additional modules for metrics collection exposed to Prometheus (optional)\n# conductor.metrics-prometheus.enabled=true\n# management.endpoints.web.exposure.include=prometheus\n\n# Additional modules for metrics collection exposed to Datadog (optional)\nmanagement.metrics.export.datadog.enabled=${conductor.metrics-datadog.enabled:false}\nmanagement.metrics.export.datadog.api-key=${conductor.metrics-datadog.api-key:}\n"
  },
  {
    "path": "server/src/main/resources/banner.txt",
    "content": "  ______   ______   .__   __.  _______   __    __    ______ .___________.  ______   .______\n /      | /  __  \\  |  \\ |  | |       \\ |  |  |  |  /      ||           | /  __  \\  |   _  \\\n|  ,----'|  |  |  | |   \\|  | |  .--.  ||  |  |  | |  ,----'`---|  |----`|  |  |  | |  |_)  |\n|  |     |  |  |  | |  . `  | |  |  |  ||  |  |  | |  |         |  |     |  |  |  | |      /\n|  `----.|  `--'  | |  |\\   | |  '--'  ||  `--'  | |  `----.    |  |     |  `--'  | |  |\\  \\----.\n \\______| \\______/  |__| \\__| |_______/  \\______/   \\______|    |__|      \\______/  | _| `._____|\n${application.formatted-version} :::Spring Boot:::${spring-boot.formatted-version}\n"
  },
  {
    "path": "server/src/main/resources/log4j2.xml",
    "content": "<!--\n\n    Copyright 2020 Netflix, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n-->\n<!-- default log configuration -->\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"CONSOLE\">\n            <PatternLayout pattern=\"%-4r [%t] %-5p %c %x - %m%n\"/>\n        </Console>\n    </Appenders>\n\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"CONSOLE\" />\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "server/src/test/java/com/netflix/conductor/common/config/ConductorObjectMapperTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.common.config;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.run.Workflow;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.protobuf.Any;\nimport com.google.protobuf.Struct;\nimport com.google.protobuf.Value;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\n\n/**\n * Tests the customized {@link ObjectMapper} that is used by {@link com.netflix.conductor.Conductor}\n * application.\n */\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)\n@RunWith(SpringRunner.class)\n@TestPropertySource(properties = \"conductor.queue.type=\")\npublic class ConductorObjectMapperTest {\n\n    @Autowired ObjectMapper objectMapper;\n\n    @Test\n    public void testSimpleMapping() throws IOException {\n        assertTrue(objectMapper.canSerialize(Any.class));\n\n        Struct struct1 =\n                Struct.newBuilder()\n                        .putFields(\n                                \"some-key\", Value.newBuilder().setStringValue(\"some-value\").build())\n                        .build();\n\n        Any source = Any.pack(struct1);\n\n        StringWriter buf = new StringWriter();\n        objectMapper.writer().writeValue(buf, source);\n\n        Any dest = objectMapper.reader().forType(Any.class).readValue(buf.toString());\n        assertEquals(source.getTypeUrl(), dest.getTypeUrl());\n\n        Struct struct2 = dest.unpack(Struct.class);\n        assertTrue(struct2.containsFields(\"some-key\"));\n        assertEquals(\n                struct1.getFieldsOrThrow(\"some-key\").getStringValue(),\n                struct2.getFieldsOrThrow(\"some-key\").getStringValue());\n    }\n\n    @Test\n    public void testNullOnWrite() throws JsonProcessingException {\n        Map<String, Object> data = new HashMap<>();\n        data.put(\"someKey\", null);\n        data.put(\"someId\", \"abc123\");\n        String result = objectMapper.writeValueAsString(data);\n        assertTrue(result.contains(\"null\"));\n    }\n\n    @Test\n    public void testWorkflowSerDe() throws IOException {\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"testDef\");\n        workflowDef.setVersion(2);\n\n        Workflow workflow = new Workflow();\n        workflow.setWorkflowDefinition(workflowDef);\n        workflow.setWorkflowId(\"test-workflow-id\");\n        workflow.setStatus(Workflow.WorkflowStatus.RUNNING);\n        workflow.setStartTime(10L);\n        workflow.setInput(null);\n\n        Map<String, Object> data = new HashMap<>();\n        data.put(\"someKey\", null);\n        data.put(\"someId\", \"abc123\");\n        workflow.setOutput(data);\n\n        String workflowPayload = objectMapper.writeValueAsString(workflow);\n        Workflow workflow1 = objectMapper.readValue(workflowPayload, Workflow.class);\n\n        assertTrue(workflow1.getOutput().containsKey(\"someKey\"));\n        assertNull(workflow1.getOutput().get(\"someKey\"));\n        assertNotNull(workflow1.getInput());\n    }\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\nplugins {\n    id \"com.gradle.enterprise\" version \"3.11.1\"\n}\n\ngradleEnterprise {\n    buildScan {\n        termsOfServiceUrl = \"https://gradle.com/terms-of-service\"\n        termsOfServiceAgree = \"yes\"\n        publishAlways()\n\n        buildScanPublished { scan ->\n            file(\"buildscan.log\") << \"${new Date()} - ${scan.buildScanUri}\\n\"\n        }\n    }\n}\n\nrootProject.name = 'conductor'\n\ninclude 'annotations'\ninclude 'annotations-processor'\n\ninclude 'server'\ninclude 'common'\ninclude 'core'\ninclude 'client'\ninclude 'client-spring'\n\ninclude 'cassandra-persistence'\ninclude 'redis-persistence'\n\ninclude 'es6-persistence'\n\ninclude 'redis-lock'\n\ninclude 'awss3-storage'\ninclude 'awssqs-event-queue'\n\ninclude 'redis-concurrency-limit'\n\ninclude 'json-jq-task'\ninclude 'http-task'\n\ninclude 'rest'\ninclude 'grpc'\ninclude 'grpc-server'\ninclude 'grpc-client'\n\ninclude 'java-sdk'\n\ninclude 'test-harness'\n\nrootProject.children.each {it.name=\"conductor-${it.name}\"}\n"
  },
  {
    "path": "springboot-bom-overrides.gradle",
    "content": "/*\n *  Copyright 2021 Netflix, Inc.\n *  <p>\n *  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n *  the License. You may obtain a copy of the License at\n *  <p>\n *  http://www.apache.org/licenses/LICENSE-2.0\n *  <p>\n *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n *  an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n *  specific language governing permissions and limitations under the License.\n */\n\n// Contains overrides for Spring Boot Dependency Management plugin\n// Dependency version override properties can be found at https://docs.spring.io/spring-boot/docs/2.7.3/reference/htmlsingle/#appendix.dependency-versions.properties\n\n// Conductor's default is ES6, but SB brings in ES7\next['elasticsearch.version'] = revElasticSearch6\n\n// SB brings groovy 3.0.x which is not compatible with Spock\next['groovy.version'] = revGroovy\n"
  },
  {
    "path": "test-harness/build.gradle",
    "content": "apply plugin: 'groovy'\n\ndependencies {\n    testImplementation project(':conductor-server')\n    testImplementation project(':conductor-common')\n    testImplementation project(':conductor-rest')\n    testImplementation project(':conductor-core')\n    testImplementation project(':conductor-redis-persistence')\n    testImplementation project(':conductor-cassandra-persistence')\n    testImplementation project(':conductor-es6-persistence')\n    testImplementation project(':conductor-grpc-server')\n    testImplementation project(':conductor-client')\n    testImplementation project(':conductor-grpc-client')\n    testImplementation project(':conductor-json-jq-task')\n    testImplementation project(':conductor-http-task')\n\n    testImplementation \"org.springframework.retry:spring-retry\"\n\n    testImplementation \"com.fasterxml.jackson.core:jackson-databind:${revFasterXml}\"\n    testImplementation \"com.fasterxml.jackson.core:jackson-core:${revFasterXml}\"\n\n    testImplementation \"org.apache.commons:commons-lang3\"\n\n    testImplementation \"com.google.protobuf:protobuf-java:${revProtoBuf}\"\n    testImplementation \"com.google.guava:guava:${revGuava}\"\n    testImplementation \"org.springframework:spring-web\"\n\n    testImplementation \"redis.clients:jedis:${revJedis}\"\n    testImplementation \"com.netflix.dyno-queues:dyno-queues-redis:${revDynoQueues}\"\n\n    testImplementation \"org.codehaus.groovy:groovy-all:${revGroovy}\"\n    testImplementation \"org.spockframework:spock-core:${revSpock}\"\n    testImplementation \"org.spockframework:spock-spring:${revSpock}\"\n\n    testImplementation \"org.elasticsearch.client:elasticsearch-rest-client\"\n    testImplementation \"org.elasticsearch.client:elasticsearch-rest-high-level-client\"\n\n    testImplementation \"org.testcontainers:elasticsearch:${revTestContainer}\"\n    testImplementation('junit:junit:4.13.2')\n    testImplementation \"org.junit.vintage:junit-vintage-engine\"\n    testImplementation \"javax.ws.rs:javax.ws.rs-api:${revJAXRS}\"\n    testImplementation \"org.glassfish.jersey.core:jersey-common:${revJerseyCommon}\"\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/base/AbstractResiliencySpecification.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.base\n\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty\nimport org.springframework.context.annotation.Bean\nimport org.springframework.context.annotation.Configuration\nimport org.springframework.context.annotation.Primary\nimport org.springframework.test.context.TestPropertySource\n\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.redis.dao.DynoQueueDAO\nimport com.netflix.conductor.redis.jedis.JedisMock\nimport com.netflix.dyno.connectionpool.Host\nimport com.netflix.dyno.queues.ShardSupplier\nimport com.netflix.dyno.queues.redis.RedisQueues\n\nimport redis.clients.jedis.commands.JedisCommands\nimport spock.mock.DetachedMockFactory\n\n@TestPropertySource(properties = [\n        \"conductor.system-task-workers.enabled=false\",\n        \"conductor.workflow-repair-service.enabled=true\",\n        \"conductor.workflow-reconciler.enabled=false\",\n        \"conductor.integ-test.queue-spy.enabled=true\"\n])\nabstract class AbstractResiliencySpecification extends AbstractSpecification {\n\n    @Configuration\n    static class TestQueueConfiguration {\n\n        @Primary\n        @Bean\n        @ConditionalOnProperty(name = \"conductor.integ-test.queue-spy.enabled\", havingValue = \"true\")\n        QueueDAO SpyQueueDAO() {\n            DetachedMockFactory detachedMockFactory = new DetachedMockFactory()\n            JedisCommands jedisMock = new JedisMock()\n            ShardSupplier shardSupplier = new ShardSupplier() {\n                @Override\n                Set<String> getQueueShards() {\n                    return new HashSet<>(Collections.singletonList(\"a\"))\n                }\n\n                @Override\n                String getCurrentShard() {\n                    return \"a\"\n                }\n\n                @Override\n                String getShardForHost(Host host) {\n                    return \"a\"\n                }\n            }\n            RedisQueues redisQueues = new RedisQueues(jedisMock, jedisMock, \"mockedQueues\", shardSupplier, 60000, 120000)\n            DynoQueueDAO dynoQueueDAO = new DynoQueueDAO(redisQueues)\n\n            return detachedMockFactory.Spy(dynoQueueDAO)\n        }\n    }\n\n    @Autowired\n    QueueDAO queueDAO\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/base/AbstractSpecification.groovy",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.base\n\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.boot.test.context.SpringBootTest\nimport org.springframework.test.context.TestPropertySource\n\nimport com.netflix.conductor.core.execution.AsyncSystemTaskExecutor\nimport com.netflix.conductor.core.execution.StartWorkflowInput\nimport com.netflix.conductor.core.execution.WorkflowExecutor\nimport com.netflix.conductor.core.operation.StartWorkflowOperation\nimport com.netflix.conductor.core.reconciliation.WorkflowSweeper\nimport com.netflix.conductor.service.ExecutionService\nimport com.netflix.conductor.service.MetadataService\nimport com.netflix.conductor.test.util.WorkflowTestUtil\n\nimport spock.lang.Specification\n\n@SpringBootTest\n@TestPropertySource(locations = \"classpath:application-integrationtest.properties\")\nabstract class AbstractSpecification extends Specification {\n\n    @Autowired\n    ExecutionService workflowExecutionService\n\n    @Autowired\n    MetadataService metadataService\n\n    @Autowired\n    WorkflowExecutor workflowExecutor\n\n    @Autowired\n    WorkflowTestUtil workflowTestUtil\n\n    @Autowired\n    WorkflowSweeper workflowSweeper\n\n    @Autowired\n    AsyncSystemTaskExecutor asyncSystemTaskExecutor\n\n    @Autowired\n    StartWorkflowOperation startWorkflowOperation\n\n    def cleanup() {\n        workflowTestUtil.clearWorkflows()\n    }\n\n    void sweep(String workflowId) {\n        workflowSweeper.sweep(workflowId)\n    }\n\n    protected String startWorkflow(String name, Integer version, String correlationId, Map<String, Object> workflowInput, String workflowInputPath) {\n        StartWorkflowInput input = new StartWorkflowInput(name: name, version: version, correlationId: correlationId, workflowInput: workflowInput, externalInputPayloadStoragePath: workflowInputPath)\n\n        startWorkflowOperation.execute(input)\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/DecisionTaskSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\nimport spock.lang.Unroll\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass DecisionTaskSpec extends AbstractSpecification {\n\n    @Autowired\n    Join joinTask\n\n    @Shared\n    def DECISION_WF = \"DecisionWorkflow\"\n\n    @Shared\n    def FORK_JOIN_DECISION_WF = \"ForkConditionalTest\"\n\n    @Shared\n    def COND_TASK_WF = \"ConditionalTaskWF\"\n\n    def setup() {\n        //initialization code for each feature\n        workflowTestUtil.registerWorkflows('simple_decision_task_integration_test.json',\n                'decision_and_fork_join_integration_test.json',\n                'conditional_task_workflow_integration_test.json')\n    }\n\n    def \"Test simple decision workflow\"() {\n        given: \"Workflow an input of a workflow with decision task\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'p1'\n        input['param2'] = 'p2'\n        input['case'] = 'c'\n\n        when: \"A decision workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(DECISION_WF, 1,\n                'decision_workflow', input,\n                null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DECISION'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'DECISION'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_2' is polled and completed\"\n        def polledAndCompletedTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2Try1)\n\n        and: \"verify that the 'integration_task_2' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_20'\n            tasks[3].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_20' is polled and completed\"\n        def polledAndCompletedTask20Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_20', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask20Try1)\n\n        and: \"verify that the 'integration_task_20' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[3].taskType == 'integration_task_20'\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test a workflow that has a decision task that leads to a fork join\"() {\n        given: \"Workflow an input of a workflow with decision task\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'p1'\n        input['param2'] = 'p2'\n        input['case'] = 'c'\n\n        when: \"A decision workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_DECISION_WF, 1,\n                'decision_forkjoin', input,\n                null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the tasks 'integration_task_1' and 'integration_task_10' are polled and completed\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"joinTask\").taskId\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n        def polledAndCompletedTask10Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_10', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask10Try1)\n\n        and: \"verify that the 'integration_task_1' and 'integration_task_10' are COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[2].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['t20', 't10']\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_2' is polled and completed\"\n        def polledAndCompletedTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2Try1)\n\n        and: \"verify that the 'integration_task_2' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['t20', 't10']\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_20'\n            tasks[6].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_20' is polled and completed\"\n        def polledAndCompletedTask20Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_20', 'task1.integration.worker')\n\n        and: \"the workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask20Try1)\n\n        when: \"JOIN task is polled and executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"verify that JOIN is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 7\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['t20', 't10']\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_20'\n            tasks[6].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test default case condition execution of a conditional workflow\"() {\n        given: \"input for a workflow to ensure that the default case is executed\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'xxx'\n        input['param2'] = 'two'\n\n        when: \"A conditional workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(COND_TASK_WF, 1,\n                'conditional_default', input,\n                null)\n\n        then: \"verify that the workflow is running and the default condition case was executed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DECISION'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['caseOutput'] == ['xxx']\n            tasks[1].taskType == 'integration_task_10'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_10' is polled and completed\"\n        def polledAndCompletedTask10Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_10', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask10Try1)\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_10'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'DECISION'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData['caseOutput'] == ['null']\n        }\n    }\n\n    @Unroll\n    def \"Test case 'nested' and '#caseValue' condition execution of a conditional workflow\"() {\n        given: \"input for a workflow to ensure that the 'nested' and '#caseValue' decision tree is executed\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'nested'\n        input['param2'] = caseValue\n\n        when: \"A conditional workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(COND_TASK_WF, 1,\n                workflowCorrelationId, input,\n                null)\n\n        then: \"verify that the workflow is running and the 'nested' and '#caseValue' condition case was executed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'DECISION'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['caseOutput'] == ['nested']\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].outputData['caseOutput'] == [caseValue]\n            tasks[2].taskType == expectedTaskName\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task '#expectedTaskName' is polled and completed\"\n        def polledAndCompletedTaskTry1 = workflowTestUtil.pollAndCompleteTask(expectedTaskName, 'task.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTaskTry1)\n\n        and:\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[2].taskType == expectedTaskName\n            tasks[2].status == endTaskStatus\n            tasks[3].taskType == 'DECISION'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].outputData['caseOutput'] == ['null']\n        }\n\n        where:\n        caseValue | expectedTaskName     | workflowCorrelationId    || endTaskStatus\n        'two'     | 'integration_task_2' | 'conditional_nested_two' || Task.Status.COMPLETED\n        'one'     | 'integration_task_1' | 'conditional_nested_one' || Task.Status.COMPLETED\n    }\n\n    def \"Test 'three' case condition execution of a conditional workflow\"() {\n        given: \"input for a workflow to ensure that the default case is executed\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'three'\n        input['param2'] = 'two'\n        input['finalCase'] = 'notify'\n\n        when: \"A conditional workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(COND_TASK_WF, 1,\n                'conditional_three', input,\n                null)\n\n        then: \"verify that the workflow is running and the 'three' condition case was executed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DECISION'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['caseOutput'] == ['three']\n            tasks[1].taskType == 'integration_task_3'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_3' is polled and completed\"\n        def polledAndCompletedTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask3Try1)\n\n        and: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[1].taskType == 'integration_task_3'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'DECISION'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData['caseOutput'] == ['notify']\n            tasks[3].taskType == 'integration_task_4'\n            tasks[3].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_4' is polled and completed\"\n        def polledAndCompletedTask4Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask4Try1)\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[1].taskType == 'integration_task_3'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'DECISION'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData['caseOutput'] == ['notify']\n            tasks[3].taskType == 'integration_task_4'\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/DoWhileSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.common.utils.TaskUtils\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass DoWhileSpec extends AbstractSpecification {\n\n    @Autowired\n    Join joinTask\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('do_while_integration_test.json',\n                'do_while_multiple_integration_test.json',\n                'do_while_as_subtask_integration_test.json',\n                'simple_one_task_sub_workflow_integration_test.json',\n                'do_while_iteration_fix_test.json',\n                'do_while_sub_workflow_integration_test.json',\n                'do_while_five_loop_over_integration_test.json',\n                'do_while_system_tasks.json',\n                'do_while_with_decision_task.json',\n                'do_while_set_variable_fix.json')\n    }\n\n    def \"Test workflow with 2 iterations of five tasks\"() {\n        given: \"Number of iterations of the loop is set to 1\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 2\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"do_while_five_loop_over_integration_test\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].iteration == 1\n            tasks[2].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].iteration == 1\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[3].iteration == 1\n        }\n\n        when: \"Polling and completing first task\"\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing second task\"\n        Tuple polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        verifyTaskIteration(polledAndCompletedTask2[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 9\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'LAMBDA'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[6].iteration == 2\n            tasks[7].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[7].iteration == 2\n            tasks[8].taskType == 'integration_task_1'\n            tasks[8].status == Task.Status.SCHEDULED\n            tasks[8].iteration == 2\n        }\n\n        when: \"Polling and completing first task\"\n        polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 2)\n\n        when: \"Polling and completing second task\"\n        polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        verifyTaskIteration(polledAndCompletedTask2[0] as Task, 2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 12\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'LAMBDA'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[6].iteration == 2\n            tasks[7].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[7].iteration == 2\n            tasks[8].taskType == 'integration_task_1'\n            tasks[8].status == Task.Status.COMPLETED\n            tasks[8].iteration == 2\n            tasks[9].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[9].status == Task.Status.COMPLETED\n            tasks[9].iteration == 2\n            tasks[10].taskType == 'integration_task_2'\n            tasks[10].status == Task.Status.COMPLETED\n            tasks[10].iteration == 2\n            tasks[11].taskType == 'integration_task_3'\n            tasks[11].status == Task.Status.SCHEDULED\n            tasks[11].iteration == 0 // this is outside DO_WHILE\n        }\n\n        when: \"Polling and completing last task\"\n        polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 12\n            tasks[11].taskType == 'integration_task_3'\n            tasks[11].status == Task.Status.COMPLETED\n            tasks[11].iteration == 0\n        }\n    }\n\n    def \"Test workflow with 2 iterations of 3 system tasks\"() {\n        given: \"Number of iterations of the loop is set to 1\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 2\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"do_while_system_tasks\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 8\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].iteration == 1\n            tasks[2].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].iteration == 1\n            tasks[3].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].iteration == 1\n            tasks[4].taskType == 'LAMBDA'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].iteration == 2\n            tasks[5].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].iteration == 2\n            tasks[6].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[6].iteration == 2\n            tasks[7].taskType == 'integration_task_1'\n            tasks[7].status == Task.Status.SCHEDULED\n            tasks[7].iteration == 0 // outside the loop\n        }\n\n        when: \"Polling and completing first task\"\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 8\n            tasks[7].taskType == 'integration_task_1'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[7].iteration == 0 // outside the loop\n        }\n    }\n\n    def \"Test workflow with a single iteration Do While task\"() {\n        given: \"Number of iterations of the loop is set to 1\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 1\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"Do_While_Workflow\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing first task\"\n        Tuple polledAndCompletedTask0 = workflowTestUtil.pollAndCompleteTask('integration_task_0', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask0)\n        verifyTaskIteration(polledAndCompletedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing second task\"\n        def joinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join__1\").taskId\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing third task\"\n        Tuple polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinId)\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        verifyTaskIteration(polledAndCompletedTask2[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test workflow with a single iteration Do While task with Sub workflow\"() {\n        given: \"Number of iterations of the loop is set to 1\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 1\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"Do_While_Sub_Workflow\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing first task\"\n        Tuple polledAndCompletedTask0 = workflowTestUtil.pollAndCompleteTask('integration_task_0', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask0)\n        verifyTaskIteration(polledAndCompletedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing second task\"\n        def joinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join__1\").taskId\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing third task\"\n        Tuple polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinId)\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        verifyTaskIteration(polledAndCompletedTask2[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'SUB_WORKFLOW'\n            tasks[6].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the sub workflow is started by issuing a system task call\"\n        def parentWorkflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowTaskId = parentWorkflow.getTaskByRefName('st1__1').taskId\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId)\n\n        then: \"verify that the sub workflow task is in a IN PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'SUB_WORKFLOW'\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"sub workflow is retrieved\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowInstanceId = workflow.getTaskByRefName('st1__1').subWorkflowId\n\n        then: \"verify that the sub workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the 'simple_task_in_sub_wf' belonging to the sub workflow is polled and completed\"\n        def polledAndCompletedSubWorkflowTask = workflowTestUtil.pollAndCompleteTask('simple_task_in_sub_wf', 'subworkflow.task.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndCompletedSubWorkflowTask)\n\n        and: \"verify that the sub workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        and: \"the parent workflow is swept\"\n        sweep(workflowInstanceId)\n\n        and: \"verify that the workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'SUB_WORKFLOW'\n            tasks[6].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test workflow with multiple Do While tasks with multiple iterations\"() {\n        given: \"Number of iterations of the first loop is set to 2 and second loop is set to 1\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 2\n        workflowInput['loop2'] = 1\n\n        when: \"A workflow with multiple do while tasks with multiple iterations is started\"\n        def workflowInstanceId = startWorkflow(\"Do_While_Multiple\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing first task\"\n        Tuple polledAndCompletedTask0 = workflowTestUtil.pollAndCompleteTask('integration_task_0', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask0)\n        verifyTaskIteration(polledAndCompletedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing second task\"\n        def join1Id = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join__1\").taskId\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing third task\"\n        Tuple polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, join1Id)\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        verifyTaskIteration(polledAndCompletedTask2[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_0'\n            tasks[6].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing second iteration of first task\"\n        Tuple polledAndCompletedSecondIterationTask0 = workflowTestUtil.pollAndCompleteTask('integration_task_0', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedSecondIterationTask0, [:])\n        verifyTaskIteration(polledAndCompletedSecondIterationTask0[0] as Task, 2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 11\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_0'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'FORK'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[8].taskType == 'integration_task_1'\n            tasks[8].status == Task.Status.SCHEDULED\n            tasks[9].taskType == 'integration_task_2'\n            tasks[9].status == Task.Status.SCHEDULED\n            tasks[10].taskType == 'JOIN'\n            tasks[10].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing second iteration of second task\"\n        def join2Id = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join__2\").taskId\n        Tuple polledAndCompletedSecondIterationTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedSecondIterationTask1)\n        verifyTaskIteration(polledAndCompletedSecondIterationTask1[0] as Task, 2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 11\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_0'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'FORK'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[8].taskType == 'integration_task_1'\n            tasks[8].status == Task.Status.COMPLETED\n            tasks[9].taskType == 'integration_task_2'\n            tasks[9].status == Task.Status.SCHEDULED\n            tasks[10].taskType == 'JOIN'\n            tasks[10].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing second iteration of third task\"\n        Tuple polledAndCompletedSecondIterationTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, join2Id)\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedSecondIterationTask2)\n        verifyTaskIteration(polledAndCompletedSecondIterationTask2[0] as Task, 2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 13\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_0'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'FORK'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[8].taskType == 'integration_task_1'\n            tasks[8].status == Task.Status.COMPLETED\n            tasks[9].taskType == 'integration_task_2'\n            tasks[9].status == Task.Status.COMPLETED\n            tasks[10].taskType == 'JOIN'\n            tasks[10].status == Task.Status.COMPLETED\n            tasks[11].taskType == 'DO_WHILE'\n            tasks[11].status == Task.Status.IN_PROGRESS\n            tasks[12].taskType == 'integration_task_3'\n            tasks[12].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing task within the second do while\"\n        Tuple polledAndCompletedIntegrationTask3 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedIntegrationTask3)\n        verifyTaskIteration(polledAndCompletedIntegrationTask3[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 13\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_1'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_2'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_0'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'FORK'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[8].taskType == 'integration_task_1'\n            tasks[8].status == Task.Status.COMPLETED\n            tasks[9].taskType == 'integration_task_2'\n            tasks[9].status == Task.Status.COMPLETED\n            tasks[10].taskType == 'JOIN'\n            tasks[10].status == Task.Status.COMPLETED\n            tasks[11].taskType == 'DO_WHILE'\n            tasks[11].status == Task.Status.COMPLETED\n            tasks[12].taskType == 'integration_task_3'\n            tasks[12].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test retrying a failed do while workflow\"() {\n        setup: \"Update the task definition with no retries\"\n        def taskName = 'integration_task_0'\n        def persistedTaskDefinition = workflowTestUtil.getPersistedTaskDefinition(taskName).get()\n        def modifiedTaskDefinition = new TaskDef(persistedTaskDefinition.name, persistedTaskDefinition.description,\n                persistedTaskDefinition.ownerEmail, 0, persistedTaskDefinition.timeoutSeconds,\n                persistedTaskDefinition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        when: \"A do while workflow is started\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 1\n        def workflowInstanceId = startWorkflow(\"Do_While_Workflow\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and failing first task\"\n        Tuple polledAndFailedTask0 = workflowTestUtil.pollAndFailTask('integration_task_0', 'integration.test.worker', \"induced..failure\")\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in failed state\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTask0)\n        verifyTaskIteration(polledAndFailedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.CANCELED\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"The workflow is retried\"\n        workflowExecutor.retry(workflowInstanceId, false)\n\n        then: \"Verify that workflow is running\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing first task\"\n        Tuple polledAndCompletedTask0 = workflowTestUtil.pollAndCompleteTask('integration_task_0', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask0)\n        verifyTaskIteration(polledAndCompletedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'FORK'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_1'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing second task\"\n        def joinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join__1\").taskId\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'FORK'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_1'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing third task\"\n        Tuple polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinId)\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        verifyTaskIteration(polledAndCompletedTask2[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'FORK'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_1'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.COMPLETED\n        }\n\n        cleanup: \"Reset the task definition\"\n        metadataService.updateTaskDef(persistedTaskDefinition)\n    }\n\n    def \"Test auto retrying a failed do while workflow\"() {\n        setup: \"Update the task definition with retryCount to 1 and retryDelaySeconds to 0\"\n        def taskName = 'integration_task_0'\n        def persistedTaskDefinition = workflowTestUtil.getPersistedTaskDefinition(taskName).get()\n        def modifiedTaskDefinition = new TaskDef(persistedTaskDefinition.name, persistedTaskDefinition.description,\n                persistedTaskDefinition.ownerEmail, 1, persistedTaskDefinition.timeoutSeconds,\n                persistedTaskDefinition.responseTimeoutSeconds)\n        modifiedTaskDefinition.setRetryDelaySeconds(0)\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        when: \"A do while workflow is started\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 1\n        def workflowInstanceId = startWorkflow(\"Do_While_Workflow\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and failing first task\"\n        Tuple polledAndFailedTask0 = workflowTestUtil.pollAndFailTask('integration_task_0', 'integration.test.worker', \"induced..failure\")\n\n        then: \"Verify that the task was polled and acknowledged and retried task was generated and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTask0)\n        verifyTaskIteration(polledAndFailedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retryCount == 1\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"Polling and completing first task\"\n        Tuple polledAndCompletedTask0 = workflowTestUtil.pollAndCompleteTask('integration_task_0', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask0)\n        verifyTaskIteration(polledAndCompletedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'FORK'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_1'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing second task\"\n        def joinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join__1\").taskId\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'FORK'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_1'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"Polling and completing third task\"\n        Tuple polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinId)\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        verifyTaskIteration(polledAndCompletedTask2[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 7\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'FORK'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_1'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.COMPLETED\n        }\n\n        cleanup: \"Reset the task definition\"\n        metadataService.updateTaskDef(persistedTaskDefinition)\n    }\n\n    def \"Test workflow with a iteration Do While task as subtask of a forkjoin task\"() {\n        given: \"Number of iterations of the loop is set to 1\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 1\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"Do_While_SubTask\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DO_WHILE'\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == 'integration_task_0'\n            tasks[4].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing first task in DO While\"\n        def joinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join\").taskId\n        Tuple polledAndCompletedTask0 = workflowTestUtil.pollAndCompleteTask('integration_task_0', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask0)\n        verifyTaskIteration(polledAndCompletedTask0[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DO_WHILE'\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == 'integration_task_0'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_1'\n            tasks[5].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polling and completing second task in DO While\"\n        Tuple polledAndCompletedTask1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'integration.test.worker')\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in running state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1)\n        verifyTaskIteration(polledAndCompletedTask1[0] as Task, 1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DO_WHILE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == 'integration_task_0'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_1'\n            tasks[5].status == Task.Status.COMPLETED\n        }\n\n        when: \"Polling and completing third task\"\n        Tuple polledAndCompletedTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'integration.test.worker')\n\n        and: \"the workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinId)\n\n        then: \"Verify that the task was polled and acknowledged and workflow is in completed state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DO_WHILE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_0'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_1'\n            tasks[5].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test workflow with Do While task contains loop over task that use iteration in script expression\"() {\n        given: \"Number of iterations of the loop is set to 2\"\n        def workflowInput = new HashMap()\n        workflowInput['loop'] = 2\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"Do_While_Workflow_Iteration_Fix\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has competed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].outputData.get(\"result\") == 0\n            tasks[2].taskType == 'LAMBDA'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData.get(\"result\") == 1\n        }\n    }\n\n    def \"Test workflow with Do While task contains set variable task\"() {\n        given: \"The loop condition is set to use set variable\"\n        def workflowInput = new HashMap()\n        workflowInput['value'] = 2\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"do_while_Set_variable_fix\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the workflow has competed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SET_VARIABLE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].inputData.get(\"value\") == \"0\"\n        }\n    }\n\n    def \"Test workflow with Do While task contains decision task\"() {\n        given: \"The loop condition is set to use set variable\"\n        def workflowInput = new HashMap()\n        def array = new ArrayList()\n        array.add(1);\n        array.add(2);\n        workflowInput['list'] = array\n\n        when: \"A do_while workflow is started\"\n        def workflowInstanceId = startWorkflow(\"DO_While_with_Decision_task\", 1, \"looptest\", workflowInput, null)\n\n        then: \"Verify that the loop over task is waiting for the wait task to get completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[1].taskType == 'INLINE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SWITCH'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'WAIT'\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"The wait task is completed\"\n        def waitTask = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[3]\n        waitTask.status = Task.Status.COMPLETED\n        workflowExecutor.updateTask(new TaskResult(waitTask))\n\n        then: \"Verify that the next iteration is scheduled and workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 8\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[0].iteration == 2\n            tasks[1].taskType == 'INLINE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SWITCH'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'WAIT'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'INLINE'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'INLINE'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'SWITCH'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'WAIT'\n            tasks[7].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"The wait task is completed\"\n        waitTask = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[7]\n        waitTask.status = Task.Status.COMPLETED\n        workflowExecutor.updateTask(new TaskResult(waitTask))\n\n        then: \"Verify that the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 9\n            tasks[0].taskType == 'DO_WHILE'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].iteration == 2\n            tasks[1].taskType == 'INLINE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SWITCH'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'WAIT'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'INLINE'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'INLINE'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'SWITCH'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'WAIT'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[8].taskType == 'INLINE'\n            tasks[8].status == Task.Status.COMPLETED\n        }\n    }\n\n\n    void verifyTaskIteration(Task task, int iteration) {\n        assert task.getReferenceTaskName().endsWith(TaskUtils.getLoopOverTaskRefNameSuffix(task.getIteration()))\n        assert task.iteration == iteration\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/DynamicForkJoinSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.StartWorkflowInput\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nclass DynamicForkJoinSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    Join joinTask\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Shared\n    def DYNAMIC_FORK_JOIN_WF = \"DynamicFanInOutTest\"\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('dynamic_fork_join_integration_test.json',\n                'simple_workflow_3_integration_test.json')\n    }\n\n    def \"Test dynamic fork join success flow\"() {\n        when: \" a dynamic fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(DYNAMIC_FORK_JOIN_WF, 1,\n                'dynamic_fork_join_workflow', [:],\n                null)\n\n        then: \"verify that the workflow has been successfully started and the first task is in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \" the first task is 'integration_task_1' output has a list of dynamic tasks\"\n        WorkflowTask workflowTask2 = new WorkflowTask()\n        workflowTask2.name = 'integration_task_2'\n        workflowTask2.taskReferenceName = 'xdt1'\n\n        WorkflowTask workflowTask3 = new WorkflowTask()\n        workflowTask3.name = 'integration_task_3'\n        workflowTask3.taskReferenceName = 'xdt2'\n\n        def dynamicTasksInput = ['xdt1': ['k1': 'v1'], 'xdt2': ['k2': 'v2']]\n\n        and: \"The 'integration_task_1' is polled and completed\"\n        def pollAndCompleteTask1Try = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker',\n                ['dynamicTasks': [workflowTask2, workflowTask3], 'dynamicTasksInput': dynamicTasksInput])\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try)\n\n        and: \"verify that workflow has progressed further ahead and new dynamic tasks have been scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"Poll and complete 'integration_task_2' and 'integration_task_3'\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"dynamicfanouttask_join\").taskId\n        def pollAndCompleteTask2Try = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.worker',\n                ['ok1': 'ov1'])\n        def pollAndCompleteTask3Try = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task3.worker',\n                ['ok1': 'ov1'])\n\n        and: \"workflow is evaluated by the reconciler\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try, ['k1': 'v1'])\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask3Try, ['k2': 'v2'])\n\n        when: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"verify that the workflow has progressed and the 'integration_task_2' and 'integration_task_3' are complete\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['xdt1', 'xdt2']\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n            tasks[4].outputData['xdt1']['ok1'] == 'ov1'\n            tasks[4].outputData['xdt2']['ok1'] == 'ov1'\n            tasks[5].taskType == 'integration_task_4'\n            tasks[5].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete 'integration_task_4'\"\n        def pollAndCompleteTask4Try = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task4.worker')\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask4Try)\n\n        and: \"verify that the workflow is complete\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[5].taskType == 'integration_task_4'\n            tasks[5].status == Task.Status.COMPLETED\n        }\n    }\n\n\n    def \"Test dynamic fork join failure of dynamic forked task flow\"() {\n        setup: \"Make sure that the integration_task_2 does not have any retry count\"\n        def persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name,\n                persistedTask2Definition.description, persistedTask2Definition.ownerEmail, 0,\n                persistedTask2Definition.timeoutSeconds, persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        when: \" a dynamic fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(DYNAMIC_FORK_JOIN_WF, 1,\n                'dynamic_fork_join_workflow', [:],\n                null)\n\n        then: \"verify that the workflow has been successfully started and the first task is in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \" the first task is 'integration_task_1' output has a list of dynamic tasks\"\n        WorkflowTask workflowTask2 = new WorkflowTask()\n        workflowTask2.name = 'integration_task_2'\n        workflowTask2.taskReferenceName = 'xdt1'\n\n        WorkflowTask workflowTask3 = new WorkflowTask()\n        workflowTask3.name = 'integration_task_3'\n        workflowTask3.taskReferenceName = 'xdt2'\n\n        def dynamicTasksInput = ['xdt1': ['k1': 'v1'], 'xdt2': ['k2': 'v2']]\n\n        and: \"The 'integration_task_1' is polled and completed\"\n        def pollAndCompleteTask1Try = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker',\n                ['dynamicTasks': [workflowTask2, workflowTask3], 'dynamicTasksInput': dynamicTasksInput])\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try)\n\n        and: \"verify that workflow has progressed further ahead and new dynamic tasks have been scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"Poll and fail 'integration_task_2'\"\n        def pollAndCompleteTask2Try = workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.worker', 'it is a failure..')\n\n        and: \"workflow is evaluated by the reconciler\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try, ['k1': 'v1'])\n\n        and: \"verify that the workflow is in failed state and 'integration_task_2' has also failed and other tasks are canceled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 5\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.FAILED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.CANCELED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['xdt1', 'xdt2']\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        cleanup: \"roll back the change made to integration_task_2 definition\"\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n\n    def \"Retry a failed dynamic fork join workflow\"() {\n        setup: \"Make sure that the integration_task_2 does not have any retry count\"\n        def persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name,\n                persistedTask2Definition.description, persistedTask2Definition.ownerEmail, 0,\n                persistedTask2Definition.timeoutSeconds, persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        when: \" a dynamic fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(DYNAMIC_FORK_JOIN_WF, 1,\n                'dynamic_fork_join_workflow', [:],\n                null)\n\n        then: \"verify that the workflow has been successfully started and the first task is in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \" the first task is 'integration_task_1' output has a list of dynamic tasks\"\n        WorkflowTask workflowTask2 = new WorkflowTask()\n        workflowTask2.name = 'integration_task_2'\n        workflowTask2.taskReferenceName = 'xdt1'\n\n        WorkflowTask workflowTask3 = new WorkflowTask()\n        workflowTask3.name = 'integration_task_3'\n        workflowTask3.taskReferenceName = 'xdt2'\n\n        def dynamicTasksInput = ['xdt1': ['k1': 'v1'], 'xdt2': ['k2': 'v2']]\n\n        and: \"The 'integration_task_1' is polled and completed\"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker',\n                ['dynamicTasks': [workflowTask2, workflowTask3], 'dynamicTasksInput': dynamicTasksInput])\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that workflow has progressed further ahead and new dynamic tasks have been scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"Poll and fail 'integration_task_2'\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"dynamicfanouttask_join\").taskId\n        def pollAndCompleteTask2Try1 = workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.worker', 'it is a failure..')\n\n        and: \"workflow is evaluated by the reconciler\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try1, ['k1': 'v1'])\n\n        and: \"verify that the workflow is in failed state and 'integration_task_2' has also failed and other tasks are canceled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 5\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.FAILED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.CANCELED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['xdt1', 'xdt2']\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"The workflow is retried\"\n        workflowExecutor.retry(workflowInstanceId, false)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.FAILED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.CANCELED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['xdt1', 'xdt2']\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[6].taskType == 'integration_task_3'\n            tasks[6].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete 'integration_task_2' and 'integration_task_3'\"\n        def pollAndCompleteTask2Try2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.worker',\n                ['ok1': 'ov1'])\n        def pollAndCompleteTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task3.worker',\n                ['ok1': 'ov1'])\n\n        and: \"workflow is evaluated by the reconciler\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try2, ['k1': 'v1'])\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask3Try1, ['k2': 'v2'])\n\n        when: \"JOIN task is polled and executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"verify that the workflow has progressed and the 'integration_task_2' and 'integration_task_3' are complete\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 8\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['xdt1', 'xdt2']\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n            tasks[4].outputData['xdt1']['ok1'] == 'ov1'\n            tasks[4].outputData['xdt2']['ok1'] == 'ov1'\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_3'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'integration_task_4'\n            tasks[7].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete 'integration_task_4'\"\n        def pollAndCompleteTask4Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task4.worker')\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask4Try1)\n\n        and: \"verify that the workflow is complete\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 8\n            tasks[7].taskType == 'integration_task_4'\n            tasks[7].status == Task.Status.COMPLETED\n        }\n\n        cleanup: \"roll back the change made to integration_task_2 definition\"\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    def \"Retry a failed dynamic fork join workflow with forked subworkflow\"() {\n        setup: \"Make sure that the integration_task_2 does not have any retry count\"\n        def persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name,\n                persistedTask2Definition.description, persistedTask2Definition.ownerEmail, 0,\n                persistedTask2Definition.timeoutSeconds, persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        when: \"the dynamic fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(DYNAMIC_FORK_JOIN_WF, 1,\n                'dynamic_fork_join_wf_subwf', [:], null)\n\n        then: \"verify that the workflow is started and first task is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"the first task's output has a list of dynamically forked tasks including a subworkflow\"\n        WorkflowTask workflowTask2 = new WorkflowTask()\n        workflowTask2.name = 'sub_wf_task'\n        workflowTask2.taskReferenceName = 'xdt1'\n        workflowTask2.workflowTaskType = TaskType.SUB_WORKFLOW\n        SubWorkflowParams subWorkflowParams = new SubWorkflowParams()\n        subWorkflowParams.setName(\"integration_test_wf3\")\n        subWorkflowParams.setVersion(1)\n        workflowTask2.subWorkflowParam = subWorkflowParams\n\n        WorkflowTask workflowTask3 = new WorkflowTask()\n        workflowTask3.name = 'integration_task_10'\n        workflowTask3.taskReferenceName = 'xdt10'\n\n        def dynamicTasksInput = ['xdt1': ['p1': 'q1', 'p2': 'q2'], 'xdt10': ['k2': 'v2']]\n\n        and: \"The 'integration_task_1' is polled and completed\"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker',\n                ['dynamicTasks': [workflowTask2, workflowTask3], 'dynamicTasksInput': dynamicTasksInput])\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that workflow has progressed further ahead and new dynamic tasks have been scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"the subworkflow is started by issuing a system task call\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"dynamicfanouttask_join\").taskId\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        String subworkflowTaskId = polledTaskIds.get(0)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subworkflowTaskId)\n\n        then: \"verify that the sub workflow task is in a IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"subworkflow is retrieved\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowId = workflow.tasks[2].subWorkflowId\n\n        then: \"verify that the sub workflow is RUNNING, and first task is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"The 'integration_task_10' is polled and completed\"\n        def pollAndCompleteTask10Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_10', 'task10.worker')\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask10Try1)\n\n        and: \"verify that the workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n       when: \"The task within sub workflow is polled and completed\"\n        pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker')\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"the next task in the subworkflow is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and fail 'integration_task_2'\"\n        def pollAndCompleteTask2Try1 = workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.worker', \"failure\")\n\n        and: \"the workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try1)\n\n        and: \"the subworkflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        and: \"the workflow is also in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.FAILED\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"The workflow is retried\"\n        workflowExecutor.retry(workflowInstanceId, true)\n\n        then: \"verify that the workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        and: \"the subworkflow is retried and in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.IN_PROGRESS\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"Poll and complete 'integration_task_2'\"\n        def pollAndCompleteTask2Try2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try2)\n\n        and: \"the sub workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete 'integration_task_3'\"\n        def pollAndCompleteTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task3.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask3Try1)\n\n        and: \"the sub workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.COMPLETED\n        }\n\n        when: \"the workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        and: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"the workflow has progressed beyond the join task\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.COMPLETED\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n            tasks[5].taskType == 'integration_task_4'\n            tasks[5].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete 'integration_task_4'\"\n        def pollAndCompleteTask4Try = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task4.worker')\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask4Try)\n\n        and: \"verify that the workflow is complete\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.COMPLETED\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n            tasks[5].taskType == 'integration_task_4'\n            tasks[5].status == Task.Status.COMPLETED\n        }\n\n        cleanup: \"roll back the change made to integration_task_2 definition\"\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    def \"Test dynamic fork join empty output\"() {\n        when: \" a dynamic fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(DYNAMIC_FORK_JOIN_WF, 1,\n                'dynamic_fork_join_workflow', [:],\n                null)\n\n        then: \"verify that the workflow has been successfully started and the first task is in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \" the first task is 'integration_task_1' output has a list of dynamic tasks\"\n        WorkflowTask workflowTask2 = new WorkflowTask()\n        workflowTask2.name = 'integration_task_2'\n        workflowTask2.taskReferenceName = 'xdt1'\n\n        WorkflowTask workflowTask3 = new WorkflowTask()\n        workflowTask3.name = 'integration_task_3'\n        workflowTask3.taskReferenceName = 'xdt2'\n\n        def dynamicTasksInput = ['xdt1': ['k1': 'v1'], 'xdt2': ['k2': 'v2']]\n\n        and: \"The 'integration_task_1' is polled and completed\"\n        def pollAndCompleteTask1Try = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker',\n                ['dynamicTasks': [workflowTask2, workflowTask3], 'dynamicTasksInput': dynamicTasksInput])\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try)\n\n        and: \"verify that workflow has progressed further ahead and new dynamic tasks have been scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n        }\n\n        when: \"Poll and complete 'integration_task_2' and 'integration_task_3'\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"dynamicfanouttask_join\").taskId\n        def pollAndCompleteTask2Try = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.worker')\n        def pollAndCompleteTask3Try = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task3.worker')\n\n        and: \"workflow is evaluated by the reconciler\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try, ['k1': 'v1'])\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask3Try, ['k2': 'v2'])\n\n        when: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"verify that the workflow has progressed and the 'integration_task_2' and 'integration_task_3' are complete\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['xdt1', 'xdt2']\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].referenceTaskName == 'dynamicfanouttask_join'\n            tasks[4].outputData.isEmpty()\n            tasks[5].taskType == 'integration_task_4'\n            tasks[5].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete 'integration_task_4'\"\n        def pollAndCompleteTask4Try = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task4.worker')\n\n        then: \"verify that the tasks were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask4Try)\n\n        and: \"verify that the workflow is complete\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[5].taskType == 'integration_task_4'\n            tasks[5].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test dynamic fork join fail when task input is invalid\"() {\n        when: \"a dynamic fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(DYNAMIC_FORK_JOIN_WF, 1,\n                'dynamic_fork_join_workflow', [:],\n                null)\n\n        then: \"verify that the workflow has been successfully started and the first task is in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \" the first task is 'integration_task_1' output has a list of dynamic tasks\"\n        WorkflowTask workflowTask2 = new WorkflowTask()\n        workflowTask2.name = 'integration_task_2'\n        workflowTask2.taskReferenceName = 'xdt1'\n\n        WorkflowTask workflowTask3 = new WorkflowTask()\n        workflowTask3.name = 'integration_task_3'\n        workflowTask3.taskReferenceName = 'xdt2'\n\n        def invalidDynamicTasksInput = ['xdt1': 'v1', 'xdt2': 'v2']\n\n        and: \"The 'integration_task_1' is polled and completed\"\n        def pollAndCompleteTask1Try = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker',\n                ['dynamicTasks': [workflowTask2, workflowTask3], 'dynamicTasksInput': invalidDynamicTasksInput])\n\n        then: \"verify that the task was completed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try)\n\n        and: \"verify that workflow failed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test dynamic fork join return failed workflow when start with invalid input\"() {\n        when: \"a dynamic fork join workflow is started\"\n        WorkflowTask workflowTask2 = new WorkflowTask()\n        workflowTask2.name = 'integration_task_2'\n        workflowTask2.taskReferenceName = 'xdt1'\n\n        WorkflowTask workflowTask3 = new WorkflowTask()\n        workflowTask3.name = 'integration_task_3'\n        workflowTask3.taskReferenceName = 'xdt2'\n\n        def invalidDynamicTasksInput = ['xdt1': 'v1', 'xdt2': 'v2']\n        def workflowInput = ['dynamicTasks': [workflowTask2, workflowTask3], 'dynamicTasksInput': invalidDynamicTasksInput]\n\n        def dynamicForkJoinTask = new WorkflowTask()\n        dynamicForkJoinTask.name = 'dynamicfanouttask'\n        dynamicForkJoinTask.taskReferenceName = 'dynamicfanouttask'\n        dynamicForkJoinTask.type = 'FORK_JOIN_DYNAMIC'\n        dynamicForkJoinTask.inputParameters = ['dynamicTasks': '${workflow.input.dynamicTasks}', 'dynamicTasksInput': '${workflow.input.dynamicTasksInput}']\n        dynamicForkJoinTask.dynamicForkTasksParam = 'dynamicTasks'\n        dynamicForkJoinTask.dynamicForkTasksInputParamName = 'dynamicTasksInput'\n\n        def workflowDef = new WorkflowDef()\n        workflowDef.name = 'DynamicForkJoinStartTest'\n        workflowDef.version = 1\n        workflowDef.tasks.add(dynamicForkJoinTask)\n        workflowDef.ownerEmail = 'test@harness.com'\n\n        def startWorkflowInput = new StartWorkflowInput(name: workflowDef.name, version: workflowDef.version, workflowInput: workflowInput, workflowDefinition: workflowDef)\n        def workflowInstanceId = startWorkflowOperation.execute(startWorkflowInput)\n\n        then: \"verify that workflow failed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.isEmpty()\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/EventTaskSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Event\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass EventTaskSpec extends AbstractSpecification {\n\n    def EVENT_BASED_WORKFLOW = 'test_event_workflow'\n\n    @Autowired\n    Event eventTask\n\n    @Autowired\n    QueueDAO queueDAO\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('event_workflow_integration_test.json')\n    }\n\n    def \"Verify that a event based simple workflow is executed\"() {\n        when: \"Start a event based workflow\"\n        def workflowInstanceId = startWorkflow(EVENT_BASED_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"Retrieve the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == TaskType.EVENT.name()\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['event_produced']\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The integration_task_1 is polled and completed\"\n        def polledAndCompletedTry1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the task was polled and completed and the workflow is in a complete state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTry1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test a workflow with event task that is asyncComplete \"() {\n        setup: \"Register a workflow definition with event task as asyncComplete\"\n        def persistedWorkflowDefinition = metadataService.getWorkflowDef(EVENT_BASED_WORKFLOW, 1)\n        def modifiedWorkflowDefinition = new WorkflowDef()\n        modifiedWorkflowDefinition.name = persistedWorkflowDefinition.name\n        modifiedWorkflowDefinition.version = persistedWorkflowDefinition.version\n        modifiedWorkflowDefinition.tasks = persistedWorkflowDefinition.tasks\n        modifiedWorkflowDefinition.inputParameters = persistedWorkflowDefinition.inputParameters\n        modifiedWorkflowDefinition.outputParameters = persistedWorkflowDefinition.outputParameters\n        modifiedWorkflowDefinition.ownerEmail = persistedWorkflowDefinition.ownerEmail\n        modifiedWorkflowDefinition.tasks[0].asyncComplete = true\n        metadataService.updateWorkflowDef([modifiedWorkflowDefinition])\n\n        when: \"The event task workflow is started\"\n        def workflowInstanceId = startWorkflow(EVENT_BASED_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"Retrieve the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == TaskType.EVENT.name()\n            tasks[0].status == Task.Status.IN_PROGRESS\n            tasks[0].outputData['event_produced']\n        }\n\n        when: \"The event task is updated async using the API\"\n        Task task = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName('wait0')\n        TaskResult taskResult = new TaskResult(task)\n        taskResult.setStatus(TaskResult.Status.COMPLETED)\n        workflowExecutor.updateTask(taskResult)\n\n        then: \"Ensure that event task is COMPLETED and workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == TaskType.EVENT.name()\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['event_produced']\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The integration_task_1 is polled and completed\"\n        def polledAndCompletedTry1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the task was polled and completed and the workflow is in a complete state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTry1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == TaskType.EVENT.name()\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['event_produced']\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        cleanup: \"Ensure that the changes to the workflow def are reverted\"\n        metadataService.updateWorkflowDef([persistedWorkflowDefinition])\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/ExclusiveJoinSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass ExclusiveJoinSpec extends AbstractSpecification {\n\n    @Shared\n    def EXCLUSIVE_JOIN_WF = \"ExclusiveJoinTestWorkflow\"\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('exclusive_join_integration_test.json')\n    }\n\n    def setTaskResult(String workflowInstanceId, String taskId, TaskResult.Status status,\n                      Map<String, Object> output) {\n        TaskResult taskResult = new TaskResult();\n        taskResult.setTaskId(taskId)\n        taskResult.setWorkflowInstanceId(workflowInstanceId)\n        taskResult.setStatus(status)\n        taskResult.setOutputData(output)\n        return taskResult\n    }\n\n    def \"Test that the default decision is run\"() {\n        given: \"The input parameter required to make decision_1 is null to ensure that the default decision is run\"\n        def input = [\"decision_1\": \"null\"]\n\n        when: \"An exclusive join workflow is started with then workflow input\"\n        def workflowInstanceId = startWorkflow(EXCLUSIVE_JOIN_WF, 1, 'exclusive_join_workflow',\n                input, null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1' +\n                '.integration.worker', [\"taskReferenceName\": \"task1\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'EXCLUSIVE_JOIN'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData['taskReferenceName'] == 'task1'\n        }\n    }\n\n    def \"Test when the one decision is true and the other is decision null\"() {\n        given: \"The input parameter required to make decision_1 true and decision_2 null\"\n        def input = [\"decision_1\": \"true\", \"decision_2\": \"null\"]\n\n        when: \"An exclusive join workflow is started with then workflow input\"\n        def workflowInstanceId = startWorkflow(EXCLUSIVE_JOIN_WF, 1, 'exclusive_join_workflow',\n                input, null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1' +\n                '.integration.worker', [\"taskReferenceName\": \"task1\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_2' is polled and completed\"\n        def polledAndCompletedTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2' +\n                '.integration.worker', [\"taskReferenceName\": \"task2\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2Try1)\n\n        and: \"verify that the 'integration_task_2' is COMPLETED and the workflow has COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'DECISION'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'EXCLUSIVE_JOIN'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].outputData['taskReferenceName'] == 'task2'\n        }\n    }\n\n    def \"Test when both the decisions, decision_1 and decision_2 are true\"() {\n        given: \"The input parameters to ensure that both the decisions are true\"\n        def input = [\"decision_1\": \"true\", \"decision_2\": \"true\"]\n\n        when: \"An exclusive join workflow is started with then workflow input\"\n        def workflowInstanceId = startWorkflow(EXCLUSIVE_JOIN_WF, 1, 'exclusive_join_workflow',\n                input, null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1' +\n                '.integration.worker', [\"taskReferenceName\": \"task1\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_2' is polled and completed\"\n        def polledAndCompletedTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2' +\n                '.integration.worker', [\"taskReferenceName\": \"task2\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2Try1)\n\n        and: \"verify that the 'integration_task_2' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'DECISION'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_3'\n            tasks[4].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_3' is polled and completed\"\n        def polledAndCompletedTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task3' +\n                '.integration.worker', [\"taskReferenceName\": \"task3\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask3Try1)\n\n        and: \"verify that the 'integration_task_3' is COMPLETED and the workflow has COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'DECISION'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_3'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'EXCLUSIVE_JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].outputData['taskReferenceName'] == 'task3'\n        }\n    }\n\n    def \"Test when decision_1 is false and  decision_3 is default\"() {\n        given: \"The input parameter required to make decision_1 false and decision_3 default\"\n        def input = [\"decision_1\": \"false\", \"decision_3\": \"null\"]\n\n        when: \"An exclusive join workflow is started with then workflow input\"\n        def workflowInstanceId = startWorkflow(EXCLUSIVE_JOIN_WF, 1, 'exclusive_join_workflow',\n                input, null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1' +\n                '.integration.worker', [\"taskReferenceName\": \"task1\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_4'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_4' is polled and completed\"\n        def polledAndCompletedTask4Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task4' +\n                '.integration.worker', [\"taskReferenceName\": \"task4\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask4Try1)\n\n        and: \"verify that the 'integration_task_4' is COMPLETED and the workflow has COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_4'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'DECISION'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'EXCLUSIVE_JOIN'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].outputData['taskReferenceName'] == 'task4'\n        }\n    }\n\n    def \"Test when decision_1 is false and  decision_3 is true\"() {\n        given: \"The input parameter required to make decision_1 false and decision_3 true\"\n        def input = [\"decision_1\": \"false\", \"decision_3\": \"true\"]\n\n        when: \"An exclusive join workflow is started with then workflow input\"\n        def workflowInstanceId = startWorkflow(EXCLUSIVE_JOIN_WF, 1, 'exclusive_join_workflow',\n                input, null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1' +\n                '.integration.worker', [\"taskReferenceName\": \"task1\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_4'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_4' is polled and completed\"\n        def polledAndCompletedTask4Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task4' +\n                '.integration.worker', [\"taskReferenceName\": \"task4\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask4Try1)\n\n        and: \"verify that the 'integration_task_4' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_4'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'DECISION'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_5'\n            tasks[4].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_5' is polled and completed\"\n        def polledAndCompletedTask5Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_5', 'task5' +\n                '.integration.worker', [\"taskReferenceName\": \"task5\"])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask5Try1)\n\n        and: \"verify that the 'integration_task_4' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_4'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'DECISION'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_5'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'EXCLUSIVE_JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].outputData['taskReferenceName'] == 'task5'\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/ExternalPayloadStorageSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.test.base.AbstractSpecification\nimport com.netflix.conductor.test.utils.MockExternalPayloadStorage\nimport com.netflix.conductor.test.utils.UserTask\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPayload\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedLargePayloadTask\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass ExternalPayloadStorageSpec extends AbstractSpecification {\n\n    @Shared\n    def LINEAR_WORKFLOW_T1_T2 = 'integration_test_wf'\n\n    @Shared\n    def CONDITIONAL_SYSTEM_TASK_WORKFLOW = 'ConditionalSystemWorkflow'\n\n    @Shared\n    def FORK_JOIN_WF = 'FanInOutTest'\n\n    @Shared\n    def DYNAMIC_FORK_JOIN_WF = \"DynamicFanInOutTest\"\n\n    @Shared\n    def WORKFLOW_WITH_INLINE_SUB_WF = 'WorkflowWithInlineSubWorkflow'\n\n    @Shared\n    def WORKFLOW_WITH_DECISION_AND_TERMINATE = 'ConditionalTerminateWorkflow'\n\n    @Shared\n    def WORKFLOW_WITH_SYNCHRONOUS_SYSTEM_TASK = 'workflow_with_synchronous_system_task'\n\n    @Autowired\n    UserTask userTask\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Autowired\n    Join joinTask\n\n    @Autowired\n    MockExternalPayloadStorage mockExternalPayloadStorage\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('simple_workflow_1_integration_test.json',\n                'conditional_system_task_workflow_integration_test.json',\n                'fork_join_integration_test.json',\n                'simple_workflow_with_sub_workflow_inline_def_integration_test.json',\n                'decision_and_terminate_integration_test.json',\n                'workflow_with_synchronous_system_task.json',\n                'dynamic_fork_join_integration_test.json'\n        )\n    }\n\n    def \"Test simple workflow using external payload storage\"() {\n\n        given: \"An existing simple workflow definition\"\n        metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        and: \"input required to start large payload workflow\"\n        def correlationId = 'wf_external_storage'\n        String workflowInputPath = uploadInitialWorkflowInput()\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_1' with external payload storage\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_1', 'task1.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_2' with external payload storage\"\n        pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask(\"integration_task_2\", \"task2.integration.worker\", \"\")\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        then: \"verify that the 'integration_task_2' is complete and the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            output.isEmpty()\n\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n\n        }\n    }\n\n    def \"Test workflow with synchronous system task using external payload storage\"() {\n        given: \"An existing workflow definition with sync system task followed by a simple task\"\n        metadataService.getWorkflowDef(WORKFLOW_WITH_SYNCHRONOUS_SYSTEM_TASK, 1)\n\n        and: \"input required to start large payload workflow\"\n        def correlationId = 'wf_external_storage'\n        String workflowInputPath = uploadInitialWorkflowInput()\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_SYNCHRONOUS_SYSTEM_TASK, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_1' with external payload storage\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_1', 'task1.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[1].status == Task.Status.COMPLETED\n\n            tasks[1].outputData['result'] == 104 // output of .tp2.TEST_SAMPLE | length expression from output.json. On assertion failure, check workflow definition and output.json\n        }\n    }\n\n    def \"Test conditional workflow with system task using external payload storage\"() {\n\n        given: \"An existing workflow definition\"\n        metadataService.getWorkflowDef(CONDITIONAL_SYSTEM_TASK_WORKFLOW, 1)\n\n        and: \"input required to start large payload workflow\"\n        String workflowInputPath = uploadInitialWorkflowInput()\n        def correlationId = \"conditional_system_external_storage\"\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(CONDITIONAL_SYSTEM_TASK_WORKFLOW, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_1' with external payload storage\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_1', 'task1.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == \"DECISION\"\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == \"USER_TASK\"\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].inputData.isEmpty()\n\n        }\n\n        when: \"the system task 'USER_TASK' is started by issuing a system task call\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def taskId = workflow.getTaskByRefName('user_task').taskId\n        asyncSystemTaskExecutor.execute(userTask, taskId)\n\n        then: \"verify that the user task is in a COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == \"DECISION\"\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == \"USER_TASK\"\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].inputData.isEmpty()\n\n            tasks[2].outputData.get(\"size\") == 104\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete and 'integration_task_3'\"\n        def pollAndCompleteTask3 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task3.integration.worker',\n                ['op': 'success_task3'])\n\n        then: \"verify that the 'integration_task_3' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask3)\n\n        then: \"verify that the 'integration_task_3' is complete and the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            output.isEmpty()\n\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == \"DECISION\"\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == \"USER_TASK\"\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].inputData.isEmpty()\n\n            tasks[2].outputData.get(\"size\") == 104\n            tasks[3].taskType == 'integration_task_3'\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test fork join workflow using external payload storage\"() {\n\n        given: \"An existing fork join workflow definition\"\n        metadataService.getWorkflowDef(FORK_JOIN_WF, 1)\n\n        and: \"input required to start large payload workflow\"\n        def correlationId = 'fork_join_external_storage'\n        String workflowInputPath = uploadInitialWorkflowInput()\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_WF, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n        }\n\n        when: \"the first task of the left fork is polled and completed\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def polledAndAckTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndAckTask)\n\n        and: \"task is completed and the next task in the fork is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        when: \"the first task of the right fork is polled and completed with external payload storage\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        def polledAndAckLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_2', 'task2.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(polledAndAckLargePayloadTask)\n\n        and: \"task is completed and the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        when: \"the second task of the left fork is polled and completed with external payload storage\"\n        polledAndAckLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_3', 'task3.integration.worker', taskOutputPath)\n\n        and: \"the workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the 'integration_task_3' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(polledAndAckLargePayloadTask)\n\n        when: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"task is completed and the next task after join in scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].outputData.isEmpty()\n\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].outputData.isEmpty()\n\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_3'\n            tasks[4].outputData.isEmpty()\n\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'integration_task_4'\n        }\n\n        when: \"the task 'integration_task_4' is polled and completed\"\n        polledAndAckTask = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task4.integration.worker')\n\n        then: \"verify that the 'integration_task_4' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndAckTask)\n\n        and: \"task is completed and the workflow is in completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].outputData.isEmpty()\n\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].outputData.isEmpty()\n\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_3'\n            tasks[4].outputData.isEmpty()\n\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_4'\n        }\n    }\n\n    def \"Test workflow with subworkflow using external payload storage\"() {\n\n        given: \"An existing workflow definition\"\n        metadataService.getWorkflowDef(WORKFLOW_WITH_INLINE_SUB_WF, 1)\n\n        and: \"input required to start large payload workflow\"\n        String workflowInputPath = uploadInitialWorkflowInput()\n        def correlationId = \"workflow_with_inline_sub_wf_external_storage\"\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_INLINE_SUB_WF, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_1' with external payload storage\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_1', 'task1.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].inputData.isEmpty()\n\n        }\n\n        when: \"the subworkflow is started by issuing a system task call\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowTaskId = workflow.getTaskByRefName('swt').taskId\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId)\n\n        then: \"verify that the sub workflow task is in a IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].inputData.isEmpty()\n\n        }\n\n        when: \"sub workflow is retrieved\"\n        workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowInstanceId = workflow.getTaskByRefName('swt').subWorkflowId\n\n        then: \"verify that the sub workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            input.isEmpty()\n\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'integration_task_3'\n        }\n\n        when: \"poll and complete the 'integration_task_3' with external payload storage\"\n        pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_3', 'task3.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_3' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the sub workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            input.isEmpty()\n\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_3'\n            tasks[0].outputData.isEmpty()\n\n            output.isEmpty()\n\n        }\n\n        and: \"the subworkflow task is completed and the workflow is in running state\"\n        sweep(workflowInstanceId)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].inputData.isEmpty()\n\n            tasks[1].outputData.isEmpty()\n\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].inputData.isEmpty()\n\n        }\n\n        when: \"poll and complete the 'integration_task_2' with external payload storage\"\n        pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_2', 'task2.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the task is completed and the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            output.isEmpty()\n\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].inputData.isEmpty()\n\n            tasks[1].outputData.isEmpty()\n\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].inputData.isEmpty()\n\n            tasks[2].outputData.isEmpty()\n\n        }\n    }\n\n    def \"Test retry workflow using external payload storage\"() {\n\n        setup: \"Modify the task definition\"\n        def persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 2, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        modifiedTask2Definition.setRetryDelaySeconds(0)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing simple workflow definition\"\n        metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        and: \"input required to start large payload workflow\"\n        def correlationId = 'retry_wf_external_storage'\n        String workflowInputPath = uploadInitialWorkflowInput()\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_1' with external payload storage\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_1', 'task1.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n\n        }\n\n        when: \"poll and fail the 'integration_task_2'\"\n        def pollAndFailTask2Try1 = workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"verify that the task is polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndFailTask2Try1)\n\n        and: \"verify that task is retried and workflow is still running\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].inputData.isEmpty()\n\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].inputData.isEmpty()\n\n        }\n\n        when: \"poll and complete the retried 'integration_task_2'\"\n        def pollAndCompleteTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'success_task2'])\n\n        then: \"verify that the task is polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask2)\n\n        and: \"verify that the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            output.isEmpty()\n\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].inputData.isEmpty()\n\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].inputData.isEmpty()\n\n        }\n\n        cleanup:\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    def \"Test workflow with terminate in decision branch using external payload storage\"() {\n\n        given: \"An existing workflow definition\"\n        metadataService.getWorkflowDef(WORKFLOW_WITH_DECISION_AND_TERMINATE, 1)\n\n        and: \"input required to start large payload workflow\"\n        String workflowInputPath = uploadInitialWorkflowInput()\n        def correlationId = \"decision_terminate_external_storage\"\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_DECISION_AND_TERMINATE, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].seq == 1\n        }\n\n        when: \"poll and complete the 'integration_task_1' with external payload storage\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_1', 'task1.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has FAILED due to terminate task\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 3\n            output.isEmpty()\n\n            reasonForIncompletion.contains('Workflow is FAILED by TERMINATE task')\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[0].seq == 1\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].seq == 2\n            tasks[2].taskType == 'TERMINATE'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].inputData.isEmpty()\n\n            tasks[2].seq == 3\n            tasks[2].outputData.isEmpty()\n        }\n    }\n\n    def \"Test dynamic fork join workflow with subworkflow using external payload storage\"() {\n        given: \"An existing dynamic fork join workflow definition\"\n        metadataService.getWorkflowDef(DYNAMIC_FORK_JOIN_WF, 1)\n\n        and: \"input required to start large payload workflow\"\n        def correlationId = \"dynamic_fork_join_subworkflow_external_storage\"\n        String workflowInputPath = uploadInitialWorkflowInput()\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(DYNAMIC_FORK_JOIN_WF, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            input.isEmpty()\n\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_1' with external payload storage\"\n        String taskOutputPath = \"${UUID.randomUUID()}.json\"\n        mockExternalPayloadStorage.upload(taskOutputPath, mockExternalPayloadStorage.curateDynamicForkLargePayload())\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_1', 'task1.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        and: \"verify that workflow has progressed further ahead and new dynamic tasks have been scheduled with externalized payloads\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        with(workflow) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            !tasks[0].outputData\n            tasks[1].taskType == 'FORK'\n            !tasks[1].inputData\n\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            !tasks[2].inputData\n\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].referenceTaskName == 'dynamicfanouttask_join'\n        }\n    }\n\n    def \"Test update task output multiple times using external payload storage\"() {\n        given: \"An existing simple workflow definition\"\n        metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1, 'multi_task_update_external_storage', new HashMap<String, Object>(), null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and update 'integration_task_1' with external payload storage output\"\n        String taskOutputPath = uploadLargeTaskOutput()\n        workflowTestUtil.pollAndUpdateTask('integration_task_1', 'task1.integration.worker', taskOutputPath, null, 1)\n\n        then: \"verify that 'integration_task1's output is updated correctly\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].outputData.isEmpty()\n            tasks[0].externalOutputPayloadStoragePath == taskOutputPath\n        }\n\n        when: \"poll and update 'integration_task_1' with no additional output\"\n        workflowTestUtil.pollAndUpdateTask('integration_task_1', 'task1.integration.worker', null, null, 1)\n\n        then: \"verify that 'integration_task1's output is updated correctly\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].outputData.isEmpty()\n            // no duplicate upload\n            tasks[0].externalOutputPayloadStoragePath == taskOutputPath\n        }\n\n        when: \"poll and complete 'integration_task_1' with additional output\"\n        Map<String, Object> output = ['k1': 'v1', 'k2': 'v2']\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', output, 1)\n\n        then: \"verify that 'integration_task1 is complete and output is updated correctly\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n            // upload again with additional output\n            tasks[0].externalOutputPayloadStoragePath != taskOutputPath\n            verifyPayload(output, mockExternalPayloadStorage.downloadPayload(tasks[0].externalOutputPayloadStoragePath))\n\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and update 'integration_task_2' with output\"\n        Map<String, Object> output1 = ['k1': 'v1', 'k2': 'v2']\n        workflowTestUtil.pollAndUpdateTask('integration_task_2', 'task1.integration.worker', null, output1, 1)\n\n        then: \"verify that 'integration_task2's output is updated correctly\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].externalOutputPayloadStoragePath == null\n            verifyPayload(output1, tasks[1].outputData)\n        }\n\n        when: \"poll and complete 'integration_task_2' with additional output\"\n        Map<String, Object> output2 = ['k3': 'v3', 'k4': 'v4']\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', output2, 1)\n\n        then: \"verify that 'integration_task2 is complete and output is updated correctly\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.isEmpty()\n\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].externalOutputPayloadStoragePath == null\n            output1.putAll(output2)\n            verifyPayload(output1, tasks[1].outputData)\n        }\n    }\n\n    def \"Test fork join workflow exceed external storage limit should fail the task and workflow\"() {\n\n        given: \"An existing fork join workflow definition\"\n        metadataService.getWorkflowDef(FORK_JOIN_WF, 1)\n\n        and: \"input required to start large payload workflow\"\n        def correlationId = 'fork_join_external_storage'\n        String workflowInputPath = uploadInitialWorkflowInput()\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_WF, 1, correlationId, null, workflowInputPath)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n        }\n\n        when: \"the first task of the left fork is polled and completed\"\n        def polledAndAckTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndAckTask)\n\n        and: \"task is completed and the next task in the fork is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        when: \"the first task of the right fork is polled and completed with external payload storage\"\n        String taskOutputPath = \"${UUID.randomUUID()}.json\"\n        mockExternalPayloadStorage.upload(taskOutputPath, mockExternalPayloadStorage.createLargePayload(500))\n        def polledAndAckLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_2', 'task2.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(polledAndAckLargePayloadTask)\n\n        and: \"task is completed and the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        when: \"the second task of the left fork is polled and completed with external payload storage\"\n        taskOutputPath = \"${UUID.randomUUID()}.json\"\n        mockExternalPayloadStorage.upload(taskOutputPath, mockExternalPayloadStorage.createLargePayload(500))\n        polledAndAckLargePayloadTask = workflowTestUtil.pollAndCompleteLargePayloadTask('integration_task_3', 'task3.integration.worker', taskOutputPath)\n\n        then: \"verify that the 'integration_task_3' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(polledAndAckLargePayloadTask)\n\n        when: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"task is completed and the join task is failed because of exceeding size limit\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 5\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].outputData.isEmpty()\n            tasks[3].status == Task.Status.FAILED_WITH_TERMINAL_ERROR\n            tasks[3].taskType == 'JOIN'\n            tasks[3].outputData.isEmpty()\n            !tasks[3].getExternalOutputPayloadStoragePath()\n        }\n    }\n\n    private String uploadLargeTaskOutput() {\n        String taskOutputPath = \"${UUID.randomUUID()}.json\"\n        mockExternalPayloadStorage.upload(taskOutputPath, mockExternalPayloadStorage.readOutputDotJson(), 0)\n        return taskOutputPath\n    }\n\n    private String uploadInitialWorkflowInput() {\n        String workflowInputPath = \"${UUID.randomUUID()}.json\"\n        mockExternalPayloadStorage.upload(workflowInputPath, ['param1': 'p1 value', 'param2': 'p2 value', 'case': 'two'])\n        return workflowInputPath\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/FailureWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nclass FailureWorkflowSpec extends AbstractSpecification {\n\n    @Shared\n    def WORKFLOW_WITH_TERMINATE_TASK_FAILED = 'test_terminate_task_failed_wf'\n\n    @Shared\n    def PARENT_WORKFLOW_WITH_FAILURE_TASK = 'test_task_failed_parent_wf'\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    def setup() {\n        workflowTestUtil.registerWorkflows(\n                'failure_workflow_for_terminate_task_workflow.json',\n                'terminate_task_failed_workflow_integration.json',\n                'test_task_failed_parent_workflow.json',\n                'test_task_failed_sub_workflow.json'\n        )\n    }\n\n    def \"Test workflow with a task that failed\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['a'] = 1\n\n        when: \"Start the workflow which has the failed task\"\n        def testId = 'testId'\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_TERMINATE_TASK_FAILED, 1,\n                testId, workflowInput, null)\n\n        then: \"Verify that the workflow has failed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            reasonForIncompletion == \"Early exit in terminate\"\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'LAMBDA'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'TERMINATE'\n            tasks[1].seq == 2\n            output\n            def failedWorkflowId = output['conductor.failure_workflow'] as String\n            def workflowCorrelationId = correlationId\n            def workflowFailureTaskId = tasks[1].taskId\n            with(workflowExecutionService.getExecutionStatus(failedWorkflowId, true)) {\n                status == Workflow.WorkflowStatus.COMPLETED\n                correlationId == workflowCorrelationId\n                input['workflowId'] == workflowInstanceId\n                input['failureTaskId'] == workflowFailureTaskId\n                tasks.size() == 1\n                tasks[0].taskType == 'LAMBDA'\n                input['failedWorkflow'] != null\n            }\n        }\n    }\n\n    def \"Test workflow with a task failed in subworkflow\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['a'] = 1\n\n        when: \"Start the workflow which has the subworkflow task\"\n        def workflowInstanceId = startWorkflow(PARENT_WORKFLOW_WITH_FAILURE_TASK, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the workflow has started and the tasks are as expected\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'LAMBDA'\n            tasks[0].referenceTaskName == 'lambdaTask1'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].seq == 2\n        }\n\n        when: \"subworkflow is retrieved\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowTaskId = workflow.getTaskByRefName(\"test_task_failed_sub_wf\").getTaskId()\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId)\n        workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowId = workflow.getTaskByRefName(\"test_task_failed_sub_wf\").subWorkflowId\n\n        then: \"verify that the sub workflow has failed\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            reasonForIncompletion.contains('Workflow is FAILED by TERMINATE task')\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'LAMBDA'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'TERMINATE'\n            tasks[1].seq == 2\n        }\n\n        then: \"Verify that the workflow has failed and correct inputs passed into the failure workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'LAMBDA'\n            tasks[0].referenceTaskName == 'lambdaTask1'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].seq == 2\n            def failedWorkflowId = output['conductor.failure_workflow'] as String\n            def workflowCorrelationId = correlationId\n            def workflowFailureTaskId = tasks[1].taskId\n            with(workflowExecutionService.getExecutionStatus(failedWorkflowId, true)) {\n                status == Workflow.WorkflowStatus.COMPLETED\n                correlationId == workflowCorrelationId\n                input['workflowId'] == workflowInstanceId\n                input['failureTaskId'] == workflowFailureTaskId\n                tasks.size() == 1\n                tasks[0].taskType == 'LAMBDA'\n                input['failedWorkflow'] != null\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/ForkJoinSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nclass ForkJoinSpec extends AbstractSpecification {\n\n    @Autowired\n    Join joinTask\n\n    @Shared\n    def FORK_JOIN_WF = 'FanInOutTest'\n\n    @Shared\n    def FORK_JOIN_NESTED_WF = 'FanInOutNestedTest'\n\n    @Shared\n    def FORK_JOIN_NESTED_SUB_WF = 'FanInOutNestedSubWorkflowTest'\n\n    @Shared\n    def WORKFLOW_FORK_JOIN_OPTIONAL_SW = \"integration_test_fork_join_optional_sw\"\n\n    @Shared\n    def FORK_JOIN_SUB_WORKFLOW = 'integration_test_fork_join_sw'\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('fork_join_integration_test.json',\n                'fork_join_with_no_task_retry_integration_test.json',\n                'nested_fork_join_integration_test.json',\n                'simple_workflow_1_integration_test.json',\n                'nested_fork_join_with_sub_workflow_integration_test.json',\n                'simple_one_task_sub_workflow_integration_test.json',\n                'fork_join_with_optional_sub_workflow_forks_integration_test.json',\n                'fork_join_sub_workflow.json'\n        )\n    }\n\n    /**\n     *             start\n     *              |\n     *             fork\n     *            /     \\\n     *         task1     task2\n     *          |        /\n     *         task3    /\n     *           \\     /\n     *            \\  /\n     *            join\n     *              |\n     *            task4\n     *              |\n     *             End\n     */\n    def \"Test a simple workflow with fork join success flow\"() {\n        when: \"A fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_WF, 1,\n                'fanoutTest', [:],\n                null)\n\n        then: \"verify that the workflow has started and the starting nodes of the each fork are in scheduled state\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n        }\n\n        when: \"The first task of the fork is polled and completed\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"fanouttask_join\").getTaskId()\n        def polledAndAckTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker')\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask1Try1)\n\n        and: \"The workflow has been updated and has all the required tasks in the right status to move forward\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        when: \"The 'integration_task_3' is polled and completed\"\n        def polledAndAckTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task1.worker')\n\n        then: \"verify that the 'integration_task_3' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask3Try1)\n\n        and: \"The workflow has been updated with the task status and task list\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        when: \"The other node of the fork is completed by completing 'integration_task_2'\"\n        def polledAndAckTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.worker')\n\n        and: \"workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask2Try1)\n\n        when: \"JOIN task executed by the async executor\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"The workflow has been updated with the task status and task list\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'JOIN'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'integration_task_4'\n        }\n\n        when: \"The last task of the workflow is then polled and completed integration_task_4'\"\n        def polledAndAckTask4Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task1.worker')\n\n        then: \"verify that the 'integration_task_4' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask4Try1)\n\n        and: \"Then verify that the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 6\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_4'\n        }\n    }\n\n    def \"Test a simple workflow with fork join failure flow\"() {\n        setup: \"Ensure that 'integration_task_2' has a retry count of 0\"\n        def persistedIntegrationTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedIntegrationTask2Definition = new TaskDef(persistedIntegrationTask2Definition.name,\n                persistedIntegrationTask2Definition.description, persistedIntegrationTask2Definition.ownerEmail, 0,\n                0, persistedIntegrationTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedIntegrationTask2Definition)\n\n        when: \"A fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_WF, 1,\n                'fanoutTest', [:],\n                null)\n\n        then: \"verify that the workflow has started and the starting nodes of the each fork are in scheduled state\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n        }\n\n        when: \"The first task of the fork is polled and completed\"\n        def polledAndAckTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker')\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask1Try1)\n\n        and: \"The workflow has been updated and has all the required tasks in the right status to move forward\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        when: \"The other node of the fork is completed by completing 'integration_task_2'\"\n        def polledAndAckTask2Try1 = workflowTestUtil.pollAndFailTask('integration_task_2',\n                'task1.worker', 'Failed....')\n\n        and: \"workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask2Try1)\n\n        and: \"the workflow is in the failed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 5\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.CANCELED\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].taskType == 'integration_task_3'\n        }\n\n        cleanup: \"Restore the task definitions that were modified as part of this feature testing\"\n        metadataService.updateTaskDef(persistedIntegrationTask2Definition)\n    }\n\n    def \"Test retrying a failed fork join workflow\"() {\n\n        when: \"A fork join workflow is started\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_WF + '_2', 1,\n                'fanoutTest', [:],\n                null)\n\n        then: \"verify that the workflow has started and the starting nodes of the each fork are in scheduled state\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].taskType == 'integration_task_0_RT_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_0_RT_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n        }\n\n        when: \"The first task of the fork is polled and completed\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"fanouttask_join\").getTaskId()\n        def polledAndAckTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_0_RT_1', 'task1.worker')\n\n        then: \"verify that the 'integration_task_0_RT_1' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask1Try1)\n\n        and: \"The workflow has been updated and has all the required tasks in the right status to move forward\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0_RT_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_0_RT_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_0_RT_3'\n        }\n\n        when: \"The other node of the fork is completed by completing 'integration_task_0_RT_2'\"\n        def polledAndAckTask2Try1 = workflowTestUtil.pollAndFailTask('integration_task_0_RT_2',\n                'task1.worker', 'Failed....')\n\n        and: \"workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the 'integration_task_0_RT_1' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask2Try1)\n\n        and: \"the workflow is in the failed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 5\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0_RT_1'\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0_RT_2'\n            tasks[3].status == Task.Status.CANCELED\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].taskType == 'integration_task_0_RT_3'\n        }\n\n        when: \"The workflow is retried\"\n        workflowExecutor.retry(workflowInstanceId, false)\n\n        then: \"verify that all the workflow is retried and new tasks are added in place of the failed tasks\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0_RT_1'\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0_RT_2'\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].taskType == 'integration_task_0_RT_3'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'integration_task_0_RT_2'\n            tasks[6].status == Task.Status.SCHEDULED\n            tasks[6].taskType == 'integration_task_0_RT_3'\n        }\n\n        when: \"The 'integration_task_0_RT_3' is polled and completed\"\n        def polledAndAckTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_0_RT_3', 'task1.worker')\n\n        then: \"verify that the 'integration_task_3' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask3Try1)\n\n        when: \"The other node of the fork is completed by completing 'integration_task_0_RT_2'\"\n        def polledAndAckTask2Try2 = workflowTestUtil.pollAndCompleteTask('integration_task_0_RT_2', 'task1.worker')\n\n        and: \"workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask2Try2)\n\n        when: \"JOIN task is polled and executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        and: \"The last task of the workflow is then polled and completed integration_task_0_RT_4'\"\n        def polledAndAckTask4Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_0_RT_4', 'task1.worker')\n\n        then: \"verify that the 'integration_task_0_RT_4' was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask4Try1)\n\n        then: \"Then verify that the workflow is completed and the task list of execution is as expected\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 8\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_0_RT_1'\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_0_RT_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'JOIN'\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].taskType == 'integration_task_0_RT_3'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_0_RT_2'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_0_RT_3'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'integration_task_0_RT_4'\n        }\n    }\n\n    def \"Test nested fork join workflow success flow\"() {\n        given: \"Input for the nested fork join workflow\"\n        Map input = new HashMap<String, Object>()\n        input[\"case\"] = \"a\"\n\n        when: \"A nested workflow is started with the input\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_NESTED_WF, 1,\n                'fork_join_nested_test', input,\n                null)\n\n        then: \"verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks.findAll { it.referenceTaskName in ['t11', 't12', 't13', 'fork1', 'fork2'] }.size() == 5\n            tasks.findAll { it.referenceTaskName in ['t1', 't2', 't16'] }.size() == 0\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_11'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_12'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_13'\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[6].inputData['joinOn'] == ['t11', 'join2']\n\n        }\n\n        when: \"Poll and Complete tasks: 'integration_task_11', 'integration_task_12' and 'integration_task_13'\"\n        def outerJoinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join1\").getTaskId()\n        def innerJoinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join2\").getTaskId()\n        def polledAndAckTask11Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_11', 'task11.worker')\n        def polledAndAckTask12Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_12', 'task12.worker')\n        def polledAndAckTask13Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_13', 'task13.worker')\n\n        then: \"verify that tasks 'integration_task_11', 'integration_task_12' and 'integration_task_13' were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask11Try1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask12Try1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask13Try1)\n\n        and: \"verify the state of the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 10\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n\n            tasks[1].taskType == 'integration_task_11'\n            tasks[1].status == Task.Status.COMPLETED\n\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n\n            tasks[3].taskType == 'integration_task_12'\n            tasks[3].status == Task.Status.COMPLETED\n\n            tasks[4].taskType == 'integration_task_13'\n            tasks[4].status == Task.Status.COMPLETED\n\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[6].inputData['joinOn'] == ['t11', 'join2']\n\n            tasks[7].taskType == 'integration_task_14'\n            tasks[7].status == Task.Status.SCHEDULED\n\n            tasks[8].taskType == 'DECISION'\n            tasks[8].status == Task.Status.COMPLETED\n\n            tasks[9].taskType == 'integration_task_16'\n            tasks[9].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and Complete tasks: 'integration_task_16' and 'integration_task_14'\"\n        def polledAndAckTask16Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_16', 'task16.worker')\n        def polledAndAckTask14Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_14', 'task14.worker')\n\n        then: \"verify that tasks 'integration_task_16' and 'integration_task_14'were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask16Try1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask14Try1)\n\n        and: \"verify the state of the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 11\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[6].inputData['joinOn'] == ['t11', 'join2']\n\n            tasks[7].taskType == 'integration_task_14'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[8].taskType == 'DECISION'\n            tasks[8].status == Task.Status.COMPLETED\n            tasks[9].taskType == 'integration_task_16'\n            tasks[9].status == Task.Status.COMPLETED\n            tasks[10].taskType == 'integration_task_19'\n            tasks[10].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and Complete tasks: 'integration_task_19'\"\n        def polledAndAckTask19Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_19', 'task19.worker')\n\n        then: \"verify that tasks 'integration_task_19' polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask19Try1)\n\n        and: \"verify the state of the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 12\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[6].inputData['joinOn'] == ['t11', 'join2']\n            tasks[10].taskType == 'integration_task_19'\n            tasks[10].status == Task.Status.COMPLETED\n            tasks[11].taskType == 'integration_task_20'\n            tasks[11].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and Complete tasks: 'integration_task_20'\"\n        def polledAndAckTask20Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_20', 'task20.worker')\n\n        and: \"workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that task 'integration_task_20' is polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask20Try1)\n\n        when: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, innerJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, outerJoinId)\n\n        then: \"verify the state of the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 13\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n            tasks[6].taskType == 'JOIN'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[6].inputData['joinOn'] == ['t11', 'join2']\n            tasks[11].taskType == 'integration_task_20'\n            tasks[11].status == Task.Status.COMPLETED\n            tasks[12].taskType == 'integration_task_15'\n            tasks[12].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and Complete tasks: 'integration_task_15'\"\n        def polledAndAckTask15Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_15', 'task15.worker')\n\n        then: \"verify that tasks 'integration_task_15' polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask15Try1)\n\n        and: \"verify that the workflow is in a complete state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 13\n            tasks[12].taskType == 'integration_task_15'\n            tasks[12].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test nested workflow which contains a sub workflow task\"() {\n        given: \"Input for the nested fork join workflow\"\n        Map input = new HashMap<String, Object>()\n        input[\"case\"] = \"a\"\n\n        when: \"A nested workflow is started with the input\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_NESTED_SUB_WF, 1,\n                'fork_join_nested_test', input,\n                null)\n\n        then: \"The workflow is in the running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 8\n            tasks.findAll { it.referenceTaskName in ['t11', 't12', 't13', 'fork1', 'fork2', 'sw1'] }.size() == 6\n            tasks.findAll { it.referenceTaskName in ['t1', 't2', 't16'] }.size() == 0\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_11'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_12'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'integration_task_13'\n            tasks[4].status == Task.Status.SCHEDULED\n\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n\n            tasks[6].taskType == 'SUB_WORKFLOW'\n            tasks[6].status == Task.Status.SCHEDULED\n            tasks[7].taskType == 'JOIN'\n            tasks[7].status == Task.Status.IN_PROGRESS\n            tasks[7].inputData['joinOn'] == ['t11', 'join2', 'sw1']\n\n        }\n\n        when: \"Poll and Complete tasks: 'integration_task_11', 'integration_task_12' and 'integration_task_13'\"\n        def outerJoinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join1\").getTaskId()\n        def innerJoinId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"join2\").getTaskId()\n        def polledAndAckTask11Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_11', 'task11.worker')\n        def polledAndAckTask12Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_12', 'task12.worker')\n        def polledAndAckTask13Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_13', 'task13.worker')\n        workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n\n\n        then: \"verify that tasks 'integration_task_11', 'integration_task_12' and 'integration_task_13' were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask11Try1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask12Try1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask13Try1)\n\n        and: \"verify the state of the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 11\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_11'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'FORK'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_12'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'integration_task_13'\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n\n            tasks[6].taskType == 'SUB_WORKFLOW'\n            tasks[6].status == Task.Status.SCHEDULED\n            tasks[7].taskType == 'JOIN'\n            tasks[7].status == Task.Status.IN_PROGRESS\n            tasks[7].inputData['joinOn'] == ['t11', 'join2', 'sw1']\n            tasks[8].taskType == 'integration_task_14'\n            tasks[8].status == Task.Status.SCHEDULED\n            tasks[9].taskType == 'DECISION'\n            tasks[9].status == Task.Status.COMPLETED\n            tasks[10].taskType == 'integration_task_16'\n            tasks[10].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and Complete tasks: 'integration_task_16' and 'integration_task_14'\"\n        def polledAndAckTask16Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_16', 'task16.worker')\n        def polledAndAckTask14Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_14', 'task14.worker')\n\n        and: \"Get the sub workflow id associated with the SubWorkflow Task sw1 and start the system task\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowTaskId = workflow.getTaskByRefName(\"sw1\").getTaskId()\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId)\n        def updatedWorkflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowInstanceId = updatedWorkflow.getTaskByRefName('sw1').subWorkflowId\n\n        then: \"verify that tasks 'integration_task_16' and 'integration_task_14'were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask16Try1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask14Try1)\n        with(updatedWorkflow) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 12\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n            tasks[6].taskType == 'SUB_WORKFLOW'\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[7].taskType == 'JOIN'\n            tasks[7].status == Task.Status.IN_PROGRESS\n            tasks[7].inputData['joinOn'] == ['t11', 'join2', 'sw1']\n            tasks[8].taskType == 'integration_task_14'\n            tasks[8].status == Task.Status.COMPLETED\n            tasks[9].taskType == 'DECISION'\n            tasks[9].status == Task.Status.COMPLETED\n            tasks[10].taskType == 'integration_task_16'\n            tasks[10].status == Task.Status.COMPLETED\n            tasks[11].taskType == 'integration_task_19'\n            tasks[11].status == Task.Status.SCHEDULED\n        }\n\n        and: \"verify that the simple Sub Workflow is in running state and the first task related to it is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete all the tasks associated with the sub workflow\"\n        def polledAndAckTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.worker')\n        def polledAndAckTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.worker')\n\n        then: \"verify that tasks 'integration_task_1' and 'integration_task_2'were polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask1Try1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask2Try1)\n\n        and: \"verify that the simple Sub Workflow is in a COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        and: \" verify that the sub workflow task is completed and other preceding tasks are added to the workflow task list\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 12\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n            tasks[6].taskType == 'SUB_WORKFLOW'\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == 'JOIN'\n            tasks[7].status == Task.Status.IN_PROGRESS\n            tasks[7].inputData['joinOn'] == ['t11', 'join2', 'sw1']\n            tasks[11].taskType == 'integration_task_19'\n            tasks[11].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Also the poll and complete the 'integration_task_19'\"\n        def polledAndAckTask19Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_19', 'task19.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask19Try1)\n\n        and: \"verify that the integration_task_19 is completed and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 13\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n            tasks[7].taskType == 'JOIN'\n            tasks[7].status == Task.Status.IN_PROGRESS\n            tasks[7].inputData['joinOn'] == ['t11', 'join2', 'sw1']\n            tasks[11].taskType == 'integration_task_19'\n            tasks[11].status == Task.Status.COMPLETED\n            tasks[12].taskType == 'integration_task_20'\n            tasks[12].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_20'\"\n        def polledAndAckTask20Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_20', 'task20.worker')\n\n        and: \"workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask20Try1)\n\n        when: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, innerJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, outerJoinId)\n\n        then: \"verify that the integration_task_20 is completed and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 14\n            tasks[5].taskType == 'JOIN'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].inputData['joinOn'] == ['t14', 't20']\n\n            tasks[7].taskType == 'JOIN'\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[7].inputData['joinOn'] == ['t11', 'join2', 'sw1']\n\n            tasks[12].taskType == 'integration_task_20'\n            tasks[12].status == Task.Status.COMPLETED\n            tasks[13].taskType == 'integration_task_15'\n            tasks[13].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'integration_task_15'\"\n        def polledAndAckTask15Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_15', 'task15.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckTask15Try1)\n\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 14\n            tasks[13].taskType == 'integration_task_15'\n            tasks[13].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test fork join with sub workflows containing optional tasks\"() {\n        given: \"A input to the workflow that has forks of sub workflows with an optional task\"\n        Map workflowInput = new HashMap<String, Object>()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        when: \"A workflow that has forks of sub workflows with an optional task is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_FORK_JOIN_OPTIONAL_SW, 1,\n                '', workflowInput,\n                null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"both the sub workflows are started by issuing a system task call\"\n        def workflowWithScheduledSubWorkflows = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowTaskId1 = workflowWithScheduledSubWorkflows.getTaskByRefName('st1').taskId\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId1)\n        def subWorkflowTaskId2 = workflowWithScheduledSubWorkflows.getTaskByRefName('st2').taskId\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId2)\n        def joinTaskId = workflowWithScheduledSubWorkflows.getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify that the sub workflow tasks are in a IN PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[3].inputData['joinOn'] == ['st1', 'st2']\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"Also verify that the sub workflows are in a RUNNING state\"\n        def workflowWithRunningSubWorkflows = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowInstanceId1 = workflowWithRunningSubWorkflows.getTaskByRefName('st1').subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId1, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        def subWorkflowInstanceId2 = workflowWithRunningSubWorkflows.getTaskByRefName('st2').subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId2, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        when: \"The 'simple_task_in_sub_wf' belonging to both the sub workflows is polled and failed\"\n        def polledAndAckSubWorkflowTask1 = workflowTestUtil.pollAndFailTask('simple_task_in_sub_wf',\n                'task1.worker', 'Failed....')\n        def polledAndAckSubWorkflowTask2 = workflowTestUtil.pollAndFailTask('simple_task_in_sub_wf',\n                'task1.worker', 'Failed....')\n\n        then: \"verify that both the tasks were polled and failed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckSubWorkflowTask1)\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndAckSubWorkflowTask2)\n\n        and: \"verify that both the sub workflows are in failed state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId1, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId2, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n        sweep(workflowInstanceId)\n\n        when: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"verify that the workflow is in a COMPLETED state and the join task is also marked as COMPLETED_WITH_ERRORS\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.COMPLETED_WITH_ERRORS\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.COMPLETED_WITH_ERRORS\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.COMPLETED_WITH_ERRORS\n        }\n\n        when: \"do a rerun on the sub workflow\"\n        def reRunSubWorkflowRequest = new RerunWorkflowRequest()\n        reRunSubWorkflowRequest.reRunFromWorkflowId = subWorkflowInstanceId1\n        workflowExecutor.rerun(reRunSubWorkflowRequest)\n\n        then: \"verify that the sub workflows are in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId1, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        and: \"parent workflow remains the same\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.COMPLETED_WITH_ERRORS\n            tasks[2].taskType == 'SUB_WORKFLOW'\n            tasks[2].status == Task.Status.COMPLETED_WITH_ERRORS\n            tasks[3].taskType == 'JOIN'\n            tasks[3].status == Task.Status.COMPLETED_WITH_ERRORS\n        }\n    }\n\n    def \"Test fork join with sub workflow task using task definition\"() {\n        given: \"A input to the workflow that has fork with sub workflow task\"\n        Map workflowInput = new HashMap<String, Object>()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        when: \"A workflow that has fork with sub workflow task is started\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_SUB_WORKFLOW, 1, '', workflowInput, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].inputData['joinOn'] == ['st1', 't2']\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the subworkflow is started by issuing a system task call\"\n        def parentWorkflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowTaskId = parentWorkflow.getTaskByRefName('st1').taskId\n        def jointaskId = parentWorkflow.getTaskByRefName(\"fanouttask_join\").taskId\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId)\n\n        then: \"verify that the sub workflow task is in a IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].inputData['joinOn'] == ['st1', 't2']\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"sub workflow is retrieved\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowInstanceId = workflow.getTaskByRefName('st1').subWorkflowId\n\n        then: \"verify that the sub workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        when: \"the 'simple_task_in_sub_wf' belonging to the sub workflow is polled and failed\"\n        def polledAndFailSubWorkflowTask = workflowTestUtil.pollAndFailTask('simple_task_in_sub_wf',\n                'task1.worker', 'Failed....')\n\n        then: \"verify that the task was polled and failed\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndFailSubWorkflowTask)\n\n        and: \"verify that the sub workflow is in failed state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        and: \"verify that the workflow is in a RUNNING state and sub workflow task is retried\"\n        sweep(workflowInstanceId)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].inputData['joinOn'] == ['st1', 't2']\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == 'SUB_WORKFLOW'\n            tasks[4].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the sub workflow is started by issuing a system task call\"\n        parentWorkflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        subWorkflowTaskId = parentWorkflow.getTaskByRefName('st1').taskId\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId)\n\n        then: \"verify that the sub workflow task is in a IN PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].inputData['joinOn'] == ['st1', 't2']\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == 'SUB_WORKFLOW'\n            tasks[4].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"sub workflow is retrieved\"\n        workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        subWorkflowInstanceId = workflow.getTaskByRefName('st1').subWorkflowId\n\n        then: \"verify that the sub workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        when: \"the 'simple_task_in_sub_wf' belonging to the sub workflow is polled and completed\"\n        def polledAndCompletedSubWorkflowTask = workflowTestUtil.pollAndCompleteTask('simple_task_in_sub_wf', 'subworkflow.task.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndCompletedSubWorkflowTask)\n\n        and: \"verify that the sub workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n        }\n\n        and: \"verify that the workflow is in a RUNNING state and sub workflow task is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].inputData['joinOn'] == ['st1', 't2']\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == 'SUB_WORKFLOW'\n            tasks[4].status == Task.Status.COMPLETED\n        }\n\n        when: \"the simple task is polled and completed\"\n        def polledAndCompletedSimpleTask = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.worker')\n\n        and: \"workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the task was polled and acknowledged\"\n        workflowTestUtil.verifyPolledAndAcknowledgedTask(polledAndCompletedSimpleTask)\n\n        when: \"JOIN task is executed\"\n        asyncSystemTaskExecutor.execute(joinTask, jointaskId)\n\n        then: \"verify that the workflow is in a COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 5\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].inputData['joinOn'] == ['st1', 't2']\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'SUB_WORKFLOW'\n            tasks[4].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/HierarchicalForkJoinSubworkflowRerunSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass HierarchicalForkJoinSubworkflowRerunSpec extends AbstractSpecification {\n\n    @Shared\n    def FORK_JOIN_HIERARCHICAL_SUB_WF = 'hierarchical_fork_join_swf'\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Autowired\n    Join joinTask\n\n    String rootWorkflowId, midLevelWorkflowId, leafWorkflowId\n\n    TaskDef persistedTask2Definition\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('hierarchical_fork_join_swf.json',\n                'simple_workflow_1_integration_test.json'\n        )\n\n        //region Test setup: 3 workflows reach FAILED state because task 'integration_task_2' in leaf workflow is FAILED.\n        setup: \"Modify task definition to 0 retries\"\n        persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(FORK_JOIN_HIERARCHICAL_SUB_WF, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'rerun_on_root_in_3level_wf'\n        def input = [\n                'param1'   : 'p1 value',\n                'param2'   : 'p2 value',\n                'subwf'    : FORK_JOIN_HIERARCHICAL_SUB_WF,\n                'nextSubwf': SIMPLE_WORKFLOW]\n\n        when: \"the workflow is started\"\n        rootWorkflowId = startWorkflow(FORK_JOIN_HIERARCHICAL_SUB_WF, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_2 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        def rootWorkflowInstance = workflowExecutionService.getExecutionStatus(rootWorkflowId, true)\n        with(rootWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n        }\n\n        and: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        midLevelWorkflowId = rootWorkflowInstance.tasks[1].subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"poll and complete the integration_task_2 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def midLevelWorkflowInstance = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)\n\n        then: \"verify that the leaf workflow is RUNNING, and first task is in SCHEDULED state\"\n        leafWorkflowId = midLevelWorkflowInstance.tasks[1].subWorkflowId\n        def leafWorkflowInstance = workflowExecutionService.getExecutionStatus(leafWorkflowId, true)\n        with(leafWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and fail the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"the leaf workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the mid level workflow is 'decided'\"\n        sweep(midLevelWorkflowId)\n\n        then: \"the mid level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the root level workflow is 'decided'\"\n        sweep(rootWorkflowId)\n\n        then: \"the root level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n        //endregion\n    }\n\n    def cleanup() {\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the root workflow.\n     *\n     * Expectation: The root workflow gets a new execution with the same id and spawns a NEW mid-level workflow, which in turn spawns a NEW leaf workflow.\n     * When the NEW leaf workflow completes successfully, both the NEW mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the root-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the root workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = rootWorkflowId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the root workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_2 task in the root workflow\"\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        and: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newMidLevelWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new mid level workflow is created and is in RUNNING state\"\n        newMidLevelWorkflowId != midLevelWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_2 task in the mid-level workflow\"\n        def midJoinId = workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        and: \"poll and execute the sub workflow task\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the two tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"the new leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the new mid level and root workflows are 'decided'\"\n        sweep(newMidLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(newMidLevelWorkflowId)\n\n        then: \"the root workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the mid-level workflow.\n     *\n     * Expectation: The mid-level workflow gets a new execution with the same id and spawns a NEW leaf workflow and also updates its parent (root workflow).\n     * When the NEW leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the mid-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the mid level workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = midLevelWorkflowId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the mid workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify the root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"poll and complete the integration_task_2 task in the mid level workflow\"\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        and: \"the SUB_WORKFLOW task in mid level workflow is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 2 tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the new leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the leaf workflow.\n     *\n     * Expectation: The leaf workflow gets a new execution with the same id and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the leaf-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the leaf workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = leafWorkflowId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the leaf workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the mid level and root workflows are sweeped\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify that the mid level workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the root workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete both tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    void assertWorkflowIsCompleted(String workflowId) {\n        assert with(workflowExecutionService.getExecutionStatus(workflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/HierarchicalForkJoinSubworkflowRestartSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass HierarchicalForkJoinSubworkflowRestartSpec extends AbstractSpecification {\n\n    @Shared\n    def FORK_JOIN_HIERARCHICAL_SUB_WF = 'hierarchical_fork_join_swf'\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Autowired\n    Join joinTask\n\n    String rootWorkflowId, midLevelWorkflowId, leafWorkflowId\n\n    TaskDef persistedTask2Definition\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('hierarchical_fork_join_swf.json',\n                'simple_workflow_1_integration_test.json'\n        )\n\n        //region Test setup: 3 workflows reach FAILED state. Task 'integration_task_2' in leaf workflow is FAILED.\n        setup: \"Modify task definition to 0 retries\"\n        persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(FORK_JOIN_HIERARCHICAL_SUB_WF, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'retry_on_root_in_3level_wf'\n        def input = [\n                'param1'   : 'p1 value',\n                'param2'   : 'p2 value',\n                'subwf'    : FORK_JOIN_HIERARCHICAL_SUB_WF,\n                'nextSubwf': SIMPLE_WORKFLOW]\n\n        when: \"the workflow is started\"\n        rootWorkflowId = startWorkflow(FORK_JOIN_HIERARCHICAL_SUB_WF, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        def rootWorkflowInstance = workflowExecutionService.getExecutionStatus(rootWorkflowId, true)\n        with(rootWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n        }\n\n        and: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        midLevelWorkflowId = rootWorkflowInstance.tasks[1].subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def midLevelWorkflowInstance = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)\n\n        then: \"verify that the leaf workflow is RUNNING, and first task is in SCHEDULED state\"\n        leafWorkflowId = midLevelWorkflowInstance.tasks[1].subWorkflowId\n        def leafWorkflowInstance = workflowExecutionService.getExecutionStatus(leafWorkflowId, true)\n        with(leafWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and fail the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"the leaf workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the mid level workflow is 'decided'\"\n        sweep(midLevelWorkflowId)\n\n        then: \"the mid level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the root level workflow is 'decided'\"\n        sweep(rootWorkflowId)\n\n        then: \"the root level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n        //endregion\n    }\n\n    def cleanup() {\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A restart is executed on the root workflow.\n     *\n     * Expectation: The root workflow gets a new execution with the same id and spawns a NEW mid-level workflow, which in turn spawns a NEW leaf workflow.\n     * When the NEW leaf workflow completes successfully, both the NEW mid-level and root workflows also complete successfully.\n     */\n    def \"Test restart on the root in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a restart on the root workflow\"\n        workflowExecutor.restart(rootWorkflowId, false)\n\n        then: \"verify that the root workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_2 task in the root workflow\"\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        and: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newMidLevelWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new mid level workflow is created and is in RUNNING state\"\n        newMidLevelWorkflowId != midLevelWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_2 task in the mid-level workflow\"\n        def midJoinId = workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        and: \"poll and execute the sub workflow task\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the two tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"the new leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the new mid level and root workflows are 'decided'\"\n        sweep(newMidLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(newMidLevelWorkflowId)\n\n        then: \"the root workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A restart is executed on the mid-level workflow.\n     *\n     * Expectation: The mid-level workflow gets a new execution with the same id and spawns a NEW leaf workflow and also updates its parent (root workflow).\n     * When the NEW leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test restart on the mid-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a restart on the mid level workflow\"\n        workflowExecutor.restart(midLevelWorkflowId, false)\n\n        then: \"verify that the mid workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify the root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"poll and complete the integration_task_2 task in the mid level workflow\"\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        and: \"the SUB_WORKFLOW task in mid level workflow is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 2 tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the new leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A restart is executed on the leaf workflow.\n     *\n     * Expectation: The leaf workflow gets a new execution with the same id and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test restart on the leaf in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a restart on the leaf workflow\"\n        workflowExecutor.restart(leafWorkflowId, false)\n\n        then: \"verify that the leaf workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the mid level and root workflows are sweeped\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the root workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete both tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    void assertWorkflowIsCompleted(String workflowId) {\n        assert with(workflowExecutionService.getExecutionStatus(workflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/HierarchicalForkJoinSubworkflowRetrySpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass HierarchicalForkJoinSubworkflowRetrySpec extends AbstractSpecification {\n\n    @Shared\n    def FORK_JOIN_HIERARCHICAL_SUB_WF = 'hierarchical_fork_join_swf'\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Autowired\n    Join joinTask\n\n    String rootWorkflowId, midLevelWorkflowId, leafWorkflowId\n\n    TaskDef persistedTask2Definition\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('hierarchical_fork_join_swf.json',\n                'simple_workflow_1_integration_test.json'\n        )\n\n        //region Test setup: 3 workflows reach FAILED state. Task 'integration_task_2' in leaf workflow is FAILED.\n        setup: \"Modify task definition to 0 retries\"\n        persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(FORK_JOIN_HIERARCHICAL_SUB_WF, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'retry_on_root_in_3level_wf'\n        def input = [\n                'param1'   : 'p1 value',\n                'param2'   : 'p2 value',\n                'subwf'    : FORK_JOIN_HIERARCHICAL_SUB_WF,\n                'nextSubwf': SIMPLE_WORKFLOW]\n\n        when: \"the workflow is started\"\n        rootWorkflowId = startWorkflow(FORK_JOIN_HIERARCHICAL_SUB_WF, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        def rootWorkflowInstance = workflowExecutionService.getExecutionStatus(rootWorkflowId, true)\n        with(rootWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n        }\n\n        and: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        midLevelWorkflowId = rootWorkflowInstance.tasks[1].subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def midLevelWorkflowInstance = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)\n\n        then: \"verify that the leaf workflow is RUNNING, and first task is in SCHEDULED state\"\n        leafWorkflowId = midLevelWorkflowInstance.tasks[1].subWorkflowId\n        def leafWorkflowInstance = workflowExecutionService.getExecutionStatus(leafWorkflowId, true)\n        with(leafWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and fail the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"the leaf workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the mid level workflow is 'decided'\"\n        sweep(midLevelWorkflowId)\n\n        then: \"the mid level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the root level workflow is 'decided'\"\n        sweep(rootWorkflowId)\n\n        then: \"the root level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n        //endregion\n    }\n\n    def cleanup() {\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed on the root workflow.\n     *\n     * Expectation: The root workflow spawns a NEW mid-level workflow, which in turn spawns a NEW leaf workflow.\n     * When the leaf workflow completes successfully, both the NEW mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the root in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the root workflow\"\n        workflowExecutor.retry(rootWorkflowId, false)\n\n        then: \"verify that the root workflow created a new SUB_WORKFLOW task\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newMidLevelWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify that a new mid level workflow is created and is in RUNNING state\"\n        newMidLevelWorkflowId != midLevelWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n        def midJoinId = workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        and: \"poll and execute the sub workflow task\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the two tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"the new leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the new mid level and root workflows are 'decided'\"\n        sweep(newMidLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(newMidLevelWorkflowId)\n\n        then: \"the root workflow is in COMPLETED state\"\n        assertSubWorkflowTaskIsRetriedAndWorkflowCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed with resume flag on the root workflow.\n     *\n     * Expectation: The leaf workflow is retried and both its parent (mid-level) and grand parent (root) workflows are also retried.\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the mid-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the mid level workflow\"\n        workflowExecutor.retry(midLevelWorkflowId, false)\n\n        then: \"verify that the mid workflow created a new SUB_WORKFLOW task\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[4].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].retriedTaskId == tasks[1].taskId\n        }\n\n        and: \"verify the SUB_WORKFLOW task in root workflow is IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the SUB_WORKFLOW task in mid level workflow is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 2 tasks in the leaf workflow\"\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the new leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        assertSubWorkflowTaskIsRetriedAndWorkflowCompleted(midLevelWorkflowId)\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed on the mid-level workflow.\n     *\n     * Expectation: The mid-level workflow spawns a NEW leaf workflow and also updates its parent (root workflow).\n     * When the NEW leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the leaf in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the leaf workflow\"\n        workflowExecutor.retry(leafWorkflowId, false)\n\n        then: \"verify that the leaf workflow is in RUNNING state and failed task is retried\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        then: \"verify that the mid-level workflow's SUB_WORKFLOW task is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the root workflow's SUB_WORKFLOW task is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify that the mid-level workflow's JOIN task is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the root workflow's JOIN task is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the scheduled task in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed with resume flag on the root workflow.\n     *\n     * Expectation: The leaf workflow is retried and both its parent (mid-level) and grand parent (root) workflows are also retried.\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the root with resume flag in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the root workflow\"\n        workflowExecutor.retry(rootWorkflowId, true)\n\n        then: \"verify that the sub workflow task in root workflow is IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the sub workflow task in mid level workflow is IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the previously failed task in leaf workflow is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify the mid level workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify the root workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n\n        and: \"the root workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed on the mid-level workflow.\n     *\n     * Expectation: The leaf workflow resumes its FAILED task and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the mid-level with resume flag in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the root workflow\"\n        workflowExecutor.retry(midLevelWorkflowId, true)\n\n        then: \"verify that the sub workflow task in root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the sub workflow task in mid level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the previously failed task in leaf workflow is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify the mid level workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify the root workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the previously failed integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n\n        and: \"the root workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed with resume flag on the leaf workflow.\n     *\n     * Expectation: The leaf workflow resumes its FAILED task and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the leaf with resume flag in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the leaf workflow\"\n        workflowExecutor.retry(leafWorkflowId, true)\n\n        then: \"verify that the leaf workflow is in RUNNING state and failed task is retried\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        and: \"verify that the root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.CANCELED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n        def midJoinId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n        def rootJoinId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).getTaskByRefName(\"fanouttask_join\").taskId\n\n        then: \"verify the mid level workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify the root workflow's JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the scheduled task in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, midJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, rootJoinId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(midLevelWorkflowId)\n\n        and: \"the root workflow is in COMPLETED state\"\n        assertWorkflowIsCompleted(rootWorkflowId)\n        //endregion\n    }\n\n    void assertWorkflowIsCompleted(String workflowId) {\n        assert with(workflowExecutionService.getExecutionStatus(workflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n\n    void assertSubWorkflowTaskIsRetriedAndWorkflowCompleted(String workflowId) {\n        assert with(workflowExecutionService.getExecutionStatus(workflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 5\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == TASK_TYPE_JOIN\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[4].retriedTaskId == tasks[1].taskId\n        }\n    }\n\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/JsonJQTransformSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nclass JsonJQTransformSpec extends AbstractSpecification {\n\n    @Shared\n    def JSON_JQ_TRANSFORM_WF = 'test_json_jq_transform_wf'\n\n    @Shared\n    def SEQUENTIAL_JSON_JQ_TRANSFORM_WF = 'sequential_json_jq_transform_wf'\n\n    @Shared\n    def JSON_JQ_TRANSFORM_RESULT_WF = 'json_jq_transform_result_wf'\n\n    def setup() {\n        workflowTestUtil.registerWorkflows(\n                'simple_json_jq_transform_integration_test.json',\n                'sequential_json_jq_transform_integration_test.json',\n                'json_jq_transform_result_integration_test.json'\n        )\n    }\n\n    /**\n     * Given the following input JSON\n     *{*   \"in1\": {*     \"array\": [ \"a\", \"b\" ]\n     *},\n     *   \"in2\": {*     \"array\": [ \"c\", \"d\" ]\n     *}*}* expect the workflow task to transform to following result:\n     *{*     out: [ \"a\", \"b\", \"c\", \"d\" ]\n     *}*/\n    def \"Test workflow with json jq transform task succeeds\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['in1'] = new HashMap()\n        workflowInput['in1']['array'] = [\"a\", \"b\"]\n        workflowInput['in2'] = new HashMap()\n        workflowInput['in2']['array'] = [\"c\", \"d\"]\n\n        when: \"workflow which has the json jq transform task has started\"\n        def workflowInstanceId = startWorkflow(JSON_JQ_TRANSFORM_WF, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the workflow and task are completed with expected output\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[0].outputData.containsKey(\"result\") && tasks[0].outputData.containsKey(\"resultList\")\n        }\n    }\n\n    /**\n     * Given the following input JSON\n     *{*   \"in1\": \"a\",\n     *   \"in2\": \"b\"\n     *}* using the same query from the success test, jq will try to get in1['array']\n     * and fail since 'in1' is a string\n     */\n    def \"Test workflow with json jq transform task fails\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['in1'] = \"a\"\n        workflowInput['in2'] = \"b\"\n\n        when: \"workflow which has the json jq transform task has started\"\n        def workflowInstanceId = startWorkflow(JSON_JQ_TRANSFORM_WF, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the workflow and task failed with expected error\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[0].reasonForIncompletion == 'Cannot index string with string \\\"array\\\"'\n        }\n    }\n\n    /**\n     * Given the following invalid input JSON\n     *{*   \"in1\": \"a\",\n     *   \"in2\": \"b\"\n     *}* using the same query from the success test, jq will try to get in1['array']\n     * and fail since 'in1' is a string.\n     *\n     * Re-run failed system task with the following valid input JSON will fix the workflow\n     *{*   \"in1\": {*     \"array\": [ \"a\", \"b\" ]\n     *},\n     *   \"in2\": {*     \"array\": [ \"c\", \"d\" ]\n     *}*}* expect the workflow task to transform to following result:\n     *{*     out: [ \"a\", \"b\", \"c\", \"d\" ]\n     *}\n     */\n    def \"Test rerun workflow with failed json jq transform task\"() {\n        given: \"workflow input\"\n        def invalidInput = new HashMap()\n        invalidInput['in1'] = \"a\"\n        invalidInput['in2'] = \"b\"\n\n        def validInput = new HashMap()\n        def input = new HashMap()\n        input['in1'] = new HashMap()\n        input['in1']['array'] = [\"a\", \"b\"]\n        input['in2'] = new HashMap()\n        input['in2']['array'] = [\"c\", \"d\"]\n        validInput['input'] = input\n        validInput['queryExpression'] = '.input as $_ | { out: ($_.in1.array + $_.in2.array) }'\n\n        when: \"workflow which has the json jq transform task started\"\n        def workflowInstanceId = startWorkflow(JSON_JQ_TRANSFORM_WF, 1,\n                '', invalidInput, null)\n\n        then: \"verify that the workflow and task failed with expected error\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[0].reasonForIncompletion == 'Cannot index string with string \\\"array\\\"'\n        }\n\n        when: \"workflow which has the json jq transform task reran\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = workflowInstanceId\n        def reRunTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[0].taskId\n        reRunWorkflowRequest.reRunFromTaskId = reRunTaskId\n        reRunWorkflowRequest.taskInput = validInput\n\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the workflow and task are completed with expected output\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[0].outputData.containsKey(\"result\") && tasks[0].outputData.containsKey(\"resultList\")\n        }\n    }\n\n    def \"Test json jq transform task with nested json object succeeds\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput[\"method\"] = \"POST\"\n        workflowInput['body'] = new HashMap()\n        workflowInput['body']['name'] = \"Beary Beariston\"\n        workflowInput['body']['title'] = \"the Brown Bear\"\n        workflowInput[\"requestTransform\"] = \"{name: (.body.name + \\\" you are \\\" + .body.title) }\"\n        workflowInput[\"responseTransform\"] = \"{result: \\\"reply: \\\" + .response.body.message}\"\n\n        when: \"workflow which has the json jq transform task has started\"\n        def workflowInstanceId = startWorkflow(SEQUENTIAL_JSON_JQ_TRANSFORM_WF, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the workflow and task are completed with expected output\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[0].outputData.containsKey(\"result\") && tasks[0].outputData.containsKey(\"resultList\")\n            HashMap<String, Object> result1 = (HashMap<String, Object>) tasks[0].outputData.get(\"result\")\n            result1.get(\"method\") == workflowInput[\"method\"]\n            result1.get(\"requestTransform\") == workflowInput[\"requestTransform\"]\n            result1.get(\"responseTransform\") == workflowInput[\"responseTransform\"]\n\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[1].outputData.containsKey(\"result\") && tasks[0].outputData.containsKey(\"resultList\")\n            HashMap<String, Object> result2 = (HashMap<String, Object>) tasks[1].outputData.get(\"result\")\n            result2.get(\"name\") == \"Beary Beariston you are the Brown Bear\"\n        }\n    }\n\n    def \"Test json jq transform task with different json object results succeeds\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput[\"requestedAction\"] = \"redeliver\"\n\n        when: \"workflow which has the json jq transform task has started\"\n        def workflowInstanceId = startWorkflow(JSON_JQ_TRANSFORM_RESULT_WF, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the workflow and task are completed with expected output\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[0].outputData.containsKey(\"result\") && tasks[0].outputData.containsKey(\"resultList\")\n            assert tasks[0].outputData.get(\"result\") == \"CREATE\"\n\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'DECISION'\n            assert tasks[1].inputData.get(\"case\") == \"CREATE\"\n\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'JSON_JQ_TRANSFORM'\n            tasks[2].outputData.containsKey(\"result\") && tasks[0].outputData.containsKey(\"resultList\")\n            List<String> result = (List<String>) tasks[2].outputData.get(\"result\")\n            assert result.size() == 3\n            assert result.indexOf(\"redeliver\") >= 0\n\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'DECISION'\n            assert tasks[3].inputData.get(\"case\") == \"true\"\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/LambdaAndTerminateTaskSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass LambdaAndTerminateTaskSpec extends AbstractSpecification {\n\n    @Shared\n    def WORKFLOW_WITH_TERMINATE_TASK = 'test_terminate_task_wf'\n\n    @Shared\n    def WORKFLOW_WITH_TERMINATE_TASK_FAILED = 'test_terminate_task_failed_wf'\n\n    @Shared\n    def WORKFLOW_WITH_LAMBDA_TASK = 'test_lambda_wf'\n\n    @Shared\n    def PARENT_WORKFLOW_WITH_TERMINATE_TASK = 'test_terminate_task_parent_wf'\n\n    @Shared\n    def WORKFLOW_WITH_DECISION_AND_TERMINATE = \"ConditionalTerminateWorkflow\"\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    def setup() {\n        workflowTestUtil.registerWorkflows(\n                'failure_workflow_for_terminate_task_workflow.json',\n                'terminate_task_completed_workflow_integration_test.json',\n                'terminate_task_failed_workflow_integration.json',\n                'simple_lambda_workflow_integration_test.json',\n                'terminate_task_parent_workflow.json',\n                'terminate_task_sub_workflow.json',\n                'decision_and_terminate_integration_test.json'\n        )\n    }\n\n    def \"Test workflow with a terminate task when the status is completed\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['a'] = 1\n\n        when: \"Start the workflow which has the terminate task\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_TERMINATE_TASK, 1,\n                '', workflowInput, null)\n\n        then: \"Ensure that the workflow has started and the first task is in scheduled state and workflow output should be terminate task's output\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            reasonForIncompletion.contains('Workflow is COMPLETED by TERMINATE task')\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'LAMBDA'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'TERMINATE'\n            tasks[1].seq == 2\n            output.size() == 1\n            output as String == \"[result:[testvalue:true]]\"\n        }\n    }\n\n    def \"Test workflow with a terminate task when the status is failed\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['a'] = 1\n\n        when: \"Start the workflow which has the terminate task\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_TERMINATE_TASK_FAILED, 1,\n                '', workflowInput, null)\n\n        then: \"Verify that the workflow has failed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            reasonForIncompletion == \"Early exit in terminate\"\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'LAMBDA'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'TERMINATE'\n            tasks[1].seq == 2\n            output\n            def failedWorkflowId = output['conductor.failure_workflow'] as String\n            with(workflowExecutionService.getExecutionStatus(failedWorkflowId, true)) {\n                status == Workflow.WorkflowStatus.COMPLETED\n                input['workflowId'] == workflowInstanceId\n                tasks.size() == 1\n                tasks[0].taskType == 'LAMBDA'\n            }\n        }\n    }\n\n    def \"Test workflow with a terminate task when the workflow has a subworkflow\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['a'] = 1\n\n        when: \"Start the workflow which has the terminate task\"\n        def workflowInstanceId = startWorkflow(PARENT_WORKFLOW_WITH_TERMINATE_TASK, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the workflow has started and the tasks are as expected\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].referenceTaskName == 'lambdaTask1'\n            tasks[1].seq == 2\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'LAMBDA'\n            tasks[2].referenceTaskName == 'lambdaTask2'\n            tasks[2].seq == 3\n            tasks[3].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'JOIN'\n            tasks[3].seq == 4\n            tasks[4].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'SUB_WORKFLOW'\n            tasks[4].seq == 5\n            tasks[5].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'WAIT'\n            tasks[5].seq == 6\n        }\n\n        when: \"subworkflow is retrieved\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowTaskId = workflow.getTaskByRefName(\"test_terminate_subworkflow\").getTaskId()\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subWorkflowTaskId)\n        workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowId = workflow.getTaskByRefName(\"test_terminate_subworkflow\").subWorkflowId\n\n        then: \"verify that the sub workflow is RUNNING, and the task within is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_3'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Complete the WAIT task that should cause the TERMINATE task to execute\"\n        def waitTask = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[5]\n        waitTask.status = Task.Status.COMPLETED\n        workflowExecutor.updateTask(new TaskResult(waitTask))\n\n        then: \"Verify that the workflow has completed and the SUB_WORKFLOW is not still IN_PROGRESS (should be SKIPPED)\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 7\n            reasonForIncompletion.contains('Workflow is COMPLETED by TERMINATE task')\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'FORK'\n            tasks[0].seq == 1\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'LAMBDA'\n            tasks[1].referenceTaskName == 'lambdaTask1'\n            tasks[1].seq == 2\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'LAMBDA'\n            tasks[2].referenceTaskName == 'lambdaTask2'\n            tasks[2].seq == 3\n            tasks[3].status == Task.Status.CANCELED\n            tasks[3].taskType == 'JOIN'\n            tasks[3].seq == 4\n            tasks[4].status == Task.Status.CANCELED\n            tasks[4].taskType == 'SUB_WORKFLOW'\n            tasks[4].seq == 5\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'WAIT'\n            tasks[5].seq == 6\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'TERMINATE'\n            tasks[6].seq == 7\n        }\n\n        and: \"ensure that the subworkflow is terminated\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            reasonForIncompletion.contains('Parent workflow has been terminated with reason: Workflow is COMPLETED by' +\n                    ' TERMINATE task')\n            tasks[0].taskType == 'integration_task_3'\n            tasks[0].status == Task.Status.CANCELED\n        }\n    }\n\n    def \"Test workflow with a terminate task within a decision branch\"() {\n        given: \"workflow input\"\n        Map workflowInput = new HashMap<String, Object>()\n        workflowInput['param1'] = 'p1'\n        workflowInput['param2'] = 'p2'\n        workflowInput['case'] = 'two'\n\n        when: \"The workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_DECISION_AND_TERMINATE, 1, '',\n                workflowInput, null)\n\n        then: \"verify that the workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].seq == 1\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op':'task1 completed'])\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has FAILED due to terminate task\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 3\n            output.size() == 1\n            output as String == \"[output:task1 completed]\"\n            reasonForIncompletion.contains('Workflow is FAILED by TERMINATE task')\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['op'] == 'task1 completed'\n            tasks[0].seq == 1\n            tasks[1].taskType == 'DECISION'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].seq == 2\n            tasks[2].taskType == 'TERMINATE'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].seq == 3\n        }\n    }\n\n    def \"Test workflow with lambda task\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['a'] = 1\n\n        when: \"Start the workflow which has the terminate task\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_LAMBDA_TASK, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the task is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'LAMBDA'\n            tasks[0].outputData as String == \"[result:[testvalue:true]]\"\n            tasks[0].seq == 1\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/NestedForkJoinSubWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_FORK\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_JOIN\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\n\nclass NestedForkJoinSubWorkflowSpec extends AbstractSpecification {\n\n    @Shared\n    def FORK_JOIN_NESTED_SUB_WF = 'nested_fork_join_swf'\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    Join joinTask\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    String parentWorkflowId, subworkflowId\n\n    TaskDef persistedTask2Definition\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('nested_fork_join_swf.json',\n                'simple_workflow_1_integration_test.json'\n        )\n\n        //region Test setup: 3 workflows reach FAILED state. Task 'integration_task_2' in leaf workflow is FAILED.\n        setup: \"Modify task definition to 0 retries\"\n        persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(FORK_JOIN_NESTED_SUB_WF, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'retry_on_root_in_3level_wf'\n        def input = [\n                'param1'   : 'p1 value',\n                'param2'   : 'p2 value',\n                'subwf'    : SIMPLE_WORKFLOW]\n\n        when: \"the workflow is started\"\n        parentWorkflowId = startWorkflow(FORK_JOIN_NESTED_SUB_WF, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds.get(0))\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        def parentWorkflowInstance = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        with(parentWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        subworkflowId = parentWorkflowInstance.tasks[2].subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"poll and fail the integration_task_2 task in the sub workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'task2 failed')\n\n        then: \"the sub workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the parent workflow is swept\"\n        sweep(parentWorkflowId)\n\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.FAILED\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.CANCELED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.CANCELED\n        }\n        //endregion\n    }\n\n    def cleanup() {\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    /**\n     * On a nested fork join workflow where all workflows reach FAILED state because of a FAILED task\n     * in the sub workflow.\n     *\n     * A restart is executed on the sub workflow.\n     *\n     * Expectation: The sub workflow spawns a execution with the same id.\n     * When the sub workflow completes successfully, the parent workflow also completes successfully.\n     */\n    def \"test restart on the sub workflow in a nested fork join workflow\"() {\n        when:\n        workflowExecutor.restart(subworkflowId, false)\n\n        then: \"verify that the subworkflow is RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"verify that the parent workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.CANCELED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.CANCELED\n        }\n\n        when: \"the parent workflow is swept\"\n        def workflow = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        def outerJoinId = workflow.getTaskByRefName(\"outer_join\").taskId\n        def innerJoinId = workflow.getTaskByRefName(\"inner_join\").taskId\n        sweep(parentWorkflowId)\n\n        then: \"verify that the flag is reset and JOIN is updated\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete both tasks in the sub workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the subworkflow completed\"\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        and: \"verify that the parent workflow's sub workflow task is completed\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.COMPLETED\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the parent workflow is swept\"\n        sweep(parentWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, innerJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, outerJoinId)\n\n        then: \"verify that the parent workflow reaches COMPLETED with all tasks completed\"\n        assertParentWorkflowIsComplete()\n    }\n\n    /**\n     * On a nested fork join workflow where all workflows reach FAILED state because of a FAILED task\n     * in the sub workflow.\n     *\n     * A restart is executed on the parent workflow.\n     *\n     * Expectation: The parent workflow spawns a execution with the same id, which in turn creates a new instance of the sub workflow.\n     * When the sub workflow completes successfully, the parent workflow also completes successfully.\n     */\n    def \"test restart on the parent workflow in a nested fork join workflow\"() {\n        when:\n        workflowExecutor.restart(parentWorkflowId, false)\n\n        then: \"verify that the parent workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds.get(0))\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n        def workflow = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        def outerJoinId = workflow.getTaskByRefName(\"outer_join\").taskId\n        def innerJoinId = workflow.getTaskByRefName(\"inner_join\").taskId\n\n        then: \"verify that SUB_WORKFLOW task in in progress\"\n        def parentWorkflowInstance = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that a new instance of the sub workflow is created\"\n        def newSubWorkflowId = parentWorkflowInstance.tasks[2].subWorkflowId\n        newSubWorkflowId != subworkflowId\n        with(workflowExecutionService.getExecutionStatus(newSubWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete both tasks in the sub workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the subworkflow completed\"\n        with(workflowExecutionService.getExecutionStatus(newSubWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        and: \"verify that the parent workflow's sub workflow task is completed\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.COMPLETED\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the parent workflow is swept\"\n        sweep(parentWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, innerJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, outerJoinId)\n\n        then: \"verify that the parent workflow reaches COMPLETED with all tasks completed\"\n        assertParentWorkflowIsComplete()\n    }\n\n    /**\n     * On a nested fork join workflow where all workflows reach FAILED state because of a FAILED task\n     * in the sub workflow.\n     *\n     * A retry is executed on the parent workflow.\n     *\n     * Expectation: The parent workflow spawns a execution with the same id, which in turn creates a new instance of the sub workflow.\n     * When the sub workflow completes successfully, the parent workflow also completes successfully.\n     */\n    def \"test retry on the parent workflow in a nested fork join workflow\"() {\n        when:\n        workflowExecutor.retry(parentWorkflowId, false)\n\n        then: \"verify that the parent workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 8\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].retried\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[7].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[7].status == Task.Status.SCHEDULED\n            tasks[7].retriedTaskId == tasks[2].taskId\n        }\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds.get(0))\n\n        then: \"verify that SUB_WORKFLOW task in in progress\"\n        def parentWorkflowInstance = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 8\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].retried\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[7].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[7].status == Task.Status.IN_PROGRESS\n            tasks[7].retriedTaskId == tasks[2].taskId\n        }\n\n        and: \"verify that a new instance of the sub workflow is created\"\n        def newSubWorkflowId = parentWorkflowInstance.tasks[7].subWorkflowId\n        newSubWorkflowId != subworkflowId\n        with(workflowExecutionService.getExecutionStatus(newSubWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete both tasks in the sub workflow\"\n        def workflow = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        def outerJoinId = workflow.getTaskByRefName(\"outer_join\").taskId\n        def innerJoinId = workflow.getTaskByRefName(\"inner_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the subworkflow completed\"\n        with(workflowExecutionService.getExecutionStatus(newSubWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        and: \"verify that the parent workflow's sub workflow task is completed\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 8\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].retried\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n            tasks[7].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[7].retriedTaskId == tasks[2].taskId\n        }\n\n        when: \"the parent workflow is swept\"\n        sweep(parentWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, innerJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, outerJoinId)\n\n        then: \"verify that the parent workflow reaches COMPLETED with all tasks completed\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 8\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.FAILED\n            tasks[2].retried\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.COMPLETED\n            tasks[7].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[7].status == Task.Status.COMPLETED\n            tasks[7].retriedTaskId == tasks[2].taskId\n        }\n    }\n\n    /**\n     * On a nested fork join workflow where all workflows reach FAILED state because of a FAILED task\n     * in the sub workflow.\n     *\n     * A retry with resume flag is executed on the parent workflow.\n     *\n     * Expectation: The parent workflow spawns a execution with the same id, which in turn creates a new instance of the sub workflow.\n     * When the sub workflow completes successfully, the parent workflow also completes successfully.\n     */\n    def \"test retry with resume on the parent workflow in a nested fork join workflow\"() {\n        when:\n        workflowExecutor.retry(parentWorkflowId, true)\n\n        then: \"verify that the sub workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        and: \"verify that the parent's SUB_WORKFLOW task is updated\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.CANCELED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.CANCELED\n        }\n\n        when: \"the parent is swept\"\n        sweep(parentWorkflowId)\n\n        then: \"verify that parent's JOIN task in in progress\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the failed task in the sub workflow\"\n        def workflow = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        def outerJoinId = workflow.getTaskByRefName(\"outer_join\").taskId\n        def innerJoinId = workflow.getTaskByRefName(\"inner_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the subworkflow completed\"\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        and: \"verify that the parent workflow's sub workflow task is completed\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the parent workflow is swept\"\n        sweep(parentWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, innerJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, outerJoinId)\n\n        then: \"verify the parent workflow reaches COMPLETED state\"\n        assertParentWorkflowIsComplete()\n    }\n\n    /**\n     * On a nested fork join workflow where all workflows reach FAILED state because of a FAILED task\n     * in the sub workflow.\n     *\n     * A retry is executed on the parent workflow.\n     *\n     * Expectation: The parent workflow spawns a execution with the same id, which in turn creates a new instance of the sub workflow.\n     * When the sub workflow completes successfully, the parent workflow also completes successfully.\n     */\n    def \"test retry on the sub workflow in a nested fork join workflow\"() {\n        when:\n        workflowExecutor.retry(subworkflowId, false)\n\n        then: \"verify that the sub workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        and: \"verify that the parent's SUB_WORKFLOW task is updated\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.CANCELED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.CANCELED\n        }\n\n        when: \"the parent is swept\"\n        sweep(parentWorkflowId)\n\n        then: \"verify that parent's JOIN task in in progress\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.IN_PROGRESS\n            !tasks[2].subworkflowChanged\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"poll and complete the failed task in the sub workflow\"\n        def workflow = workflowExecutionService.getExecutionStatus(parentWorkflowId, true)\n        def outerJoinId = workflow.getTaskByRefName(\"outer_join\").taskId\n        def innerJoinId = workflow.getTaskByRefName(\"inner_join\").taskId\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the subworkflow completed\"\n        with(workflowExecutionService.getExecutionStatus(subworkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        and: \"verify that the parent workflow's sub workflow task is completed\"\n        with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the parent workflow is swept\"\n        sweep(parentWorkflowId)\n\n        and: \"JOIN tasks are executed\"\n        asyncSystemTaskExecutor.execute(joinTask, innerJoinId)\n        asyncSystemTaskExecutor.execute(joinTask, outerJoinId)\n\n        then: \"verify the parent workflow reaches COMPLETED state\"\n        assertParentWorkflowIsComplete()\n    }\n\n    private void assertParentWorkflowIsComplete() {\n        assert with(workflowExecutionService.getExecutionStatus(parentWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 7\n            tasks[0].taskType == TASK_TYPE_FORK\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_FORK\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == TASK_TYPE_JOIN\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == TASK_TYPE_JOIN\n            tasks[6].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SetVariableTaskSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nclass SetVariableTaskSpec extends AbstractSpecification {\n\n    @Shared\n    def SET_VARIABLE_WF = 'test_set_variable_wf'\n\n    def setup() {\n        workflowTestUtil.registerWorkflows(\n                'simple_set_variable_workflow_integration_test.json'\n        )\n    }\n\n    def \"Test workflow with set variable task\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['var'] = \"var_test_value\"\n\n        when: \"Start the workflow which has the set variable task\"\n        def workflowInstanceId = startWorkflow(SET_VARIABLE_WF, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the task is completed and variables were set\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].taskType == 'SET_VARIABLE'\n            tasks[0].status == Task.Status.COMPLETED\n            variables as String == '[var:var_test_value]'\n            output as String == '[variables:[var:var_test_value]]'\n        }\n    }\n\n    def \"Test workflow with set variable task passing variables payload size threshold\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        long maxThreshold = 2\n        workflowInput['var'] = String.join(\"\",\n                Collections.nCopies(1 + ((int) (maxThreshold * 1024 / 8)), \"01234567\"))\n\n        when: \"Start the workflow which has the set variable task\"\n        def workflowInstanceId = startWorkflow(SET_VARIABLE_WF, 1,\n                '', workflowInput, null)\n        def EXTRA_HASHMAP_SIZE = 17\n        def expectedErrorMessage =\n                String.format(\n                        \"The variables payload size: %d of workflow: %s is greater than the permissible limit: %d bytes\",\n                        EXTRA_HASHMAP_SIZE + maxThreshold * 1024 + 1, workflowInstanceId, maxThreshold)\n\n        then: \"verify that the task is completed and variables were set\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].taskType == 'SET_VARIABLE'\n            tasks[0].status == Task.Status.FAILED_WITH_TERMINAL_ERROR\n            tasks[0].reasonForIncompletion == expectedErrorMessage\n            variables as String == '[:]'\n            output as String == '[variables:[:]]'\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SimpleWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.apache.commons.lang3.StringUtils\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.exception.ConflictException\nimport com.netflix.conductor.core.exception.NotFoundException\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass SimpleWorkflowSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Shared\n    def LINEAR_WORKFLOW_T1_T2 = 'integration_test_wf'\n\n    @Shared\n    def INTEGRATION_TEST_WF_NON_RESTARTABLE = \"integration_test_wf_non_restartable\"\n\n\n    def setup() {\n        //Register LINEAR_WORKFLOW_T1_T2,  RTOWF, WORKFLOW_WITH_OPTIONAL_TASK\n        workflowTestUtil.registerWorkflows('simple_workflow_1_integration_test.json',\n                'simple_workflow_with_resp_time_out_integration_test.json')\n    }\n\n    def \"Test simple workflow completion\"() {\n\n        given: \"An existing simple workflow definition\"\n        metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'unit_test_1'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        when: \"Start a workflow based on the registered simple workflow\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, input,\n                null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete the 'integration_task_1' \"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete 'integration_task_2'\"\n        def pollAndCompleteTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the 'integration_task_2' has been polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try1, ['tp1': inputParam1, 'tp2': 'task1.done'])\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n            output.containsKey('o3')\n        }\n    }\n\n    def \"Test simple workflow with null inputs\"() {\n\n        when: \"An existing simple workflow definition\"\n        def workflowDef = metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        then:\n        workflowDef.getTasks().get(0).getInputParameters().containsKey('someNullKey')\n\n        when: \"Start a workflow based on the registered simple workflow with one input param null\"\n        String correlationId = \"unit_test_1\"\n        def input = new HashMap()\n        input.put(\"param1\", \"p1 value\")\n        input.put(\"param2\", null)\n\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, input,\n                null)\n\n        then: \"verify the workflow has started and the input params have propagated\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            input['param2'] == null\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n            !tasks[0].inputData['someNullKey']\n        }\n\n        when: \"'integration_task_1' is polled and completed with output data\"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker',\n                ['someOtherKey': ['a': 1, 'A': null], 'someKey': null])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that the task is completed and the output data has propagated as input data to 'integration_task_2'\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData.containsKey('someKey')\n            !tasks[0].outputData['someKey']\n            def someOtherKey = tasks[0].outputData['someOtherKey'] as Map\n            someOtherKey.containsKey('A')\n            !someOtherKey['A']\n        }\n    }\n\n    def \"Test simple workflow terminal error condition\"() {\n        setup: \"Modify the task definition and the workflow output definition\"\n        def persistedTask1Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_1').get()\n        def modifiedTask1Definition = new TaskDef(persistedTask1Definition.name, persistedTask1Definition.description,\n                persistedTask1Definition.ownerEmail, 1, persistedTask1Definition.timeoutSeconds,\n                persistedTask1Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask1Definition)\n        def workflowDef = metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        def outputParameters = workflowDef.outputParameters\n        outputParameters['validationErrors'] = '${t1.output.ErrorMessage}'\n        metadataService.updateWorkflowDef(workflowDef)\n\n        when: \"A simple workflow which is started\"\n        String correlationId = \"unit_test_1\"\n        def input = new HashMap()\n        input.put(\"param1\", \"p1 value\")\n        input.put(\"param2\", \"p2 value\")\n\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, input,\n                null)\n\n        then: \"verify that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n        }\n\n        when: \"Rewind the running workflow that was just started\"\n        workflowExecutor.restart(workflowInstanceId, false)\n\n        then: \"Ensure that a exception is thrown when a running workflow is being rewind\"\n        def exceptionThrown = thrown(ConflictException.class)\n        exceptionThrown != null\n\n        when: \"'integration_task_1' is polled and failed with terminal error\"\n        def polledIntegrationTask1 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n        TaskResult taskResult = new TaskResult(polledIntegrationTask1)\n        taskResult.reasonForIncompletion = 'NON TRANSIENT ERROR OCCURRED: An integration point required to complete the task is down'\n        taskResult.status = TaskResult.Status.FAILED_WITH_TERMINAL_ERROR\n        taskResult.addOutputData('TERMINAL_ERROR', 'Integration endpoint down: FOOBAR')\n        taskResult.addOutputData('ErrorMessage', 'There was a terminal error')\n\n        workflowExecutionService.updateTask(taskResult)\n        sweep(workflowInstanceId)\n\n        then: \"The first polled task is integration_task_1 and the workflowInstanceId of the task is same as running workflowInstanceId\"\n        polledIntegrationTask1\n        polledIntegrationTask1.taskType == 'integration_task_1'\n        polledIntegrationTask1.workflowInstanceId == workflowInstanceId\n\n        and: \"verify that the workflow is in a failed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            def t1 = getTaskByRefName('t1')\n            reasonForIncompletion == \"Task ${t1.taskId} failed with status: FAILED and reason: \" +\n                    \"'NON TRANSIENT ERROR OCCURRED: An integration point required to complete the task is down'\"\n            output['o1'] == 'p1 value'\n            output['validationErrors'] == 'There was a terminal error'\n            t1.retryCount == 0\n            failedReferenceTaskNames == ['t1'] as HashSet\n            failedTaskNames == ['integration_task_1'] as HashSet\n        }\n\n        cleanup:\n        metadataService.updateTaskDef(modifiedTask1Definition)\n        outputParameters.remove('validationErrors')\n        metadataService.updateWorkflowDef(workflowDef)\n    }\n\n\n    def \"Test Simple Workflow with response timeout \"() {\n        given: 'Workflow input and correlationId'\n        def correlationId = 'unit_test_1'\n        def workflowInput = new HashMap()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        when: \"Start a workflow that has a response time out\"\n        def workflowInstanceId = startWorkflow('RTOWF', 1, correlationId, workflowInput,\n                null)\n\n\n        then: \"Workflow is in running state and the task 'task_rt' is ready to be polled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'task_rt'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n        queueDAO.getSize('task_rt') == 1\n\n        when: \"Poll for a 'task_rt' task and then ack the task\"\n        def polledTaskRtTry1 = workflowExecutionService.poll('task_rt', 'task1.integration.worker.testTimeout')\n\n        then: \"Verify that the 'task_rt' was polled\"\n        polledTaskRtTry1\n        polledTaskRtTry1.taskType == 'task_rt'\n        polledTaskRtTry1.workflowInstanceId == workflowInstanceId\n        polledTaskRtTry1.status == Task.Status.IN_PROGRESS\n\n        when: \"An additional poll is done wto retrieved another 'task_rt'\"\n        def noTaskAvailable = workflowExecutionService.poll('task_rt', 'task1.integration.worker.testTimeout')\n\n        then: \"Ensure that there is no additional 'task_rt' available to poll\"\n        !noTaskAvailable\n\n        when: \"The processing of the polled task takes more time than the response time out\"\n        Thread.sleep(10000)\n        workflowExecutor.decide(workflowInstanceId)\n\n        then: \"Expect a new task to be added to the queue in place of the timed out task\"\n        queueDAO.getSize('task_rt') == 1\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].status == Task.Status.TIMED_OUT\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The task_rt is polled again and the task is set to be called back after 2 seconds\"\n        def polledTaskRtTry2 = workflowExecutionService.poll('task_rt', 'task1.integration.worker.testTimeout')\n        polledTaskRtTry2.callbackAfterSeconds = 2\n        polledTaskRtTry2.status = Task.Status.IN_PROGRESS\n        workflowExecutionService.updateTask(new TaskResult(polledTaskRtTry2))\n\n        then: \"verify that the polled task is not null\"\n        polledTaskRtTry2\n\n        and: \"verify the state of the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"induce the time for the call back for the task to expire and run the un ack process\"\n        Thread.sleep(2010)\n        queueDAO.processUnacks(polledTaskRtTry2.taskDefName)\n\n        and: \"run the decide process on the workflow\"\n        workflowExecutor.decide(workflowInstanceId)\n\n        and: \"poll for the task and then complete the task 'task_rt' \"\n        def pollAndCompleteTaskTry3 = workflowTestUtil.pollAndCompleteTask('task_rt', 'task1.integration.worker.testTimeout', ['op': 'task1.done'])\n\n        then: 'Verify that the task was polled '\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTaskTry3)\n\n        when: \"The next task of the workflow is polled and then completed\"\n        def polledIntegrationTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker.testTimeout')\n\n        then: \"Verify that 'integration_task_2' is polled and acked\"\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask2Try1)\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n        }\n    }\n\n    def \"Test if the workflow definitions with and without schema version can be registered\"() {\n        given: \"A workflow definition with no schema version\"\n        def workflowDef1 = new WorkflowDef()\n        workflowDef1.name = 'Test_schema_version1'\n        workflowDef1.version = 1\n        workflowDef1.ownerEmail = \"test@harness.com\"\n\n        and: \"A new workflow task is created\"\n        def workflowTask = new WorkflowTask()\n        workflowTask.name = 'integration_task_1'\n        workflowTask.taskReferenceName = 't1'\n        workflowDef1.tasks.add(workflowTask)\n\n        and: \"The workflow definition with no schema version is saved\"\n        metadataService.updateWorkflowDef(workflowDef1)\n\n        and: \"A workflow definition with a schema version is created\"\n        def workflowDef2 = new WorkflowDef()\n        workflowDef2.name = 'Test_schema_version2'\n        workflowDef2.version = 1\n        workflowDef2.schemaVersion = 2\n        workflowDef2.ownerEmail = \"test@harness.com\"\n        workflowDef2.tasks.add(workflowTask)\n\n        and: \"The workflow definition with schema version is persisted\"\n        metadataService.updateWorkflowDef(workflowDef2)\n\n        when: \"The persisted workflow definitions are retrieved by their name\"\n        def foundWorkflowDef1 = metadataService.getWorkflowDef(workflowDef1.getName(), 1)\n        def foundWorkflowDef2 = metadataService.getWorkflowDef(workflowDef2.getName(), 1)\n\n        then: \"Ensure that the schema version is by default 2\"\n        foundWorkflowDef1\n        foundWorkflowDef1.schemaVersion == 2\n        foundWorkflowDef2\n        foundWorkflowDef2.schemaVersion == 2\n    }\n\n    def \"Test Simple workflow restart without using the latest definition\"() {\n        setup: \"Register a task definition with no retries\"\n        def persistedTask1Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_1').get()\n        def modifiedTaskDefinition = new TaskDef(persistedTask1Definition.name, persistedTask1Definition.description,\n                persistedTask1Definition.ownerEmail, 0, persistedTask1Definition.timeoutSeconds,\n                persistedTask1Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        when: \"Get the workflow definition associated with the simple workflow\"\n        WorkflowDef workflowDefinition = metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        then: \"Ensure that there is a workflow definition\"\n        workflowDefinition\n        workflowDefinition.failureWorkflow\n        StringUtils.isNotBlank(workflowDefinition.failureWorkflow)\n\n        when: \"Start a simple workflow with non null params\"\n        def correlationId = 'integration_test_1' + UUID.randomUUID().toString()\n        def workflowInput = new HashMap()\n        String inputParam1 = 'p1 value'\n        workflowInput['param1'] = inputParam1\n        workflowInput['param2'] = 'p2 value'\n\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"A workflow instance has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n        }\n\n        when: \"poll the task that is queued and fail the task\"\n        def polledIntegrationTask1Try1 = workflowTestUtil.pollAndFailTask('integration_task_1', 'task1.integration.worker', 'failed..')\n\n        then: \"The workflow ends up in a failed state\"\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask1Try1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"Rewind the workflow which is in the failed state without the latest definition\"\n        workflowExecutor.restart(workflowInstanceId, false)\n\n        then: \"verify that the rewound workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n        }\n\n        when: \"Poll for the 'integration_task_1' \"\n        def polledIntegrationTask1Try2 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the task is polled and the workflow is in a running state\"\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask1Try2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when:\n        def polledIntegrationTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then:\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask2Try1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n        }\n\n        cleanup:\n        metadataService.updateTaskDef(persistedTask1Definition)\n    }\n\n    def \"Test Simple workflow restart with the latest definition\"() {\n\n        setup: \"Register a task definition with no retries\"\n        def persistedTask1Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_1').get()\n        def modifiedTaskDefinition = new TaskDef(persistedTask1Definition.name, persistedTask1Definition.description,\n                persistedTask1Definition.ownerEmail, 0, persistedTask1Definition.timeoutSeconds,\n                persistedTask1Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        when: \"Get the workflow definition associated with the simple workflow\"\n        WorkflowDef workflowDefinition = metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n\n        then: \"Ensure that there is a workflow definition\"\n        workflowDefinition\n        workflowDefinition.failureWorkflow\n        StringUtils.isNotBlank(workflowDefinition.failureWorkflow)\n\n        when: \"Start a simple workflow with non null params\"\n        def correlationId = 'integration_test_1' + UUID.randomUUID().toString()\n        def workflowInput = new HashMap()\n        String inputParam1 = 'p1 value'\n        workflowInput['param1'] = inputParam1\n        workflowInput['param2'] = 'p2 value'\n\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"A workflow instance has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n        }\n\n        when: \"poll the task that is queued and fail the task\"\n        def polledIntegrationTask1Try1 = workflowTestUtil.pollAndFailTask('integration_task_1', 'task1.integration.worker', 'failed..')\n\n        then: \"the workflow ends up in a failed state\"\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask1Try1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"A new version of the workflow definition is registered\"\n        WorkflowTask workflowTask = new WorkflowTask()\n        workflowTask.name = 'integration_task_20'\n        workflowTask.taskReferenceName = 'task_added'\n        workflowTask.workflowTaskType = TaskType.SIMPLE\n\n        workflowDefinition.tasks.add(workflowTask)\n        workflowDefinition.version = 2\n        metadataService.updateWorkflowDef(workflowDefinition)\n\n        and: \"rewind/restart the workflow with the latest workflow definition\"\n        workflowExecutor.restart(workflowInstanceId, true)\n\n        then: \"verify that the rewound workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n        }\n\n        when: \"Poll and complete the 'integration_task_1' \"\n        def polledIntegrationTask1Try2 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the task is polled and the workflow is in a running state\"\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask1Try2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"Poll and complete the 'integration_task_2' \"\n        def polledIntegrationTask2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then: \"verify that the task is polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n        }\n\n        when: \"Poll and complete the 'integration_task_20' \"\n        def polledIntegrationTask20Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_20', 'task1.integration.worker')\n\n        then: \"verify that the task is polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledIntegrationTask20Try1)\n        def polledIntegrationTask20 = polledIntegrationTask20Try1[0] as Task\n        polledIntegrationTask20.workflowInstanceId == workflowInstanceId\n        polledIntegrationTask20.referenceTaskName == 'task_added'\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n        }\n\n        cleanup:\n        metadataService.updateTaskDef(persistedTask1Definition)\n        metadataService.unregisterWorkflowDef(workflowDefinition.getName(), 2)\n    }\n\n    def \"Test simple workflow with task retries\"() {\n        setup: \"Change the task definition to ensure that it has retries and delay between retries\"\n        def integrationTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTaskDefinition = new TaskDef(integrationTask2Definition.name, integrationTask2Definition.description,\n                integrationTask2Definition.ownerEmail, 3, integrationTask2Definition.timeoutSeconds,\n                integrationTask2Definition.responseTimeoutSeconds)\n        modifiedTaskDefinition.retryDelaySeconds = 2\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        when: \"A new simple workflow is started\"\n        def correlationId = 'integration_test_1'\n        def workflowInput = new HashMap()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"verify that the workflow has started\"\n        workflowInstanceId\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        workflow.status == Workflow.WorkflowStatus.RUNNING\n\n        when: \"Poll for the first task and complete the task\"\n        def polledIntegrationTask1 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n        polledIntegrationTask1.status = Task.Status.COMPLETED\n        def polledIntegrationTask1Output = \"task1.output -> \" + polledIntegrationTask1.inputData['p1'] + \".\" + polledIntegrationTask1.inputData['p2']\n        polledIntegrationTask1.outputData['op'] = polledIntegrationTask1Output\n        workflowExecutionService.updateTask(new TaskResult(polledIntegrationTask1))\n\n        then: \"verify that the 'integration_task_1' is polled and completed\"\n        with(polledIntegrationTask1) {\n            inputData.containsKey('p1')\n            inputData.containsKey('p2')\n            inputData['p1'] == 'p1 value'\n            inputData['p2'] == 'p2 value'\n        }\n\n        //Need to figure out how to use expect and where here\n        when: \" 'integration_task_2'  is polled and marked as failed for the first time\"\n        Tuple polledAndFailedTaskTry1 = workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failure...0', null, 2)\n\n        then: \"verify that the task was polled and the input params of the tasks are as expected\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTaskTry1, ['tp2': polledIntegrationTask1Output, 'tp1': 'p1 value'])\n\n        when: \" 'integration_task_2'  is polled and marked as failed for the second time\"\n        Tuple polledAndFailedTaskTry2 = workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failure...0', null, 2)\n\n        then: \"verify that the task was polled and the input params of the tasks are as expected\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTaskTry2, ['tp2': polledIntegrationTask1Output, 'tp1': 'p1 value'])\n\n        when: \"'integration_task_2'  is polled and marked as completed for the third time\"\n        def polledAndCompletedTry3 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the task was polled and the input params of the tasks are as expected\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTry3, ['tp2': polledIntegrationTask1Output, 'tp1': 'p1 value'])\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.FAILED\n            tasks[3].taskType == 'integration_task_2'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[1].taskId == tasks[2].retriedTaskId\n            tasks[2].taskId == tasks[3].retriedTaskId\n            failedReferenceTaskNames == ['t2'] as HashSet\n            failedTaskNames == ['integration_task_2'] as HashSet\n        }\n\n        cleanup:\n        metadataService.updateTaskDef(integrationTask2Definition)\n    }\n\n    def \"Test simple workflow with retry at workflow level\"() {\n        setup: \"Change the task definition to ensure that it has retries and no delay between retries\"\n        def integrationTask1Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_1').get()\n        def modifiedTaskDefinition = new TaskDef(integrationTask1Definition.name, integrationTask1Definition.description,\n                integrationTask1Definition.ownerEmail, 1, integrationTask1Definition.timeoutSeconds,\n                integrationTask1Definition.responseTimeoutSeconds)\n        modifiedTaskDefinition.retryDelaySeconds = 0\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        when: \"Start a simple workflow with non null params\"\n        def correlationId = 'retry_test' + UUID.randomUUID().toString()\n        def workflowInput = new HashMap()\n        String inputParam1 = 'p1 value'\n        workflowInput['param1'] = inputParam1\n        workflowInput['param2'] = 'p2 value'\n\n        and: \"start a simple workflow with input params\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"verify that the workflow has started and the next task is scheduled\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].getInputData().get(\"p3\") == tasks[0].getTaskId()\n        }\n        with(metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)) {\n            failureWorkflow\n            StringUtils.isNotBlank(failureWorkflow)\n        }\n\n        when: \"The first task 'integration_task_1' is polled and failed\"\n        Tuple polledAndFailedTask1Try1 = workflowTestUtil.pollAndFailTask('integration_task_1', 'task1.integration.worker', 'failure...0')\n\n        then: \"verify that the task was polled and acknowledged and the workflow is still in a running state\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTask1Try1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].status == Task.Status.FAILED\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].getInputData().get(\"p3\") == tasks[1].getTaskId()\n        }\n\n        when: \"The first task 'integration_task_1' is polled and failed for the second time\"\n        Tuple polledAndFailedTask1Try2 = workflowTestUtil.pollAndFailTask('integration_task_1', 'task1.integration.worker', 'failure...0')\n\n        then: \"verify that the task was polled and acknowledged and the workflow is still in a running state\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTask1Try2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].status == Task.Status.FAILED\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"The workflow is retried\"\n        workflowExecutor.retry(workflowInstanceId, false)\n\n        then:\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].status == Task.Status.FAILED\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].getInputData().get(\"p3\") == tasks[2].getTaskId()\n        }\n\n        when: \"The 'integration_task_1' task is polled and is completed\"\n        def polledAndCompletedTry3 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task2.integration.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTry3)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The 'integration_task_2' task is polled and is completed\"\n        def polledAndCompletedTaskTry1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTaskTry1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].status == Task.Status.COMPLETED\n            failedReferenceTaskNames == ['t1'] as HashSet\n            failedTaskNames == ['integration_task_1'] as HashSet\n        }\n\n        cleanup:\n        metadataService.updateTaskDef(integrationTask1Definition)\n    }\n\n    def \"Test Long running simple workflow\"() {\n        given: \"A new simple workflow is started\"\n        def correlationId = 'integration_test_1'\n        def workflowInput = new HashMap()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        when: \"start a new workflow with the input\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"verify that the workflow is in running state and the task queue has an entry for the first task of the workflow\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n        }\n        workflowExecutionService.getTaskQueueSizes(['integration_task_1']).get('integration_task_1') == 1\n\n        when: \"the first task 'integration_task_1' is polled and then sent back with a callBack seconds\"\n        def pollTaskTry1 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n        pollTaskTry1.outputData['op'] = 'task1.in.progress'\n        pollTaskTry1.callbackAfterSeconds = 5\n        pollTaskTry1.status = Task.Status.IN_PROGRESS\n        workflowExecutionService.updateTask(new TaskResult(pollTaskTry1))\n\n        then: \"verify that the task is polled and acknowledged\"\n        pollTaskTry1\n\n        and: \"the input data of the data is as expected\"\n        pollTaskTry1.inputData.containsKey('p1')\n        pollTaskTry1.inputData['p1'] == 'p1 value'\n        pollTaskTry1.inputData.containsKey('p2')\n        pollTaskTry1.inputData['p1'] == 'p1 value'\n\n        and: \"the task queue reflects the presence of 'integration_task_1' \"\n        workflowExecutionService.getTaskQueueSizes(['integration_task_1']).get('integration_task_1') == 1\n\n        when: \"the 'integration_task_1' task is polled again\"\n        def pollTaskTry2 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that there was no task polled\"\n        !pollTaskTry2\n\n        when: \"the 'integration_task_1' is polled again after a delay of 5 seconds and completed\"\n        Thread.sleep(5000)\n        def task1Try3Tuple = workflowTestUtil.pollAndCompleteTask('integration_task_1',\n                'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the task is polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(task1Try3Tuple, [:])\n\n        and: \"verify that the workflow is updated with the latest task\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].outputData['op'] == 'task1.done'\n        }\n\n        when: \"the 'integration_task_1' is polled and completed\"\n        def task2Try1Tuple = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the task was polled and completed with the expected inputData for the task that was polled\"\n        verifyPolledAndAcknowledgedTask(task2Try1Tuple, ['tp2': 'task1.done', 'tp1': 'p1 value'])\n\n        and: \"The workflow is in a completed state and reflects the tasks that are completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n        }\n    }\n\n\n    def \"Test simple workflow when the task's call back after seconds are reset\"() {\n\n        given: \"A new simple workflow is started\"\n        def correlationId = 'integration_test_1'\n        def workflowInput = new HashMap()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        when: \"start a new workflow with the input\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"verify that the workflow is in running state and the task queue has an entry for the first task of the workflow\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n        workflowExecutionService.getTaskQueueSizes(['integration_task_1']).get('integration_task_1') == 1\n\n        when: \"the first task 'integration_task_1' is polled and then sent back with a callBack seconds\"\n        def pollTaskTry1 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n        pollTaskTry1.outputData['op'] = 'task1.in.progress'\n        pollTaskTry1.callbackAfterSeconds = 3600\n        pollTaskTry1.status = Task.Status.IN_PROGRESS\n        workflowExecutionService.updateTask(new TaskResult(pollTaskTry1))\n\n        then: \"verify that the task is polled and acknowledged\"\n        pollTaskTry1\n\n        and: \"the input data of the data is as expected\"\n        pollTaskTry1.inputData.containsKey('p1')\n        pollTaskTry1.inputData['p1'] == 'p1 value'\n        pollTaskTry1.inputData.containsKey('p2')\n        pollTaskTry1.inputData['p1'] == 'p1 value'\n\n        and: \"the task queue reflects the presence of 'integration_task_1' \"\n        workflowExecutionService.getTaskQueueSizes(['integration_task_1']).get('integration_task_1') == 1\n\n        when: \"the 'integration_task_1' task is polled again\"\n        def pollTaskTry2 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that there was no task polled\"\n        !pollTaskTry2\n\n        when: \"the 'integration_task_1' task is polled again\"\n        def pollTaskTry3 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that there was no task polled\"\n        !pollTaskTry3\n\n        when: \"The callbackSeconds of the tasks in progress for the workflow are reset\"\n        workflowExecutor.resetCallbacksForWorkflow(workflowInstanceId)\n\n        and: \"the 'integration_task_1' is polled again after all the in progress tasks are reset\"\n        def task1Try4Tuple = workflowTestUtil.pollAndCompleteTask('integration_task_1',\n                'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the task is polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(task1Try4Tuple)\n\n        and: \"verify that the workflow is updated with the latest task\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].outputData['op'] == 'task1.done'\n        }\n\n        when: \"the 'integration_task_1' is polled and completed\"\n        def task2Try1Tuple = workflowTestUtil.pollAndCompleteTask('integration_task_2',\n                'task2.integration.worker')\n\n        then: \"verify that the task was polled and completed with the expected inputData for the task that was polled\"\n        verifyPolledAndAcknowledgedTask(task2Try1Tuple, ['tp2': 'task1.done', 'tp1': 'p1 value'])\n\n        and: \"The workflow is in a completed state and reflects the tasks that are completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n        }\n    }\n\n    def \"Test non restartable simple workflow\"() {\n        setup: \"Change the task definition to ensure that it has no retries and register a non restartable workflow\"\n        def integrationTask1Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_1').get()\n        def modifiedTaskDefinition = new TaskDef(integrationTask1Definition.name, integrationTask1Definition.description,\n                integrationTask1Definition.ownerEmail, 0, integrationTask1Definition.timeoutSeconds,\n                integrationTask1Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        def simpleWorkflowDefinition = metadataService.getWorkflowDef(LINEAR_WORKFLOW_T1_T2, 1)\n        simpleWorkflowDefinition.name = INTEGRATION_TEST_WF_NON_RESTARTABLE\n        simpleWorkflowDefinition.restartable = false\n        metadataService.updateWorkflowDef(simpleWorkflowDefinition)\n\n        when: \"A non restartable workflow is started\"\n        def correlationId = 'integration_test_1'\n        def workflowInput = new HashMap()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        def workflowInstanceId = startWorkflow(INTEGRATION_TEST_WF_NON_RESTARTABLE, 1,\n                correlationId, workflowInput,\n                null)\n\n        and: \"the 'integration_task_1' is polled and failed\"\n        Tuple polledAndFailedTaskTry1 = workflowTestUtil.pollAndFailTask('integration_task_1',\n                'task1.integration.worker', 'failure...0')\n\n        then: \"verify that the task was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTaskTry1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"The failed workflow is rewound\"\n        workflowExecutor.restart(workflowInstanceId, false)\n\n        and: \"The first task 'integration_task_1' is polled and completed\"\n        def task1Try2 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker',\n                ['op': 'task1.done'])\n\n        then: \"Verify that the task is polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(task1Try2)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"The second task 'integration_task_2' is polled and completed\"\n        def task2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"Verify that the task was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(task2Try1, ['tp2': 'task1.done', 'tp1': 'p1 value'])\n\n        and: \"The workflow is in a completed state and reflects the tasks that are completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].taskType == 'integration_task_1'\n            output['o3'] == 'task1.done'\n        }\n\n        when: \"The successfully completed non restartable workflow is rewound\"\n        workflowExecutor.restart(workflowInstanceId, false)\n\n        then: \"Ensure that an exception is thrown\"\n        thrown(NotFoundException.class)\n\n        cleanup: \"clean up the changes made to the task and workflow definition during start up\"\n        metadataService.updateTaskDef(integrationTask1Definition)\n        simpleWorkflowDefinition.name = LINEAR_WORKFLOW_T1_T2\n        simpleWorkflowDefinition.restartable = true\n        metadataService.updateWorkflowDef(simpleWorkflowDefinition)\n    }\n\n    def \"Test simple workflow when update task's result with call back after seconds\"() {\n\n        given: \"A new simple workflow is started\"\n        def correlationId = 'integration_test_1'\n        def workflowInput = new HashMap()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        when: \"start a new workflow with the input\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"verify that the workflow is in running state and the task queue has an entry for the first task of the workflow\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n        workflowExecutionService.getTaskQueueSizes(['integration_task_1']).get('integration_task_1') == 1\n\n        when: \"the first task 'integration_task_1' is polled and then sent back with no callBack seconds\"\n        def pollTaskTry1 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n        pollTaskTry1.outputData['op'] = 'task1.in.progress'\n        pollTaskTry1.status = Task.Status.IN_PROGRESS\n        workflowExecutionService.updateTask(new TaskResult(pollTaskTry1))\n\n        then: \"verify that the task is polled and acknowledged\"\n        pollTaskTry1\n\n        and: \"the input data of the data is as expected\"\n        pollTaskTry1.inputData.containsKey('p1')\n        pollTaskTry1.inputData['p1'] == 'p1 value'\n        pollTaskTry1.inputData.containsKey('p2')\n        pollTaskTry1.inputData['p1'] == 'p1 value'\n\n        and: \"the task gets put back into the queue of 'integration_task_1' immediately for future poll\"\n        workflowExecutionService.getTaskQueueSizes(['integration_task_1']).get('integration_task_1') == 1\n\n        and: \"The task in in SCHEDULED status with workerId reset\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].callbackAfterSeconds == 0\n        }\n\n        when: \"the 'integration_task_1' task is polled again\"\n        def pollTaskTry2 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n        pollTaskTry2.outputData['op'] = 'task1.in.progress'\n        pollTaskTry2.status = Task.Status.IN_PROGRESS\n        pollTaskTry2.callbackAfterSeconds = 3600\n        workflowExecutionService.updateTask(new TaskResult(pollTaskTry2))\n\n        then: \"verify that the task is polled and acknowledged\"\n        pollTaskTry2\n\n        and: \"the task gets put back into the queue of 'integration_task_1' with callbackAfterSeconds delay for future poll\"\n        workflowExecutionService.getTaskQueueSizes(['integration_task_1']).get('integration_task_1') == 1\n\n        and: \"The task in in SCHEDULED status with workerId reset\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].callbackAfterSeconds == pollTaskTry2.callbackAfterSeconds\n        }\n\n        when: \"the 'integration_task_1' task is polled again\"\n        def pollTaskTry3 = workflowExecutionService.poll('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that there was no task polled\"\n        !pollTaskTry3\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/StartWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.StartWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\nimport com.netflix.conductor.test.utils.MockExternalPayloadStorage\n\nimport spock.lang.Shared\nimport spock.lang.Unroll\n\nclass StartWorkflowSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    StartWorkflow startWorkflowTask\n\n    @Autowired\n    MockExternalPayloadStorage mockExternalPayloadStorage\n\n    @Shared\n    def WORKFLOW_THAT_STARTS_ANOTHER_WORKFLOW = 'workflow_that_starts_another_workflow'\n\n    static String workflowInputPath = \"${UUID.randomUUID()}.json\"\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('workflow_that_starts_another_workflow.json',\n                'simple_workflow_1_integration_test.json')\n        mockExternalPayloadStorage.upload(workflowInputPath, StartWorkflowSpec.class.getResourceAsStream(\"/start_workflow_input.json\"), 0)\n    }\n\n    @Unroll\n    def \"start another workflow using #testCase.name\"() {\n        setup: 'create the correlationId for the starter workflow'\n        def correlationId = UUID.randomUUID().toString()\n\n        when: \"starter workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_THAT_STARTS_ANOTHER_WORKFLOW, 1,\n                correlationId, testCase.workflowInput, testCase.workflowInputPath)\n\n        then: \"verify that the starter workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'START_WORKFLOW'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the START_WORKFLOW task is started\"\n        List<String> polledTaskIds = queueDAO.pop(\"START_WORKFLOW\", 1, 200)\n        String startWorkflowTaskId = polledTaskIds.get(0)\n        asyncSystemTaskExecutor.execute(startWorkflowTask, startWorkflowTaskId)\n\n        then: \"verify the START_WORKFLOW task and workflow are COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].taskType == 'START_WORKFLOW'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n\n        when: \"the started workflow is retrieved\"\n        def startWorkflowTask = workflowExecutionService.getTask(startWorkflowTaskId)\n        String startedWorkflowId = startWorkflowTask.outputData['workflowId']\n\n        then: \"verify that the started workflow is RUNNING\"\n        with(workflowExecutionService.getExecutionStatus(startedWorkflowId, false)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            it.correlationId == correlationId\n            // when the \"starter\" workflow is started with input from external payload storage,\n            // it sends a large input to the \"started\" workflow\n            // see start_workflow_input.json\n            if(testCase.workflowInputPath) {\n                externalInputPayloadStoragePath != null\n            } else {\n                input != null\n            }\n        }\n\n        where:\n        testCase << [workflowName(), workflowDef(), workflowRequestWithExternalPayloadStorage()]\n    }\n\n    def \"start_workflow does not conform to StartWorkflowRequest\"() {\n        given: \"start_workflow that does not conform to StartWorkflowRequest\"\n        def startWorkflowParam = ['param1': 'value1', 'param2': 'value2']\n        def workflowInput = ['start_workflow': startWorkflowParam]\n\n        when: \"starter workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_THAT_STARTS_ANOTHER_WORKFLOW, 1,\n                null, workflowInput, null)\n\n        then: \"verify that the starter workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'START_WORKFLOW'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the START_WORKFLOW task is started\"\n        List<String> polledTaskIds = queueDAO.pop(\"START_WORKFLOW\", 1, 200)\n        String startWorkflowTaskId = polledTaskIds.get(0)\n        asyncSystemTaskExecutor.execute(startWorkflowTask, startWorkflowTaskId)\n\n        then: \"verify the START_WORKFLOW task and workflow FAILED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 1\n            tasks[0].taskType == 'START_WORKFLOW'\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].reasonForIncompletion != null\n        }\n    }\n\n    /**\n     * Builds a TestCase for a StartWorkflowRequest with a WorkflowDef that contains two tasks.\n     */\n    static workflowDef() {\n        def task1 = ['name': 'integration_task_1', 'taskReferenceName': 't1', 'type': 'SIMPLE',\n                     'inputParameters': ['tp1': '${workflow.input.param1}', 'tp2': '${workflow.input.param2}', 'tp3': '${CPEWF_TASK_ID}']]\n        def task2 = ['name': 'integration_task_2', 'taskReferenceName': 't2', 'type': 'SIMPLE',\n                     'inputParameters': ['tp1': '${workflow.input.param1}', 'tp2': '${t1.output.op}', 'tp3': '${CPEWF_TASK_ID}']]\n        def workflowDef = ['name': 'dynamic_wf', 'version': 1, 'tasks': [task1, task2], 'ownerEmail': 'abc@abc.com']\n\n        def startWorkflow = ['name': 'dynamic_wf', 'workflowDef': workflowDef]\n\n        new TestCase(name: 'workflow definition', workflowInput: ['startWorkflow': startWorkflow])\n    }\n\n    /**\n     * Builds a TestCase for a StartWorkflowRequest with a workflow name.\n     */\n    static workflowName() {\n        def startWorkflow = ['name': 'integration_test_wf', 'input': ['param1': 'value1', 'param2': 'value2']]\n\n        new TestCase(name: 'name and version', workflowInput: ['startWorkflow': startWorkflow])\n    }\n\n    /**\n     * Builds a TestCase for a StartWorkflowRequest with a workflow name and input in external payload storage.\n     */\n    static workflowRequestWithExternalPayloadStorage() {\n        new TestCase(name: 'name and version with external input', workflowInputPath: workflowInputPath)\n    }\n\n    static class TestCase {\n        String name\n        Map workflowInput\n        String workflowInputPath\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SubWorkflowRerunSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass SubWorkflowRerunSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Shared\n    def WORKFLOW_WITH_SUBWORKFLOW = 'integration_test_wf_with_sub_wf'\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    String rootWorkflowId, midLevelWorkflowId, leafWorkflowId\n\n    TaskDef persistedTask2Definition\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('simple_workflow_1_integration_test.json',\n                'workflow_with_sub_workflow_1_integration_test.json')\n\n        //region Test setup: 3 workflows. Task 'integration_task_2' in leaf workflow is FAILED.\n        setup: \"Modify task definition to 0 retries\"\n        persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(WORKFLOW_WITH_SUBWORKFLOW, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'rerun_on_root_in_3level_wf'\n        def input = [\n                'param1'   : 'p1 value',\n                'param2'   : 'p2 value',\n                'subwf'    : WORKFLOW_WITH_SUBWORKFLOW,\n                'nextSubwf': SIMPLE_WORKFLOW]\n\n        when: \"the workflow is started\"\n        rootWorkflowId = startWorkflow(WORKFLOW_WITH_SUBWORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        def rootWorkflowInstance = workflowExecutionService.getExecutionStatus(rootWorkflowId, true)\n        with(rootWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        midLevelWorkflowId = rootWorkflowInstance.tasks[1].subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def midLevelWorkflowInstance = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)\n\n        then: \"verify that the leaf-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        leafWorkflowId = midLevelWorkflowInstance.tasks[1].subWorkflowId\n        def leafWorkflowInstance = workflowExecutionService.getExecutionStatus(leafWorkflowId, true)\n        with(leafWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and fail the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"the leaf workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the mid level workflow is 'decided'\"\n        sweep(midLevelWorkflowId)\n\n        then: \"the mid level subworkflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the root level workflow is 'decided'\"\n        sweep(rootWorkflowId)\n\n        then: \"the root level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n        }\n        //endregion\n    }\n\n    def cleanup() {\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the root workflow.\n     *\n     * Expectation: The root workflow spawns a NEW mid-level workflow, which in turn spawns a NEW leaf workflow.\n     * When the leaf workflow completes successfully, both the NEW mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the root-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the root workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = rootWorkflowId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"poll and complete the 'integration_task_1' task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op1': 'task1.done'])\n\n        and: \"verify that the root workflow created a new SUB_WORKFLOW task\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newMidLevelWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new mid level workflow is created and is in RUNNING state\"\n        newMidLevelWorkflowId != midLevelWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"poll and execute the sub workflow task\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the two tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"the new leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the new mid level and root workflows are 'decided'\"\n        sweep(newMidLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        then: \"the root workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed with taskId on the root workflow.\n     *\n     * Expectation: The root workflow gets a new execution with the same id and both the mid-level workflow and leaf workflows are also reran.\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the root-level with taskId in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the root workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = rootWorkflowId\n        def reRunTaskId = workflowExecutionService.getExecutionStatus(rootWorkflowId, true).tasks[0].taskId\n        reRunWorkflowRequest.reRunFromTaskId = reRunTaskId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"poll and complete the 'integration_task_1' task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op1': 'task1.done'])\n\n        and: \"verify that the root workflow created a new SUB_WORKFLOW task\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newMidLevelWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new mid level workflow is created and is in RUNNING state\"\n        newMidLevelWorkflowId != midLevelWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"poll and execute the sub workflow task\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the two tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"the new leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the new mid level and root workflows are 'decided'\"\n        sweep(newMidLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        then: \"the root workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the mid-level workflow.\n     *\n     * Expectation: The mid-level workflow gets a new execution with the same id and spawns a NEW leaf workflow and also updates its parent (root workflow).\n     * When the NEW leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the mid-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the mid level workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = midLevelWorkflowId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the mid workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"verify the SUB_WORKFLOW task in root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the task in the mid level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"the SUB_WORKFLOW task in mid level workflow is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"poll and complete the 2 tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the new leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the mid-level workflow with taskId.\n     *\n     * Expectation: The mid-level workflow gets a new execution with the same id and spawns a NEW leaf workflow and also updates its parent (root workflow).\n     * When the NEW leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the mid-level with taskId in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the mid level workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = midLevelWorkflowId\n        def reRunTaskId = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true).tasks[0].taskId\n        reRunWorkflowRequest.reRunFromTaskId = reRunTaskId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the mid workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"verify the SUB_WORKFLOW task in root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the task in the mid level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"the SUB_WORKFLOW task in mid level workflow is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"poll and complete the 2 tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the new leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the leaf workflow.\n     *\n     * Expectation: The leaf workflow gets a new execution with the same id and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the leaf-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the leaf workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = leafWorkflowId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the leaf workflow creates a new execution\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the root workflow's SUB_WORKFLOW is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the scheduled task in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A rerun is executed on the leaf workflow.\n     *\n     * Expectation: The leaf workflow gets a new execution with the same id and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test rerun on the leaf-level with taskId in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a rerun on the leaf workflow\"\n        def reRunWorkflowRequest = new RerunWorkflowRequest()\n        reRunWorkflowRequest.reRunFromWorkflowId = leafWorkflowId\n        def reRunTaskId = workflowExecutionService.getExecutionStatus(leafWorkflowId, true).tasks[0].taskId\n        reRunWorkflowRequest.reRunFromTaskId = reRunTaskId\n        workflowExecutor.rerun(reRunWorkflowRequest)\n\n        then: \"verify that the leaf workflow creates a new execution\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the root workflow's SUB_WORKFLOW is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the scheduled task in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        //endregion\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SubWorkflowRestartSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass SubWorkflowRestartSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Shared\n    def WORKFLOW_WITH_SUBWORKFLOW = 'integration_test_wf_with_sub_wf'\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    String rootWorkflowId, midLevelWorkflowId, leafWorkflowId\n\n    TaskDef persistedTask2Definition\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('simple_one_task_sub_workflow_integration_test.json',\n                'simple_workflow_1_integration_test.json',\n                'workflow_with_sub_workflow_1_integration_test.json')\n\n        //region Test setup: 3 workflows reach FAILED state. Task 'integration_task_2' in leaf workflow is FAILED.\n        setup: \"Modify task definition to 0 retries\"\n        persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(WORKFLOW_WITH_SUBWORKFLOW, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'retry_on_root_in_3level_wf'\n        def input = [\n                'param1'   : 'p1 value',\n                'param2'   : 'p2 value',\n                'subwf'    : WORKFLOW_WITH_SUBWORKFLOW,\n                'nextSubwf': SIMPLE_WORKFLOW]\n\n        when: \"the workflow is started\"\n        rootWorkflowId = startWorkflow(WORKFLOW_WITH_SUBWORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        def rootWorkflowInstance = workflowExecutionService.getExecutionStatus(rootWorkflowId, true)\n        with(rootWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        midLevelWorkflowId = rootWorkflowInstance.tasks[1].subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def midLevelWorkflowInstance = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)\n\n        then: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        leafWorkflowId = midLevelWorkflowInstance.tasks[1].subWorkflowId\n        def leafWorkflowInstance = workflowExecutionService.getExecutionStatus(leafWorkflowId, true)\n        with(leafWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and fail the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"the leaf workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the mid level workflow is 'decided'\"\n        sweep(midLevelWorkflowId)\n\n        then: \"the mid level subworkflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the root level workflow is 'decided'\"\n        sweep(rootWorkflowId)\n\n        then: \"the root level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n        }\n        //endregion\n    }\n\n    def cleanup() {\n        // Ensure that changes to the task def are reverted\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A restart is executed on the root workflow.\n     *\n     * Expectation: The root workflow gets a new execution with the same id and spawns a NEW mid-level workflow, which in turn spawns a NEW leaf workflow.\n     * When the NEW leaf workflow completes successfully, both the NEW mid-level and root workflows also complete successfully.\n     */\n    def \"Test restart on the root in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a restart on the root workflow\"\n        workflowExecutor.restart(rootWorkflowId, false)\n\n        then: \"poll and complete the 'integration_task_1' task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op1': 'task1.done'])\n\n        and: \"verify that the root workflow created a new SUB_WORKFLOW task\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newMidLevelWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new mid level workflow is created and is in RUNNING state\"\n        newMidLevelWorkflowId != midLevelWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"poll and execute the sub workflow task\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the two tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"the new leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the new mid level and root workflows are 'decided'\"\n        sweep(newMidLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        then: \"the root workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A restart is executed on the mid-level workflow.\n     *\n     * Expectation: The mid-level workflow gets a new execution with the same id and spawns a NEW leaf workflow and also updates its parent (root workflow).\n     * When the NEW leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test restart on the mid-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a restart on the mid level workflow\"\n        workflowExecutor.restart(midLevelWorkflowId, false)\n\n        then: \"verify that the mid workflow created a new execution\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"verify the SUB_WORKFLOW task in root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the task in the mid level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"the SUB_WORKFLOW task in mid level workflow is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'integration_task_1'\n        }\n\n        when: \"poll and complete the 2 tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the new leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A restart is executed on the leaf workflow.\n     *\n     * Expectation: The leaf workflow gets a new execution with the same id and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test restart on the leaf in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a restart on the leaf workflow\"\n        workflowExecutor.restart(leafWorkflowId, false)\n\n        then: \"verify that the leaf workflow creates a new execution\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the root workflow's SUB_WORKFLOW is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the scheduled task in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        //endregion\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SubWorkflowRetrySpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass SubWorkflowRetrySpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Shared\n    def WORKFLOW_WITH_SUBWORKFLOW = 'integration_test_wf_with_sub_wf'\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    String rootWorkflowId, midLevelWorkflowId, leafWorkflowId\n\n    TaskDef persistedTask2Definition\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('simple_one_task_sub_workflow_integration_test.json',\n                'simple_workflow_1_integration_test.json',\n                'workflow_with_sub_workflow_1_integration_test.json')\n\n        //region Test setup: 3 workflows reach FAILED state. Task 'integration_task_2' in leaf workflow is FAILED.\n        setup: \"Modify task definition to 0 retries\"\n        persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(WORKFLOW_WITH_SUBWORKFLOW, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'retry_on_root_in_3level_wf'\n        def input = [\n                'param1'   : 'p1 value',\n                'param2'   : 'p2 value',\n                'subwf'    : WORKFLOW_WITH_SUBWORKFLOW,\n                'nextSubwf': SIMPLE_WORKFLOW]\n\n        when: \"the workflow is started\"\n        rootWorkflowId = startWorkflow(WORKFLOW_WITH_SUBWORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        def rootWorkflowInstance = workflowExecutionService.getExecutionStatus(rootWorkflowId, true)\n        with(rootWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        midLevelWorkflowId = rootWorkflowInstance.tasks[1].subWorkflowId\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def midLevelWorkflowInstance = workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)\n\n        then: \"verify that the mid-level workflow is RUNNING, and first task is in SCHEDULED state\"\n        leafWorkflowId = midLevelWorkflowInstance.tasks[1].subWorkflowId\n        def leafWorkflowInstance = workflowExecutionService.getExecutionStatus(leafWorkflowId, true)\n        with(leafWorkflowInstance) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and fail the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"the leaf workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the mid level workflow is 'decided'\"\n        sweep(midLevelWorkflowId)\n\n        then: \"the mid level subworkflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the root level workflow is 'decided'\"\n        sweep(rootWorkflowId)\n\n        then: \"the root level workflow is in FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed on the root workflow.\n     *\n     * Expectation: The root workflow spawns a NEW mid-level workflow, which in turn spawns a NEW leaf workflow.\n     * When the leaf workflow completes successfully, both the NEW mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the root in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the root workflow\"\n        workflowExecutor.retry(rootWorkflowId, false)\n\n        then: \"poll and complete the 'integration_task_1' task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op1': 'task1.done'])\n\n        and: \"verify that the root workflow created a new SUB_WORKFLOW task\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the subworkflow task should be in SCHEDULED state and is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newMidLevelWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new mid level workflow is created and is in RUNNING state\"\n        newMidLevelWorkflowId != midLevelWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task in the mid-level workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        and: \"poll and execute the sub workflow task\"\n        polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the two tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"the new leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the new mid level and root workflows are 'decided'\"\n        sweep(newMidLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newMidLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        then: \"the root workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed with resume flag on the root workflow.\n     *\n     * Expectation: The leaf workflow is retried and both its parent (mid-level) and grand parent (root) workflows are also retried.\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the root with resume flag in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the root workflow\"\n        workflowExecutor.retry(rootWorkflowId, true)\n\n        then: \"verify that the sub workflow task in root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the sub workflow task in mid level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the previously failed task in leaf workflow is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the mid level workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after \"decide\"\n        }\n\n        and: \"the root workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after \"decide\"\n        }\n\n        when: \"poll and complete the integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the new mid level workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        and: \"the root workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed on the mid-level workflow.\n     *\n     * Expectation: The mid-level workflow spawns a NEW leaf workflow and also updates its parent (root workflow).\n     * When the NEW leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the mid-level in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the mid level workflow\"\n        workflowExecutor.retry(midLevelWorkflowId, false)\n\n        then: \"verify that the mid workflow created a new SUB_WORKFLOW task\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        and: \"verify the SUB_WORKFLOW task in root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"the SUB_WORKFLOW task in mid level workflow is started by issuing a system task call\"\n        def polledTaskIds = queueDAO.pop(TASK_TYPE_SUB_WORKFLOW, 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def newLeafWorkflowId = workflowExecutionService.getTask(polledTaskIds[0]).subWorkflowId\n\n        then: \"verify that a new leaf workflow is created and is in RUNNING state\"\n        newLeafWorkflowId != leafWorkflowId\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 2 tasks in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the new leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(newLeafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed with resume flag on the mid-level workflow.\n     *\n     * Expectation: The leaf workflow is retried and both its parent (mid-level) and grand parent (root) workflows are also retried.\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the mid-level with resume flag in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the root workflow\"\n        workflowExecutor.retry(midLevelWorkflowId, true)\n\n        then: \"verify that the sub workflow task in root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the sub workflow task in mid level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the previously failed task in leaf workflow is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the mid level workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after \"decide\"\n        }\n\n        and: \"the root workflow is in RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            !tasks[1].subworkflowChanged // flag is reset after \"decide\"\n        }\n\n        when: \"poll and complete the previously failed integration_task_2 task\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the leaf workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"the mid level workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n        }\n\n        and: \"the root workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged // flag is reset after decide\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed on the leaf workflow.\n     *\n     * Expectation: The leaf workflow resumes its FAILED task and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the leaf in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the leaf workflow\"\n        workflowExecutor.retry(leafWorkflowId, false)\n\n        then: \"verify that the leaf workflow is in RUNNING state and failed task is retried\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the root workflow' is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the scheduled task in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        //endregion\n    }\n\n    /**\n     * On a 3-level workflow where all workflows reach FAILED state because of a FAILED task\n     * in the leaf workflow.\n     *\n     * A retry is executed with resume flag on the leaf workflow.\n     *\n     * Expectation: The leaf workflow resumes its FAILED task and updates both its parent (mid-level) and grandparent (root).\n     * When the leaf workflow completes successfully, both the mid-level and root workflows also complete successfully.\n     */\n    def \"Test retry on the leaf with resume flag in a 3-level subworkflow\"() {\n        //region Test case\n        when: \"do a retry on the leaf workflow\"\n        workflowExecutor.retry(leafWorkflowId, true)\n\n        then: \"verify that the leaf workflow is in RUNNING state and failed task is retried\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        then: \"verify that the mid-level workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        and: \"verify that the root workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the scheduled task in the leaf workflow\"\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the leaf workflow reached COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(leafWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[1].retried\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].retriedTaskId == tasks[1].taskId\n        }\n\n        when: \"the mid level and root workflows are 'decided'\"\n        sweep(midLevelWorkflowId)\n        sweep(rootWorkflowId)\n\n        then: \"verify that the mid level and root workflows reach COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(midLevelWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        with(workflowExecutionService.getExecutionStatus(rootWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            (!tasks[1].subworkflowChanged) // flag is reset after decide\n        }\n        //endregion\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SubWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.SubWorkflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass SubWorkflowSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    SubWorkflow subWorkflowTask\n\n    @Shared\n    def WORKFLOW_WITH_SUBWORKFLOW = 'integration_test_wf_with_sub_wf'\n\n    @Shared\n    def SUB_WORKFLOW = \"sub_workflow\"\n\n    @Shared\n    def SIMPLE_WORKFLOW = \"integration_test_wf\"\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('simple_one_task_sub_workflow_integration_test.json',\n                'simple_workflow_1_integration_test.json',\n                'workflow_with_sub_workflow_1_integration_test.json')\n    }\n\n    def \"Test retrying a subworkflow where parent workflow timed out due to workflowTimeout\"() {\n\n        setup: \"Register a workflow definition with a timeout policy set to timeout workflow\"\n        def persistedWorkflowDefinition = metadataService.getWorkflowDef(WORKFLOW_WITH_SUBWORKFLOW, 1)\n        def modifiedWorkflowDefinition = new WorkflowDef()\n        modifiedWorkflowDefinition.name = persistedWorkflowDefinition.name\n        modifiedWorkflowDefinition.version = persistedWorkflowDefinition.version\n        modifiedWorkflowDefinition.tasks = persistedWorkflowDefinition.tasks\n        modifiedWorkflowDefinition.inputParameters = persistedWorkflowDefinition.inputParameters\n        modifiedWorkflowDefinition.outputParameters = persistedWorkflowDefinition.outputParameters\n        modifiedWorkflowDefinition.timeoutPolicy = WorkflowDef.TimeoutPolicy.TIME_OUT_WF\n        modifiedWorkflowDefinition.timeoutSeconds = 10\n        modifiedWorkflowDefinition.ownerEmail = persistedWorkflowDefinition.ownerEmail\n        metadataService.updateWorkflowDef([modifiedWorkflowDefinition])\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SUB_WORKFLOW, 1)\n        metadataService.getWorkflowDef(WORKFLOW_WITH_SUBWORKFLOW, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'wf_with_subwf_test_1'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n        input['subwf'] = 'sub_workflow'\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_SUBWORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        and: \"verify that the 'integration_task1' is complete and the next task (subworkflow) is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the subworkflow is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        String subworkflowTaskId = polledTaskIds.get(0)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, subworkflowTaskId)\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"subworkflow is retrieved\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowId = workflow.tasks[1].subWorkflowId\n\n        then: \"verify that the sub workflow is RUNNING, and first task is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"a delay of 10 seconds is introduced and the workflow is sweeped to run the evaluation\"\n        Thread.sleep(10000)\n        sweep(workflowInstanceId)\n\n        then: \"ensure that the workflow has been TIMED OUT and subworkflow task is CANCELED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TIMED_OUT\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.CANCELED\n        }\n\n        and: \"ensure that the subworkflow is TERMINATED and task is CANCELED\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n            tasks[0].status == Task.Status.CANCELED\n        }\n\n        when: \"the subworkflow is retried\"\n        workflowExecutor.retry(subWorkflowId, false)\n\n        then: \"ensure that the subworkflow is RUNNING and task is retried\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n            tasks[0].status == Task.Status.CANCELED\n            tasks[1].taskType == 'simple_task_in_sub_wf'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        and: \"verify that change flag is set on the sub workflow task in parent\"\n        workflowExecutionService.getTask(subworkflowTaskId).subworkflowChanged\n\n        when: \"Polled for simple_task_in_sub_wf task in subworkflow\"\n        pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('simple_task_in_sub_wf', 'task1.integration.worker', ['op': 'simple_task_in_sub_wf.done'])\n\n        then: \"verify that the 'simple_task_in_sub_wf' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        and: \"verify that the subworkflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n            tasks[0].status == Task.Status.CANCELED\n            tasks[1].taskType == 'simple_task_in_sub_wf'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n\n        and: \"subworkflow task is in a completed state\"\n        with(workflowExecutionService.getTask(subworkflowTaskId)) {\n            status == Task.Status.COMPLETED\n            subworkflowChanged\n        }\n\n        and: \"the parent workflow is swept\"\n        sweep(workflowInstanceId)\n\n        and: \"the parent workflow has been resumed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged\n            output['op'] == 'simple_task_in_sub_wf.done'\n        }\n\n        cleanup: \"Ensure that the changes to the workflow def are reverted\"\n        metadataService.updateWorkflowDef([persistedWorkflowDefinition])\n    }\n\n    def \"Test terminating a subworkflow terminates parent workflow\"() {\n        given: \"Existing workflow and subworkflow definitions\"\n        metadataService.getWorkflowDef(SUB_WORKFLOW, 1)\n        metadataService.getWorkflowDef(WORKFLOW_WITH_SUBWORKFLOW, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'wf_with_subwf_test_1'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n        input['subwf'] = 'sub_workflow'\n\n        when: \"Start a workflow with subworkflow based on the registered definition\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_SUBWORKFLOW, 1,\n                correlationId, input,\n                null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polled for integration_task_1 task\"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that the 'integration_task1' is complete and the next task (subworkflow) is in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Polled for and executed subworkflow task\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowId = workflow.tasks[1].subWorkflowId\n\n        then: \"verify that the 'sub_workflow_task' is polled and IN_PROGRESS\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        and: \"verify that the sub workflow is RUNNING, and first task is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"subworkflow is terminated\"\n        def terminateReason = \"terminating from a test case\"\n        workflowExecutor.terminateWorkflow(subWorkflowId, terminateReason)\n\n        then: \"verify that sub workflow is in terminated state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'simple_task_in_sub_wf'\n            tasks[0].status == Task.Status.CANCELED\n            reasonForIncompletion == terminateReason\n        }\n\n        and:\n        sweep(workflowInstanceId)\n\n        and: \"verify that parent workflow is in terminated state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.CANCELED\n            reasonForIncompletion && reasonForIncompletion.contains(terminateReason)\n        }\n    }\n\n    def \"Test retrying a workflow with subworkflow resume\"() {\n        setup: \"Modify task definition to 0 retries\"\n        def persistedTask2Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_2').get()\n        def modifiedTask2Definition = new TaskDef(persistedTask2Definition.name, persistedTask2Definition.description,\n                persistedTask2Definition.ownerEmail, 0, persistedTask2Definition.timeoutSeconds,\n                persistedTask2Definition.responseTimeoutSeconds)\n        metadataService.updateTaskDef(modifiedTask2Definition)\n\n        and: \"an existing workflow with subworkflow and registered definitions\"\n        metadataService.getWorkflowDef(SIMPLE_WORKFLOW, 1)\n        metadataService.getWorkflowDef(WORKFLOW_WITH_SUBWORKFLOW, 1)\n\n        and: \"input required to start the workflow execution\"\n        String correlationId = 'wf_retry_with_subwf_resume_test'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n        input['subwf'] = 'integration_test_wf'\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_SUBWORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"verify that the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        and: \"verify that the 'integration_task_1' is complete and the next task (subworkflow) is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the subworkflow is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"SUB_WORKFLOW\", 1, 200)\n        asyncSystemTaskExecutor.execute(subWorkflowTask, polledTaskIds[0])\n\n        then: \"verify that the 'sub_workflow_task' is in a IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TaskType.SUB_WORKFLOW.name()\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"subworkflow is retrieved\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        def subWorkflowId = workflow.tasks[1].subWorkflowId\n\n        then: \"verify that the sub workflow is RUNNING, and first task is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        and: \"verify that the 'integration_task_1' is complete and the next task is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and fail the integration_task_2 task\"\n        def pollAndFailTask = workflowTestUtil.pollAndFailTask('integration_task_2', 'task2.integration.worker', 'failed')\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndFailTask)\n\n        then: \"the sub workflow ends up in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        and:\n        sweep(workflowInstanceId)\n\n        and: \"the workflow is in a FAILED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.FAILED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SUB_WORKFLOW'\n            tasks[1].status == Task.Status.FAILED\n        }\n\n        when: \"the workflow is retried by resuming subworkflow task\"\n        workflowExecutor.retry(workflowInstanceId, true)\n\n        then: \"the subworkflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        and: \"the workflow is in a RUNNING state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.IN_PROGRESS\n            tasks[1].subworkflowChanged\n        }\n\n        when: \"poll and complete the integration_task_2 task\"\n        pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker', ['op': 'task2.done'])\n\n        then: \"verify that the 'integration_task_2' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        then: \"the integration_task_2 is complete sub workflow ends up in a COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(subWorkflowId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.FAILED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n        }\n\n        and:\n        sweep(workflowInstanceId)\n\n        then: \"the workflow is in a COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == TASK_TYPE_SUB_WORKFLOW\n            tasks[1].status == Task.Status.COMPLETED\n            !tasks[1].subworkflowChanged\n        }\n\n        cleanup: \"Ensure that changes to the task def are reverted\"\n        metadataService.updateTaskDef(persistedTask2Definition)\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SwitchTaskSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.tasks.Join\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\nimport spock.lang.Unroll\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass SwitchTaskSpec extends AbstractSpecification {\n\n    @Autowired\n    Join joinTask\n\n    @Shared\n    def SWITCH_WF = \"SwitchWorkflow\"\n\n    @Shared\n    def FORK_JOIN_SWITCH_WF = \"ForkConditionalTest\"\n\n    @Shared\n    def COND_TASK_WF = \"ConditionalTaskWF\"\n\n    @Shared\n    def SWITCH_NODEFAULT_WF = \"SwitchWithNoDefaultCaseWF\"\n\n    def setup() {\n        //initialization code for each feature\n        workflowTestUtil.registerWorkflows('simple_switch_task_integration_test.json',\n                'switch_and_fork_join_integration_test.json',\n                'conditional_switch_task_workflow_integration_test.json',\n                'switch_with_no_default_case_integration_test.json')\n    }\n\n    def \"Test simple switch workflow\"() {\n        given: \"Workflow an input of a workflow with switch task\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'p1'\n        input['param2'] = 'p2'\n        input['case'] = 'c'\n\n        when: \"A switch workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(SWITCH_WF, 1,\n                'switch_workflow', input,\n                null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'SWITCH'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_1' is polled and completed\"\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n\n        and: \"verify that the 'integration_task_1' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'SWITCH'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_2' is polled and completed\"\n        def polledAndCompletedTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2Try1)\n\n        and: \"verify that the 'integration_task_2' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[2].taskType == 'integration_task_2'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_20'\n            tasks[3].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_20' is polled and completed\"\n        def polledAndCompletedTask20Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_20', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask20Try1)\n\n        and: \"verify that the 'integration_task_20' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[3].taskType == 'integration_task_20'\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test a workflow that has a switch task that leads to a fork join\"() {\n        given: \"Workflow an input of a workflow with switch task\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'p1'\n        input['param2'] = 'p2'\n        input['case'] = 'c'\n\n        when: \"A switch workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(FORK_JOIN_SWITCH_WF, 1,\n                'switch_forkjoin', input,\n                null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 5\n            tasks[0].taskType == 'FORK'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SWITCH'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.SCHEDULED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"the tasks 'integration_task_1' and 'integration_task_10' are polled and completed\"\n        def joinTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName(\"joinTask\").taskId\n        def polledAndCompletedTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n        def polledAndCompletedTask10Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_10', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask1Try1)\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask10Try1)\n\n        and: \"verify that the 'integration_task_1' and 'integration_task_10' are COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 6\n            tasks[2].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[3].taskType == 'integration_task_10'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['t20', 't10']\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_2' is polled and completed\"\n        def polledAndCompletedTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask2Try1)\n\n        and: \"verify that the 'integration_task_2' is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 7\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['t20', 't10']\n            tasks[4].status == Task.Status.IN_PROGRESS\n            tasks[5].taskType == 'integration_task_2'\n            tasks[5].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_20'\n            tasks[6].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_20' is polled and completed\"\n        def polledAndCompletedTask20Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_20', 'task1.integration.worker')\n\n        and: \"the workflow is evaluated\"\n        sweep(workflowInstanceId)\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask20Try1)\n\n        when: \"JOIN task is polled and executed\"\n        asyncSystemTaskExecutor.execute(joinTask, joinTaskId)\n\n        then: \"verify that the JOIN is COMPLETED and the workflow has progressed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 7\n            tasks[4].taskType == 'JOIN'\n            tasks[4].inputData['joinOn'] == ['t20', 't10']\n            tasks[4].status == Task.Status.COMPLETED\n            tasks[6].taskType == 'integration_task_20'\n            tasks[6].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test default case condition execution of a conditional workflow\"() {\n        given: \"input for a workflow to ensure that the default case is executed\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'xxx'\n        input['param2'] = 'two'\n\n        when: \"A conditional workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(COND_TASK_WF, 1,\n                'conditional_default', input,\n                null)\n\n        then: \"verify that the workflow is running and the default condition case was executed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'SWITCH'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['evaluationResult'] == ['xxx']\n            tasks[1].taskType == 'integration_task_10'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_10' is polled and completed\"\n        def polledAndCompletedTask10Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_10', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask10Try1)\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_10'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SWITCH'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData['evaluationResult'] == ['null']\n        }\n    }\n\n    @Unroll\n    def \"Test case 'nested' and '#caseValue' condition execution of a conditional workflow\"() {\n        given: \"input for a workflow to ensure that the 'nested' and '#caseValue' switch tree is executed\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'nested'\n        input['param2'] = caseValue\n\n        when: \"A conditional workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(COND_TASK_WF, 1,\n                workflowCorrelationId, input,\n                null)\n\n        then: \"verify that the workflow is running and the 'nested' and '#caseValue' condition case was executed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'SWITCH'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['evaluationResult'] == ['nested']\n            tasks[1].taskType == 'SWITCH'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[1].outputData['evaluationResult'] == [caseValue]\n            tasks[2].taskType == expectedTaskName\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task '#expectedTaskName' is polled and completed\"\n        def polledAndCompletedTaskTry1 = workflowTestUtil.pollAndCompleteTask(expectedTaskName, 'task.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTaskTry1)\n\n        and:\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[2].taskType == expectedTaskName\n            tasks[2].status == endTaskStatus\n            tasks[3].taskType == 'SWITCH'\n            tasks[3].status == Task.Status.COMPLETED\n            tasks[3].outputData['evaluationResult'] == ['null']\n        }\n\n        where:\n        caseValue | expectedTaskName     | workflowCorrelationId    || endTaskStatus\n        'two'     | 'integration_task_2' | 'conditional_nested_two' || Task.Status.COMPLETED\n        'one'     | 'integration_task_1' | 'conditional_nested_one' || Task.Status.COMPLETED\n    }\n\n    def \"Test 'three' case condition execution of a conditional workflow\"() {\n        given: \"input for a workflow to ensure that the default case is executed\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'three'\n        input['param2'] = 'two'\n        input['finalCase'] = 'notify'\n\n        when: \"A conditional workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(COND_TASK_WF, 1,\n                'conditional_three', input,\n                null)\n\n        then: \"verify that the workflow is running and the 'three' condition case was executed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'SWITCH'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData['evaluationResult'] == ['three']\n            tasks[1].taskType == 'integration_task_3'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_3' is polled and completed\"\n        def polledAndCompletedTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask3Try1)\n\n        and: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 4\n            tasks[1].taskType == 'integration_task_3'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SWITCH'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData['evaluationResult'] == ['notify']\n            tasks[3].taskType == 'integration_task_4'\n            tasks[3].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_4' is polled and completed\"\n        def polledAndCompletedTask4Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_4', 'task1.integration.worker')\n\n        then: \"verify that the tasks are completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask4Try1)\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 4\n            tasks[1].taskType == 'integration_task_3'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'SWITCH'\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].outputData['evaluationResult'] == ['notify']\n            tasks[3].taskType == 'integration_task_4'\n            tasks[3].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test switch with no default case workflow\"() {\n        given: \"Workflow input\"\n        Map input = new HashMap<String, Object>()\n        input['param1'] = 'p1'\n        input['param2'] = 'p2'\n\n        when: \"A switch workflow is started with the workflow input\"\n        def workflowInstanceId = startWorkflow(SWITCH_NODEFAULT_WF, 1,\n                'switch_no_default_workflow', input,\n                null)\n\n        then: \"verify that the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'SWITCH'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the task 'integration_task_2' is polled and completed\"\n        def polledAndCompletedTaskTry = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then: \"verify that the task is completed and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTaskTry)\n\n        and: \"verify that the 'integration_task_2' is COMPLETED and the workflow is in COMPLETED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'SWITCH'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/SystemTaskSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\nimport com.netflix.conductor.test.utils.UserTask\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass SystemTaskSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    UserTask userTask\n\n    @Shared\n    def ASYNC_COMPLETE_SYSTEM_TASK_WORKFLOW = 'async_complete_integration_test_wf'\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('simple_workflow_with_async_complete_system_task_integration_test.json')\n    }\n\n    def \"Test system task with asyncComplete set to true\"() {\n\n        given: \"An existing workflow definition with async complete system task\"\n        metadataService.getWorkflowDef(ASYNC_COMPLETE_SYSTEM_TASK_WORKFLOW, 1)\n\n        and: \"input required to start the workflow\"\n        String correlationId = 'async_complete_test' + UUID.randomUUID()\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        when: \"the workflow is started\"\n        def workflowInstanceId = startWorkflow(ASYNC_COMPLETE_SYSTEM_TASK_WORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"ensure that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the integration_task_1 task\"\n        def pollAndCompleteTask = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is in SCHEDULED state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'USER_TASK'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the system task is started by issuing a system task call\"\n        List<String> polledTaskIds = queueDAO.pop(\"USER_TASK\", 1, 200)\n        asyncSystemTaskExecutor.execute(userTask, polledTaskIds[0])\n\n        then: \"verify that the system task is in IN_PROGRESS state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == \"USER_TASK\"\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"sweeper evaluates the workflow\"\n        sweep(workflowInstanceId)\n\n        then: \"workflow state is unchanged\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == \"USER_TASK\"\n            tasks[1].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"result of the user task is curated\"\n        Task task = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).getTaskByRefName('user_task')\n        def taskResult = new TaskResult(task)\n        taskResult.status = TaskResult.Status.COMPLETED\n        taskResult.outputData['op'] = 'user.task.done'\n\n        and: \"external signal is simulated with this output to complete the system task\"\n        workflowExecutor.updateTask(taskResult)\n\n        then: \"ensure that the system task is COMPLETED and workflow is COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'USER_TASK'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/TaskLimitsWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\nimport com.netflix.conductor.test.utils.UserTask\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass TaskLimitsWorkflowSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Autowired\n    UserTask userTask\n\n    def RATE_LIMITED_SYSTEM_TASK_WORKFLOW = 'test_rate_limit_system_task_workflow'\n    def RATE_LIMITED_SIMPLE_TASK_WORKFLOW = 'test_rate_limit_simple_task_workflow'\n    def CONCURRENCY_EXECUTION_LIMITED_WORKFLOW = 'test_concurrency_limits_workflow'\n\n    def setup() {\n        workflowTestUtil.registerWorkflows(\n                'rate_limited_system_task_workflow_integration_test.json',\n                'rate_limited_simple_task_workflow_integration_test.json',\n                'concurrency_limited_task_workflow_integration_test.json'\n        )\n    }\n\n    def \"Verify that the rate limiting for system tasks is honored\"() {\n        when: \"Start a workflow that has a rate limited system task in it\"\n        def workflowInstanceId = startWorkflow(RATE_LIMITED_SYSTEM_TASK_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'USER_TASK'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Execute the user task\"\n        def scheduledTask1 = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[0]\n        asyncSystemTaskExecutor.execute(userTask, scheduledTask1.taskId)\n\n        then: \"Verify the state of the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].taskType == 'USER_TASK'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n\n        when: \"A new instance of the workflow is started\"\n        def workflowTwoInstanceId = startWorkflow(RATE_LIMITED_SYSTEM_TASK_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'USER_TASK'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Execute the user task on the second workflow\"\n        def scheduledTask2 = workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true).tasks[0]\n        asyncSystemTaskExecutor.execute(userTask, scheduledTask2.taskId)\n\n        then: \"Verify the state of the workflow is still in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'USER_TASK'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n    }\n\n    def \"Verify that the rate limiting for simple tasks is honored\"() {\n        when: \"Start a workflow that has a rate limited simple task in it\"\n        def workflowInstanceId = startWorkflow(RATE_LIMITED_SIMPLE_TASK_WORKFLOW, 1, '', [:], null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'test_simple_task_with_rateLimits'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"polling and completing the task\"\n        Tuple polledAndCompletedTask = workflowTestUtil.pollAndCompleteTask('test_simple_task_with_rateLimits', 'rate.limit.test.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask)\n\n        and: \"the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].taskType == 'test_simple_task_with_rateLimits'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n\n        when: \"A new instance of the workflow is started\"\n        def workflowTwoInstanceId = startWorkflow(RATE_LIMITED_SIMPLE_TASK_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'test_simple_task_with_rateLimits'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"polling for the task\"\n        def polledTask = workflowExecutionService.poll('test_simple_task_with_rateLimits', 'rate.limit.test.worker')\n\n        then: \"verify that no task is returned\"\n        !polledTask\n\n        when: \"sleep for 10 seconds to ensure rate limit duration is past\"\n        Thread.sleep(10000L)\n\n        and: \"the task offset time is reset to ensure that a task is returned on the next poll\"\n        queueDAO.resetOffsetTime('test_simple_task_with_rateLimits',\n                workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true).tasks[0].taskId)\n\n        and: \"polling and completing the task\"\n        polledAndCompletedTask = workflowTestUtil.pollAndCompleteTask('test_simple_task_with_rateLimits', 'rate.limit.test.worker')\n\n        then: \"verify that the task was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTask)\n\n        and: \"the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].taskType == 'test_simple_task_with_rateLimits'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Verify that concurrency limited tasks are honored during workflow execution\"() {\n        when: \"Start a workflow that has a concurrency execution limited task in it\"\n        def workflowInstanceId = startWorkflow(CONCURRENCY_EXECUTION_LIMITED_WORKFLOW, 1,\n                '', [:], null)\n\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'test_task_with_concurrency_limit'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The task is polled and acknowledged\"\n        def polledTask1 = workflowExecutionService.poll('test_task_with_concurrency_limit', 'test_task_worker')\n\n        then: \"Verify that the task was polled and acknowledged\"\n        polledTask1.taskType == 'test_task_with_concurrency_limit'\n        polledTask1.workflowInstanceId == workflowInstanceId\n\n        when: \"A additional workflow that has a concurrency execution limited task in it\"\n        def workflowTwoInstanceId = startWorkflow(CONCURRENCY_EXECUTION_LIMITED_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'test_task_with_concurrency_limit'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The task is polled\"\n        def polledTaskTry1 = workflowExecutionService.poll('test_task_with_concurrency_limit', 'test_task_worker')\n\n        then: \"Verify that there is no task returned\"\n        !polledTaskTry1\n\n        when: \"The task that was polled and acknowledged is completed\"\n        polledTask1.status = Task.Status.COMPLETED\n        workflowExecutionService.updateTask(new TaskResult(polledTask1))\n\n        and: \"The task offset time is reset to ensure that a task is returned on the next poll\"\n        queueDAO.resetOffsetTime('test_task_with_concurrency_limit',\n                workflowExecutionService.getExecutionStatus(workflowTwoInstanceId, true).tasks[0].taskId)\n\n        then: \"Verify that the first workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 1\n            tasks[0].taskType == 'test_task_with_concurrency_limit'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n\n        and: \"The task is polled again and acknowledged\"\n        def polledTaskTry2 = workflowExecutionService.poll('test_task_with_concurrency_limit', 'test_task_worker')\n\n        then: \"Verify that the task is returned since there are no tasks in progress\"\n        polledTaskTry2.taskType == 'test_task_with_concurrency_limit'\n        polledTaskTry2.workflowInstanceId == workflowTwoInstanceId\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/TestWorkflowSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport java.util.concurrent.ArrayBlockingQueue\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.common.run.WorkflowTestRequest\nimport com.netflix.conductor.core.operation.StartWorkflowOperation\nimport com.netflix.conductor.service.WorkflowTestService\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedLargePayloadTask\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass TestWorkflowSpec extends AbstractSpecification {\n\n    @Autowired\n    WorkflowTestService workflowTestService\n\n    def \"Run Workflow Test with simple tasks\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['var'] = \"var_test_value\"\n\n        WorkflowTestRequest request = new WorkflowTestRequest();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test_workflow\");\n        workflowDef.setVersion(1);\n        workflowDef.setOwnerEmail(\"owner@example.com\");\n\n        WorkflowTask task1 = new WorkflowTask();\n        task1.setType(TaskType.TASK_TYPE_SIMPLE);\n        task1.setName(\"task1\");\n        task1.setTaskReferenceName(\"task1\");\n\n        WorkflowTask task2 = new WorkflowTask();\n        task2.setType(TaskType.TASK_TYPE_SIMPLE);\n        task2.setName(\"task2\");\n        task2.setTaskReferenceName(\"task2\");\n\n        workflowDef.getTasks().add(task1);\n        workflowDef.getTasks().add(task2);\n\n        request.setName(workflowDef.getName());\n        request.setVersion(workflowDef.getVersion());\n\n        Queue<WorkflowTestRequest.TaskMock> task1Executions = new LinkedList<>();\n        task1Executions.add(new WorkflowTestRequest.TaskMock(TaskResult.Status.COMPLETED, Map.of(\"key\", \"value\")));\n\n        request.getTaskRefToMockOutput().put(\"task1\", task1Executions);\n\n        request.setWorkflowDef(workflowDef);\n\n        when: \"Start the workflow which has the set variable task\"\n        def workflow = workflowTestService.testWorkflow(request)\n\n        then: \"verify that the simple task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflow.getWorkflowId(), true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'task1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData[\"key\"] == \"value\"\n\n            tasks[1].taskType == 'task2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n\n    }\n\n    def \"Run Workflow Test with decision task\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['var'] = \"var_test_value\"\n\n        WorkflowTestRequest request = new WorkflowTestRequest();\n        WorkflowDef workflowDef = new WorkflowDef();\n        workflowDef.setName(\"test_workflow\");\n        workflowDef.setVersion(1);\n        workflowDef.setOwnerEmail(\"owner@example.com\");\n\n        WorkflowTask task1 = new WorkflowTask();\n        task1.setType(TaskType.TASK_TYPE_SIMPLE);\n        task1.setName(\"task1\");\n        task1.setTaskReferenceName(\"task1\");\n\n        WorkflowTask decision = new WorkflowTask();\n        decision.setType(TaskType.TASK_TYPE_SWITCH);\n        decision.setName(\"switch\");\n        decision.setTaskReferenceName(\"switch\");\n        decision.setEvaluatorType(\"value-param\")\n        decision.setExpression(\"switchCaseValue\")\n        decision.getInputParameters().put(\"switchCaseValue\", \"\\${workflow.input.case}\")\n\n        WorkflowTask d1 = new WorkflowTask();\n        d1.setType(TaskType.TASK_TYPE_SIMPLE);\n        d1.setName(\"task1\");\n        d1.setTaskReferenceName(\"d1\");\n\n        WorkflowTask d2 = new WorkflowTask();\n        d2.setType(TaskType.TASK_TYPE_SIMPLE);\n        d2.setName(\"task2\");\n        d2.setTaskReferenceName(\"d2\");\n\n        decision.getDecisionCases().put(\"a\", Arrays.asList(d1));\n        decision.getDecisionCases().put(\"b\", Arrays.asList(d2));\n\n\n        workflowDef.getTasks().add(task1);\n        workflowDef.getTasks().add(decision);\n\n        request.setName(workflowDef.getName());\n        request.setVersion(workflowDef.getVersion());\n\n        Queue<WorkflowTestRequest.TaskMock> task1Executions = new LinkedList<>();\n        task1Executions.add(new WorkflowTestRequest.TaskMock(TaskResult.Status.COMPLETED, Map.of(\"key\", \"value\")));\n\n        request.getTaskRefToMockOutput().put(\"task1\", task1Executions);\n\n        request.setWorkflowDef(workflowDef);\n        request.setInput(Map.of(\"case\", \"b\"));\n\n        when: \"Start the workflow which has the set variable task\"\n        def workflow = workflowTestService.testWorkflow(request)\n\n        then: \"verify that the simple task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflow.getWorkflowId(), true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'task1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[0].outputData[\"key\"] == \"value\"\n\n            tasks[1].taskType == 'SWITCH'\n            tasks[1].status == Task.Status.COMPLETED\n\n            tasks[2].taskType == 'task2'\n            tasks[2].referenceTaskName == 'd2'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/WaitTaskSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedLargePayloadTask\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass WaitTaskSpec extends AbstractSpecification {\n\n    @Shared\n    def WAIT_BASED_WORKFLOW = 'test_wait_workflow'\n    def SET_VARIABLE_WORKFLOW = 'set_variable_workflow_integration_test'\n\n    def setup() {\n        workflowTestUtil.registerWorkflows('wait_workflow_integration_test.json',\n                'set_variable_workflow_integration_test.json')\n    }\n\n    def \"Test workflow with set variable task\"() {\n        given: \"workflow input\"\n        def workflowInput = new HashMap()\n        workflowInput['var'] = \"var_test_value\"\n\n        when: \"Start the workflow which has the set variable task\"\n        def workflowInstanceId = startWorkflow(SET_VARIABLE_WORKFLOW, 1,\n                '', workflowInput, null)\n\n        then: \"verify that the simple task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'simple'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete the 'simple' with external payload storage\"\n        def pollAndCompleteLargePayloadTask = workflowTestUtil.pollAndCompleteTask('simple', 'simple.worker',\n                ['ok1': 'ov1'])\n\n        then: \"verify that the 'simple' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedLargePayloadTask(pollAndCompleteLargePayloadTask)\n\n        then: \"ensure that the wait task is completed and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[0].taskType == 'simple'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SET_VARIABLE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'WAIT'\n            tasks[2].status == Task.Status.IN_PROGRESS\n            variables as String == '[var:var_test_value]'\n        }\n\n        when: \"The wait task is completed\"\n        def waitTask = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[2]\n        waitTask.status = Task.Status.COMPLETED\n        workflowExecutor.updateTask(new TaskResult(waitTask))\n\n        then: \"ensure that the wait task is completed and the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[0].taskType == 'simple'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'SET_VARIABLE'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'WAIT'\n            tasks[2].status == Task.Status.COMPLETED\n            variables as String == '[var:var_test_value]'\n            output as String == '[variables:[var:var_test_value]]'\n        }\n    }\n\n    def \"Verify that a wait based simple workflow is executed\"() {\n        when: \"Start a wait task based workflow\"\n        def workflowInstanceId = startWorkflow(WAIT_BASED_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"Retrieve the workflow\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == TaskType.WAIT.name()\n            tasks[0].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"The wait task is completed\"\n        def waitTask = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[0]\n        waitTask.status = Task.Status.COMPLETED\n        workflowExecutor.updateTask(new TaskResult(waitTask))\n\n        then: \"ensure that the wait task is completed and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == TaskType.WAIT.name()\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The integration_task_1 is polled and completed\"\n        def polledAndCompletedTry1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"verify that the task was polled and completed and the workflow is in a complete state\"\n        verifyPolledAndAcknowledgedTask(polledAndCompletedTry1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/integration/WorkflowAndTaskConfigurationSpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.metadata.tasks.TaskType\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.execution.StartWorkflowInput\nimport com.netflix.conductor.core.utils.Utils\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.test.base.AbstractSpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass WorkflowAndTaskConfigurationSpec extends AbstractSpecification {\n\n    @Autowired\n    QueueDAO queueDAO\n\n    @Shared\n    def LINEAR_WORKFLOW_T1_T2 = 'integration_test_wf'\n\n    @Shared\n    def TEMPLATED_LINEAR_WORKFLOW = 'integration_test_template_wf'\n\n    @Shared\n    def WORKFLOW_WITH_OPTIONAL_TASK = 'optional_task_wf'\n\n    @Shared\n    def TEST_WORKFLOW = 'integration_test_wf3'\n\n    @Shared\n    def WAIT_TIME_OUT_WORKFLOW = 'test_wait_timeout'\n\n    def setup() {\n        //Register LINEAR_WORKFLOW_T1_T2, TEST_WORKFLOW, RTOWF, WORKFLOW_WITH_OPTIONAL_TASK\n        workflowTestUtil.registerWorkflows(\n                'simple_workflow_1_integration_test.json',\n                'simple_workflow_1_input_template_integration_test.json',\n                'simple_workflow_3_integration_test.json',\n                'simple_workflow_with_optional_task_integration_test.json',\n                'simple_wait_task_workflow_integration_test.json')\n    }\n\n    def \"Test simple workflow which has an optional task\"() {\n\n        given: \"A input parameters for a workflow with an optional task\"\n        def correlationId = 'integration_test' + UUID.randomUUID().toString()\n        def workflowInput = new HashMap()\n        workflowInput['param1'] = 'p1 value'\n        workflowInput['param2'] = 'p2 value'\n\n        when: \"An optional task workflow is started\"\n        def workflowInstanceId = startWorkflow(WORKFLOW_WITH_OPTIONAL_TASK, 1,\n                correlationId, workflowInput,\n                null)\n\n        then: \"verify that the workflow has started and the optional task is in a scheduled state\"\n        workflowInstanceId\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].status == Task.Status.SCHEDULED\n            tasks[0].taskType == 'task_optional'\n        }\n\n        when: \"The first optional task is polled and failed\"\n        Tuple polledAndFailedTaskTry1 = workflowTestUtil.pollAndFailTask('task_optional',\n                'task1.integration.worker', 'NETWORK ERROR')\n\n        then: \"Verify that the task_optional was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(polledAndFailedTaskTry1)\n\n        when: \"A decide is executed on the workflow\"\n        workflowExecutor.decide(workflowInstanceId)\n\n        then: \"verify that the workflow is still running and the first optional task has failed and the retry has kicked in\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].status == Task.Status.FAILED\n            tasks[0].taskType == 'task_optional'\n            tasks[1].status == Task.Status.SCHEDULED\n            tasks[1].taskType == 'task_optional'\n        }\n\n        when: \"Poll the optional task again and do not complete it and run decide\"\n        workflowExecutionService.poll('task_optional', 'task1.integration.worker')\n        Thread.sleep(5000)\n        workflowExecutor.decide(workflowInstanceId)\n\n        then: \"Ensure that the workflow is updated\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].status == Task.Status.COMPLETED_WITH_ERRORS\n            tasks[1].taskType == 'task_optional'\n            tasks[2].status == Task.Status.SCHEDULED\n            tasks[2].taskType == 'integration_task_2'\n        }\n\n        when: \"The second task 'integration_task_2' is polled and completed\"\n        def task2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"Verify that the task was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(task2Try1)\n\n        and: \"Ensure that the workflow is in completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[2].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_2'\n        }\n    }\n\n    def \"test workflow with input template parsing\"() {\n        given: \"Input parameters for a workflow with input template\"\n        def correlationId = 'integration_test' + UUID.randomUUID().toString()\n        def workflowInput = new HashMap()\n        // leave other params blank on purpose to test input templates\n        workflowInput['param3'] = 'external string'\n\n        when: \"Is executed and completes\"\n        def workflowInstanceId = startWorkflow(TEMPLATED_LINEAR_WORKFLOW, 1,\n                correlationId, workflowInput,\n                null)\n        workflowExecutor.decide(workflowInstanceId)\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"Verify that input template is processed\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            output == [\n                    output: \"task1.done\",\n                    param3: 'external string',\n                    param2: ['list', 'of', 'strings'],\n                    param1: [nested_object: [nested_key: \"nested_value\"]]\n            ]\n        }\n    }\n\n    def \"Test simple workflow with task time out configuration\"() {\n\n        setup: \"Register a task definition with retry policy on time out\"\n        def persistedTask1Definition = workflowTestUtil.getPersistedTaskDefinition('integration_task_1').get()\n        def modifiedTaskDefinition = new TaskDef(persistedTask1Definition.name, persistedTask1Definition.description,\n                persistedTask1Definition.ownerEmail, 1, 1, 1)\n        modifiedTaskDefinition.retryDelaySeconds = 0\n        modifiedTaskDefinition.timeoutPolicy = TaskDef.TimeoutPolicy.RETRY\n        metadataService.updateTaskDef(modifiedTaskDefinition)\n\n        when: \"A simple workflow is started that has a task with time out and retry configured\"\n        String correlationId = 'unit_test_1' + UUID.randomUUID()\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n        input['failureWfName'] = 'FanInOutTest'\n\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, input,\n                null)\n\n        then: \"Ensure that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        and: \"The decider queue has one task that is ready to be polled\"\n        queueDAO.getSize(Utils.DECIDER_QUEUE) == 1\n\n        when: \"The the first task 'integration_task_1' is polled and acknowledged\"\n        def task1Try1 = workflowExecutionService.poll('integration_task_1', 'task1.worker')\n\n        then: \"Ensure that a task was polled\"\n        task1Try1\n        task1Try1.workflowInstanceId == workflowInstanceId\n\n        and: \"Ensure that the decider size queue is 1 to to enable the evaluation\"\n        queueDAO.getSize(Utils.DECIDER_QUEUE) == 1\n\n        when: \"There is a delay of 3 seconds introduced and the workflow is sweeped to run the evaluation\"\n        Thread.sleep(3000)\n        sweep(workflowInstanceId)\n\n        then: \"Ensure that the first task has been TIMED OUT and the next task is SCHEDULED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.TIMED_OUT\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll for the task again and acknowledge\"\n        def task1Try2 = workflowExecutionService.poll('integration_task_1', 'task1.worker')\n\n        then: \"Ensure that a task was polled\"\n        task1Try2\n        task1Try2.workflowInstanceId == workflowInstanceId\n\n        when: \"There is a delay of 3 seconds introduced and the workflow is swept to run the evaluation\"\n        Thread.sleep(3000)\n        sweep(workflowInstanceId)\n\n        then: \"Ensure that the first task has been TIMED OUT and the next task is SCHEDULED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TIMED_OUT\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.TIMED_OUT\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.TIMED_OUT\n        }\n\n        cleanup: \"Ensure that the changes of the 'integration_task_1' are reverted\"\n        metadataService.updateTaskDef(persistedTask1Definition)\n    }\n\n    def \"Test workflow timeout configurations\"() {\n        setup: \"Get the workflow definition and change the workflow configuration\"\n        def testWorkflowDefinition = metadataService.getWorkflowDef(TEST_WORKFLOW, 1)\n        testWorkflowDefinition.timeoutPolicy = WorkflowDef.TimeoutPolicy.TIME_OUT_WF\n        testWorkflowDefinition.timeoutSeconds = 5\n        metadataService.updateWorkflowDef(testWorkflowDefinition)\n\n        when: \"A simple workflow is started that has a workflow timeout configured\"\n        String correlationId = 'unit_test_3' + UUID.randomUUID()\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n        input['failureWfName'] = 'FanInOutTest'\n\n        def workflowInstanceId = startWorkflow(TEST_WORKFLOW, 1,\n                correlationId, input,\n                null)\n\n        then: \"Ensure that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The the first task 'integration_task_1' is polled and acknowledged\"\n        def task1Try1 = workflowExecutionService.poll('integration_task_1', 'task1.worker')\n\n        then: \"Ensure that a task was polled\"\n        task1Try1\n        task1Try1.workflowInstanceId == workflowInstanceId\n\n        when: \"There is a delay of 6 seconds introduced and the workflow is swept to run the evaluation\"\n        Thread.sleep(6000)\n        sweep(workflowInstanceId)\n\n        then: \"Ensure that the workflow has timed out\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TIMED_OUT\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n        }\n\n        cleanup: \"Ensure that the workflow configuration changes are reverted\"\n        testWorkflowDefinition.timeoutPolicy = WorkflowDef.TimeoutPolicy.ALERT_ONLY\n        testWorkflowDefinition.timeoutSeconds = 0\n        metadataService.updateWorkflowDef(testWorkflowDefinition)\n    }\n\n    def \"Test retrying a timed out workflow due to workflow timeout\"() {\n        setup: \"Get the workflow definition and change the workflow configuration\"\n        def testWorkflowDefinition = metadataService.getWorkflowDef(TEST_WORKFLOW, 1)\n        testWorkflowDefinition.timeoutPolicy = WorkflowDef.TimeoutPolicy.TIME_OUT_WF\n        testWorkflowDefinition.timeoutSeconds = 5\n        metadataService.updateWorkflowDef(testWorkflowDefinition)\n\n        when: \"A simple workflow is started that has a workflow timeout configured\"\n        String correlationId = 'retry_timeout_wf'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        def workflowInstanceId = startWorkflow(TEST_WORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"Ensure that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The the first task 'integration_task_1' is polled and acknowledged\"\n        def task1Try1 = workflowExecutionService.poll('integration_task_1', 'task1.worker')\n\n        then: \"Ensure that a task was polled\"\n        task1Try1\n        task1Try1.workflowInstanceId == workflowInstanceId\n\n        when: \"There is a delay of 6 seconds introduced and the workflow is swept to run the evaluation\"\n        Thread.sleep(6000)\n        sweep(workflowInstanceId)\n\n        then: \"Ensure that the workflow has timed out\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TIMED_OUT\n            lastRetriedTime == 0\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n        }\n\n        when: \"Retrying the workflow\"\n        workflowExecutor.retry(workflowInstanceId, false)\n\n        then: \"Ensure that the workflow is RUNNING and task is retried\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            lastRetriedTime != 0\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        cleanup: \"Ensure that the workflow configuration changes are reverted\"\n        testWorkflowDefinition.timeoutPolicy = WorkflowDef.TimeoutPolicy.ALERT_ONLY\n        testWorkflowDefinition.timeoutSeconds = 0\n        metadataService.updateWorkflowDef(testWorkflowDefinition)\n    }\n\n    def \"Test retrying a timed out workflow due to workflow timeout without unsuccessful tasks\"() {\n        setup: \"Get the workflow definition and change the workflow configuration\"\n        def testWorkflowDefinition = metadataService.getWorkflowDef(TEST_WORKFLOW, 1)\n        testWorkflowDefinition.timeoutPolicy = WorkflowDef.TimeoutPolicy.TIME_OUT_WF\n        testWorkflowDefinition.timeoutSeconds = 5\n        metadataService.updateWorkflowDef(testWorkflowDefinition)\n\n        when: \"A simple workflow is started that has a workflow timeout configured\"\n        String correlationId = 'retry_timeout_wf'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        def workflowInstanceId = startWorkflow(TEST_WORKFLOW, 1,\n                correlationId, input, null)\n\n        then: \"Ensure that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The the first task 'integration_task_1' is polled and acknowledged\"\n        def task1 = workflowExecutionService.poll('integration_task_1', 'task1.worker')\n\n        then: \"Ensure that a task was polled\"\n        task1\n        task1.workflowInstanceId == workflowInstanceId\n\n        when: \"There is a delay of 6 seconds introduced and the task is completed\"\n        Thread.sleep(6000)\n        task1.status = Task.Status.COMPLETED\n        workflowExecutor.updateTask(new TaskResult(task1))\n\n        then: \"verify that the workflow is TIMED_OUT and the task is COMPLETED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TIMED_OUT\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n\n        when: \"Retrying the workflow\"\n        workflowExecutor.retry(workflowInstanceId, false)\n        sweep(workflowInstanceId)\n\n        then: \"Ensure that the workflow is RUNNING and next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            lastRetriedTime != 0\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        cleanup: \"Ensure that the workflow configuration changes are reverted\"\n        testWorkflowDefinition.timeoutPolicy = WorkflowDef.TimeoutPolicy.ALERT_ONLY\n        testWorkflowDefinition.timeoutSeconds = 0\n        metadataService.updateWorkflowDef(testWorkflowDefinition)\n    }\n\n    def \"Test re-running the simple workflow multiple times after completion\"() {\n\n        given: \"input required to start the workflow execution\"\n        String correlationId = 'unit_test_1'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        when: \"Start a workflow based on the registered simple workflow\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, input,\n                null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete the 'integration_task_1' \"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete 'integration_task_2'\"\n        def pollAndCompleteTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the 'integration_task_2' has been polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try1, ['tp1': inputParam1, 'tp2': 'task1.done'])\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n            output.containsKey('o3')\n        }\n\n        when: \"The completed workflow is re run after integration_task_1\"\n        def reRunWorkflowRequest1 = new RerunWorkflowRequest()\n        reRunWorkflowRequest1.reRunFromWorkflowId = workflowInstanceId\n        def reRunTaskId = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[1].taskId\n        reRunWorkflowRequest1.reRunFromTaskId = reRunTaskId\n        def reRun1WorkflowInstanceId = workflowExecutor.rerun(reRunWorkflowRequest1)\n\n        then: \"Verify that the workflow is in running state and has started the re run after task 1\"\n        with(workflowExecutionService.getExecutionStatus(reRun1WorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete 'integration_task_2'\"\n        def pollAndCompleteReRunTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the 'integration_task_2' has been polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteReRunTask2Try1, ['tp1': inputParam1, 'tp2': 'task1.done'])\n\n        and: \"verify that the re run workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(reRun1WorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n            output.containsKey('o3')\n        }\n\n        when: \"The completed workflow is re run\"\n        def reRunWorkflowRequest2 = new RerunWorkflowRequest()\n        reRunWorkflowRequest2.reRunFromWorkflowId = workflowInstanceId\n        def reRun2WorkflowInstanceId = workflowExecutor.rerun(reRunWorkflowRequest2)\n\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(reRun2WorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete the 'integration_task_1' \"\n        def pollAndCompleteReRun2Task1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteReRun2Task1Try1)\n\n        and: \"verify that the 'integration_task1' is complete and the next task is scheduled\"\n        with(workflowExecutionService.getExecutionStatus(reRun2WorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete 'integration_task_2'\"\n        def pollAndCompleteReRun2Task2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the 'integration_task_2' has been polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteReRun2Task2Try1, ['tp1': inputParam1, 'tp2': 'task1.done'])\n\n        and: \"verify that the workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(reRun2WorkflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n            output.containsKey('o3')\n        }\n    }\n\n    def \"Test task skipping in simple workflows\"() {\n\n        when: \"A simple workflow is started\"\n        String correlationId = 'unit_test_3' + UUID.randomUUID()\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        def workflowInstanceId = startWorkflow(TEST_WORKFLOW, 1,\n                correlationId, input,\n                null)\n\n        then: \"Ensure that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The second task in the workflow is skipped\"\n        workflowExecutor.skipTaskFromWorkflow(workflowInstanceId, 't2', null)\n\n        then: \"Ensure that the second task in the workflow is skipped and the first one is still in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_2'\n            tasks[0].status == Task.Status.SKIPPED\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete the 'integration_task_1' \"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"Ensure that the third task is scheduled and the first one is in complete state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'integration_task_1'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_3'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Poll and complete the 'integration_task_3' \"\n        def pollAndCompleteTask3Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_3', 'task3.integration.worker')\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask3Try1)\n\n        and: \"verify that the workflow is in a complete state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[2].taskType == 'integration_task_3'\n            tasks[2].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test pause and resume simple workflow\"() {\n\n        given: \"input required to start the workflow execution\"\n        String correlationId = 'unit_test_1'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        when: \"Start a workflow based on the registered simple workflow\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, input,\n                null)\n\n        then: \"verify that the workflow is in a running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The running workflow is paused\"\n        workflowExecutor.pauseWorkflow(workflowInstanceId)\n\n        and: \"Poll and complete the 'integration_task_1' \"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that the workflow is in PAUSED state and the next task is not scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.PAUSED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n        }\n\n        when: \"The next task in the workflow is polled for\"\n        def task2Try1 = workflowExecutionService.poll('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that there was no task polled\"\n        !task2Try1\n\n        when: \"A decide is run explicitly\"\n        workflowExecutor.decide(workflowInstanceId)\n\n        and: \"The next task is polled again\"\n        def task2Try2 = workflowExecutionService.poll('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that there was no task polled\"\n        !task2Try2\n\n        when: \"The workflow is resumed\"\n        workflowExecutor.resumeWorkflow(workflowInstanceId)\n\n        then: \"verify that the workflow was resumed and the next task is in a scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll and complete 'integration_task_2'\"\n        def pollAndCompleteTask2Try3 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the 'integration_task_2' has been polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try3, ['tp1': inputParam1, 'tp2': 'task1.done'])\n\n        and: \"verify that the re run workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n            output.containsKey('o3')\n        }\n    }\n\n    def \"Test wait time out task based simple workflow\"() {\n        when: \"Start a workflow based on a task that has a registered wait time out\"\n        def workflowInstanceId = startWorkflow(WAIT_TIME_OUT_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"verify that the workflow is running and the first task scheduled\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'WAIT'\n            tasks[0].status == Task.Status.IN_PROGRESS\n        }\n\n        when: \"A delay is introduced\"\n        Thread.sleep(3000)\n\n        and: \"A decide is executed on the workflow\"\n        workflowExecutor.decide(workflowInstanceId)\n\n        then: \"verify that the workflow is in running state and a replacement task has been scheduled due to time out\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'WAIT'\n            tasks[0].status == Task.Status.TIMED_OUT\n            tasks[1].taskType == 'WAIT'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The wait task is completed\"\n        def waitTask = workflowExecutionService.getExecutionStatus(workflowInstanceId, true).tasks[1]\n        waitTask.status = Task.Status.COMPLETED\n        workflowExecutor.updateTask(new TaskResult(waitTask))\n\n        and: \"verify that the workflow is in running state and the next task is scheduled and 'waitTimeout' task is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 3\n            tasks[1].taskType == 'WAIT'\n            tasks[1].status == Task.Status.COMPLETED\n            tasks[2].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.SCHEDULED\n        }\n\n        and: \"Poll and complete the 'integration_task_1' \"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"The workflow is in a completed state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 3\n            tasks[2].taskType == 'integration_task_1'\n            tasks[2].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test simple workflow with callbackAfterSeconds for tasks\"() {\n\n        given: \"input required to start the workflow execution\"\n        String correlationId = 'unit_test_1'\n        def input = new HashMap()\n        String inputParam1 = 'p1 value'\n        input['param1'] = inputParam1\n        input['param2'] = 'p2 value'\n\n        when: \"Start a workflow based on the registered simple workflow\"\n        def workflowInstanceId = startWorkflow(LINEAR_WORKFLOW_T1_T2, 1,\n                correlationId, input,\n                null)\n\n        then: \"Ensure that the workflow has started\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The first task is polled and then a callbackAfterSeconds is added to the task\"\n        def task1Try1 = workflowExecutionService.poll('integration_task_1', 'task1.worker')\n        task1Try1.status = Task.Status.IN_PROGRESS\n        task1Try1.callbackAfterSeconds = 2L\n        workflowExecutionService.updateTask(new TaskResult(task1Try1))\n\n        then: \"verify that the workflow is in running state and the task is in SCHEDULED\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"the 'integration_task_1' is polled again\"\n        def task1Try2 = workflowExecutionService.poll('integration_task_1', 'task1.worker')\n\n        then: \"Ensure that there was no task polled due to the callBackAfterSeconds\"\n        !task1Try2\n\n        then: \"verify that the workflow is in running state and the task is in progress\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"There is a delay introduced to go over the callbackAfterSeconds interval\"\n        Thread.sleep(2050)\n\n        and: \"the 'integration_task_1' is polled and completed\"\n        def pollAndCompleteTask1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker', ['op': 'task1.done'])\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask1Try1)\n\n        and: \"verify that the workflow has moved forward and 'integration_task_1 is completed'\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The second task is polled and then a callbackAfterSeconds is added to the task\"\n        def task2Try1 = workflowExecutionService.poll('integration_task_2', 'task2.worker')\n        task2Try1.status = Task.Status.IN_PROGRESS\n        task2Try1.callbackAfterSeconds = 5L\n        workflowExecutionService.updateTask(new TaskResult(task2Try1))\n\n        then: \"Verify that the workflow is in running state and the task is in scheduled state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"poll for 'integration_task_2'\"\n        def task2Try2 = workflowExecutionService.poll('integration_task_2', 'task2.worker')\n\n        then: \"Ensure that there was no task polled due to the callBackAfterSeconds, even though the task is in scheduled state\"\n        !task2Try2\n\n        when: \"A delay is introduced to get over the callBackAfterSeconds interval\"\n        Thread.sleep(5100)\n\n        and: \"the 'integration_task_2' is polled and completed\"\n        def pollAndCompleteTask2Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task1.integration.worker')\n\n        then: \"verify that the 'integration_task_1' was polled and acknowledged\"\n        verifyPolledAndAcknowledgedTask(pollAndCompleteTask2Try1)\n\n        and: \"verify that the workflow has moved forward and 'integration_task_1 is completed'\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n    }\n\n    def \"Test workflow with no tasks\"() {\n        setup: \"Create a workflow definition with no tasks\"\n        WorkflowDef emptyWorkflowDef = new WorkflowDef()\n        emptyWorkflowDef.setName(\"empty_workflow\")\n        emptyWorkflowDef.setSchemaVersion(2)\n\n        when: \"a workflow is started with this definition\"\n        def input = new HashMap()\n        def correlationId = 'empty_workflow'\n        def workflowInstanceId = startWorkflowOperation.execute(new StartWorkflowInput(workflowDefinition: emptyWorkflowDef, workflowInput: input, correlationId: correlationId))\n\n        then: \"the workflow is completed\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 0\n        }\n    }\n\n    def \"Test task def template\"() {\n        setup: \"Register a task definition with input template\"\n        TaskDef templatedTask = new TaskDef()\n        templatedTask.setName('templated_task')\n        def httpRequest = new HashMap<>()\n        httpRequest['method'] = 'GET'\n        httpRequest['vipStack'] = '${STACK2}'\n        httpRequest['uri'] = '/get/something'\n        def body = new HashMap<>()\n        body['inputPaths'] = Arrays.asList('${workflow.input.path1}', '${workflow.input.path2}')\n        body['requestDetails'] = '${workflow.input.requestDetails}'\n        body['outputPath'] = '${workflow.input.outputPath}'\n        httpRequest['body'] = body\n        templatedTask.inputTemplate['http_request'] = httpRequest\n        templatedTask.ownerEmail = \"test@harness.com\"\n        metadataService.registerTaskDef(Arrays.asList(templatedTask))\n\n        and: \"set a system property for STACK2\"\n        System.setProperty('STACK2', 'test_stack')\n\n        and: \"a workflow definition using this task is created\"\n        WorkflowTask workflowTask = new WorkflowTask()\n        workflowTask.setName(templatedTask.getName())\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE)\n        workflowTask.setTaskReferenceName(\"t0\")\n\n        WorkflowDef templateWorkflowDef = new WorkflowDef()\n        templateWorkflowDef.setName(\"template_workflow\")\n        templateWorkflowDef.getTasks().add(workflowTask)\n        templateWorkflowDef.setSchemaVersion(2)\n        templateWorkflowDef.setOwnerEmail(\"test@harness.com\")\n        metadataService.registerWorkflowDef(templateWorkflowDef)\n\n        and: \"the input to the workflow is curated\"\n        def requestDetails = new HashMap<>()\n        requestDetails['key1'] = 'value1'\n        requestDetails['key2'] = 42\n\n        Map<String, Object> input = new HashMap<>()\n        input['path1'] = 'file://path1'\n        input['path2'] = 'file://path2'\n        input['outputPath'] = 's3://bucket/outputPath'\n        input['requestDetails'] = requestDetails\n\n        when: \"the workflow is started\"\n        def correlationId = 'workflow_taskdef_template'\n        def workflowInstanceId = startWorkflowOperation.execute(new StartWorkflowInput(workflowDefinition: templateWorkflowDef, workflowInput: input, correlationId: correlationId))\n\n        then: \"the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].inputData.get('http_request') instanceof Map\n            tasks[0].inputData.get('http_request')['method'] == 'GET'\n            tasks[0].inputData.get('http_request')['vipStack'] == 'test_stack'\n            tasks[0].inputData.get('http_request')['body'] instanceof Map\n            tasks[0].inputData.get('http_request')['body']['requestDetails'] instanceof Map\n            tasks[0].inputData.get('http_request')['body']['requestDetails']['key1'] == 'value1'\n            tasks[0].inputData.get('http_request')['body']['requestDetails']['key2'] == 42\n            tasks[0].inputData.get('http_request')['body']['outputPath'] == 's3://bucket/outputPath'\n            tasks[0].inputData.get('http_request')['body']['inputPaths'] instanceof List\n            tasks[0].inputData.get('http_request')['body']['inputPaths'][0] == 'file://path1'\n            tasks[0].inputData.get('http_request')['body']['inputPaths'][1] == 'file://path2'\n            tasks[0].inputData.get('http_request')['uri'] == '/get/something'\n        }\n    }\n\n    def \"Test task def created if not exist\"() {\n        setup: \"Register a workflow definition with task def not registered\"\n        def taskDefName = \"task_not_registered\"\n        WorkflowTask workflowTask = new WorkflowTask()\n        workflowTask.setName(taskDefName)\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE)\n        workflowTask.setTaskReferenceName(\"t0\")\n\n        WorkflowDef testWorkflowDef = new WorkflowDef()\n        testWorkflowDef.setName(\"test_workflow\")\n        testWorkflowDef.getTasks().add(workflowTask)\n        testWorkflowDef.setSchemaVersion(2)\n        testWorkflowDef.setOwnerEmail(\"test@harness.com\")\n        metadataService.registerWorkflowDef(testWorkflowDef)\n\n        when: \"the workflow is started\"\n        def correlationId = 'workflow_taskdef_not_registered'\n        def workflowInstanceId = startWorkflowOperation.execute(new StartWorkflowInput(workflowDefinition: testWorkflowDef, workflowInput: [:], correlationId: correlationId))\n\n        then: \"the workflow is in running state\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskDefName == taskDefName\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/resiliency/QueueResiliencySpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.resiliency\n\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.http.HttpStatus\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage\nimport com.netflix.conductor.core.exception.NotFoundException\nimport com.netflix.conductor.core.exception.TransientException\nimport com.netflix.conductor.core.utils.QueueUtils\nimport com.netflix.conductor.core.utils.Utils\nimport com.netflix.conductor.rest.controllers.TaskResource\nimport com.netflix.conductor.rest.controllers.WorkflowResource\nimport com.netflix.conductor.test.base.AbstractResiliencySpecification\n\n/**\n * When QueueDAO is unavailable,\n * Ensure All Worklow and Task resource endpoints either:\n * 1. Fails and/or throws an Exception\n * 2. Succeeds\n * 3. Doesn't involve QueueDAO\n */\nclass QueueResiliencySpec extends AbstractResiliencySpecification {\n\n    @Autowired\n    WorkflowResource workflowResource\n\n    @Autowired\n    TaskResource taskResource\n\n    def SIMPLE_TWO_TASK_WORKFLOW = 'integration_test_wf'\n\n    def setup() {\n        workflowTestUtil.taskDefinitions()\n        workflowTestUtil.registerWorkflows(\n                'simple_workflow_1_integration_test.json'\n        )\n    }\n\n    /// Workflow Resource endpoints\n\n    def \"Verify Start workflow fails when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def response = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n        then: \"Verify workflow starts when there are no Queue failures\"\n        response\n\n        when: \"We try same request Queue failure\"\n        response = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        then: \"Verify that workflow start fails with BACKEND_ERROR\"\n        1 * queueDAO.push(*_) >> { throw new TransientException(\"Queue push failed from Spy\") }\n        thrown(TransientException.class)\n    }\n\n    def \"Verify terminate succeeds when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"We terminate it when QueueDAO is unavailable\"\n        workflowResource.terminate(workflowInstanceId, \"Terminated from a test\")\n\n        then: \"Verify that terminate is successful without any exceptions\"\n        2 * queueDAO.remove(*_) >> { throw new TransientException(\"Queue remove failed from Spy\") }\n        0 * queueDAO._\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n        }\n    }\n\n    def \"Verify Restart workflow fails when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        and: \"We terminate it when QueueDAO is unavailable\"\n        workflowResource.terminate(workflowInstanceId, \"Terminated from a test\")\n\n        then: \"Verify that workflow is in terminated state\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n        }\n\n        when: \"We restart workflow when QueueDAO is unavailable\"\n        workflowResource.restart(workflowInstanceId, false)\n\n        then: \"\"\n        1 * queueDAO.push(*_) >> { throw new TransientException(\"Queue push failed from Spy\") }\n        1 * queueDAO.remove(*_) >> { throw new TransientException(\"Queue remove failed from Spy\") }\n        0 * queueDAO._\n        thrown(TransientException.class)\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 0\n        }\n    }\n\n    def \"Verify rerun fails when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        and: \"terminate it\"\n        workflowResource.terminate(workflowInstanceId, \"Terminated from a test\")\n\n        then: \"Verify that workflow is in terminated state\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n        }\n\n        when: \"Workflow is rerun when QueueDAO is unavailable\"\n        def rerunWorkflowRequest = new RerunWorkflowRequest()\n        rerunWorkflowRequest.setReRunFromWorkflowId(workflowInstanceId)\n        workflowResource.rerun(workflowInstanceId, rerunWorkflowRequest)\n\n        then: \"\"\n        1 * queueDAO.push(*_) >> { throw new TransientException(\"Queue push failed from Spy\") }\n        0 * queueDAO._\n        thrown(TransientException.class)\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 0\n        }\n    }\n\n    def \"Verify retry fails when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        and: \"terminate it\"\n        workflowResource.terminate(workflowInstanceId, \"Terminated from a test\")\n\n        then: \"Verify that workflow is in terminated state\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n        }\n\n        when: \"workflow is restarted when QueueDAO is unavailable\"\n        workflowResource.retry(workflowInstanceId, false)\n\n        then: \"Verify retry fails\"\n        1 * queueDAO.push(*_) >> { throw new TransientException(\"Queue push failed from Spy\") }\n        0 * queueDAO._\n        thrown(TransientException.class)\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n        }\n    }\n\n    def \"Verify getWorkflow succeeds when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"We get a workflow when QueueDAO is unavailable\"\n        def workflow = workflowResource.getExecutionStatus(workflowInstanceId, true)\n\n        then: \"Verify workflow is returned\"\n        0 * queueDAO._\n        workflow.getStatus() == Workflow.WorkflowStatus.RUNNING\n        workflow.getTasks().size() == 1\n        workflow.getTasks()[0].status == Task.Status.SCHEDULED\n    }\n\n    def \"Verify getWorkflows succeeds when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"We get a workflow when QueueDAO is unavailable\"\n        def workflows = workflowResource.getWorkflows(SIMPLE_TWO_TASK_WORKFLOW, \"\", true, true)\n\n        then: \"Verify queueDAO is not involved and an exception is not thrown\"\n        0 * queueDAO._\n        notThrown(Exception)\n    }\n\n    def \"Verify remove workflow succeeds when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n        then: \"Verify workflow is started\"\n\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"We get a workflow when QueueDAO is unavailable\"\n        workflowResource.delete(workflowInstanceId, false)\n\n        then: \"Verify queueDAO is called to remove from _deciderQueue\"\n        1 * queueDAO.remove(Utils.DECIDER_QUEUE, _)\n\n        when: \"We try to get deleted workflow, verify the status and check if tasks are not removed from queue\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.TERMINATED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.CANCELED\n            0 * queueDAO.remove(QueueUtils.getQueueName(tasks[0]), _)\n        }\n\n        then:\n        thrown(NotFoundException.class)\n    }\n\n    def \"Verify decide succeeds when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"We decide a workflow\"\n        workflowResource.decide(workflowInstanceId)\n\n        then: \"Verify queueDAO is not involved\"\n        0 * queueDAO._\n    }\n\n    def \"Verify pause succeeds when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The workflow is paused when QueueDAO is unavailable\"\n        workflowResource.pauseWorkflow(workflowInstanceId)\n\n        then: \"Verify workflow is paused without any exceptions\"\n        1 * queueDAO.remove(*_) >> { throw new IllegalStateException(\"Queue remove failed from Spy\") }\n        0 * queueDAO._\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.PAUSED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n    }\n\n    def \"Verify resume fails when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The workflow is paused\"\n        workflowResource.pauseWorkflow(workflowInstanceId)\n\n        then: \"Verify workflow is paused\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.PAUSED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Workflow is resumed when QueueDAO is unavailable\"\n        workflowResource.resumeWorkflow(workflowInstanceId)\n\n        then: \"exception is thrown\"\n        1 * queueDAO.push(*_) >> { throw new TransientException(\"Queue push failed from Spy\") }\n        thrown(TransientException.class)\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.PAUSED\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n    }\n\n    def \"Verify reset callbacks fails when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"Task is updated with callBackAfterSeconds\"\n        def workflow = workflowResource.getExecutionStatus(workflowInstanceId, true)\n        def task = workflow.getTasks().get(0)\n        def taskResult = new TaskResult(task)\n        taskResult.setCallbackAfterSeconds(120)\n        taskResource.updateTask(taskResult)\n\n        and: \"and then reset callbacks when QueueDAO is unavailable\"\n        workflowResource.resetWorkflow(workflowInstanceId)\n\n        then: \"Verify an exception is thrown\"\n        1 * queueDAO.resetOffsetTime(*_) >> { throw new TransientException(\"Queue resetOffsetTime failed from Spy\") }\n        thrown(TransientException.class)\n    }\n\n    def \"Verify search is not impacted by QueueDAO\"() {\n        when: \"We perform a search\"\n        workflowResource.search(0, 1, \"\", \"\", \"\")\n\n        then: \"Verify it doesn't involve QueueDAO\"\n        0 * queueDAO._\n    }\n\n    def \"Verify search workflows by tasks is not impacted by QueueDAO\"() {\n        when: \"We perform a search\"\n        workflowResource.searchWorkflowsByTasks(0, 1, \"\", \"\", \"\")\n\n        then: \"Verify it doesn't involve QueueDAO\"\n        0 * queueDAO._\n    }\n\n    def \"Verify get external storage location is not impacted by QueueDAO\"() {\n        when:\n        workflowResource.getExternalStorageLocation(\"\", ExternalPayloadStorage.Operation.READ as String, ExternalPayloadStorage.PayloadType.WORKFLOW_INPUT as String)\n\n        then: \"Verify it doesn't involve QueueDAO\"\n        0 * queueDAO._\n    }\n\n\n    /// Task Resource endpoints\n\n    def \"Verify polls return with no result when QueueDAO is unavailable\"() {\n        when: \"Some task 'integration_task_1' is polled\"\n        def responseEntity = taskResource.poll(\"integration_task_1\", \"test\", \"\")\n\n        then:\n        1 * queueDAO.pop(*_) >> { throw new IllegalStateException(\"Queue pop failed from Spy\") }\n        0 * queueDAO._\n        notThrown(Exception)\n        responseEntity && responseEntity.statusCode == HttpStatus.NO_CONTENT && !responseEntity.body\n    }\n\n    def \"Verify updateTask with COMPLETE status succeeds when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The first task 'integration_task_1' is polled\"\n        def responseEntity = taskResource.poll(\"integration_task_1\", \"test\", null)\n\n        then: \"Verify task is returned successfully\"\n        responseEntity && responseEntity.statusCode == HttpStatus.OK && responseEntity.body\n        responseEntity.body.status == Task.Status.IN_PROGRESS\n        responseEntity.body.taskType == 'integration_task_1'\n\n        when: \"the above task is updated, while QueueDAO is unavailable\"\n        def taskResult = new TaskResult(responseEntity.body)\n        taskResult.setStatus(TaskResult.Status.COMPLETED)\n        def result = taskResource.updateTask(taskResult)\n\n        then: \"updateTask returns successfully without any exceptions\"\n        1 * queueDAO.remove(*_) >> { throw new IllegalStateException(\"Queue remove failed from Spy\") }\n        result == responseEntity.body.taskId\n        notThrown(Exception)\n    }\n\n    def \"Verify updateTask with IN_PROGRESS state fails when QueueDAO is unavailable\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = workflowResource.startWorkflow(new StartWorkflowRequest()\n                .withName(SIMPLE_TWO_TASK_WORKFLOW)\n                .withVersion(1))\n\n        then: \"Verify workflow is started\"\n        with(workflowResource.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 1\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The first task 'integration_task_1' is polled\"\n        def responseEntity = taskResource.poll(\"integration_task_1\", \"test\", null)\n\n        then: \"Verify task is returned successfully\"\n        responseEntity && responseEntity.statusCode == HttpStatus.OK\n        responseEntity.body.status == Task.Status.IN_PROGRESS\n        responseEntity.body.taskType == 'integration_task_1'\n\n        when: \"the above task is updated, while QueueDAO is unavailable\"\n        def taskResult = new TaskResult(responseEntity.body)\n        taskResult.setStatus(TaskResult.Status.IN_PROGRESS)\n        taskResult.setCallbackAfterSeconds(120)\n        def result = taskResource.updateTask(taskResult)\n\n        then: \"updateTask fails with an exception\"\n        1 * queueDAO.postpone(*_) >> { throw new IllegalStateException(\"Queue postpone failed from Spy\") }\n        thrown(Exception)\n    }\n\n    def \"verify getTaskQueueSizes fails when QueueDAO is unavailable\"() {\n        when:\n        taskResource.size(Arrays.asList(\"testTaskType\", \"testTaskType2\"))\n\n        then:\n        1 * queueDAO.getSize(*_) >> { throw new IllegalStateException(\"Queue getSize failed from Spy\") }\n        thrown(Exception)\n    }\n\n    def \"Verify log doesn't involve QueueDAO\"() {\n        when:\n        taskResource.log(\"testTaskId\", \"test log\")\n\n        then:\n        0 * queueDAO._\n    }\n\n    def \"Verify getTaskLogs doesn't involve QueueDAO\"() {\n        when:\n        taskResource.getTaskLogs(\"testTaskId\")\n\n        then:\n        0 * queueDAO._\n    }\n\n    def \"Verify getTask doesn't involve QueueDAO\"() {\n        when:\n        taskResource.getTask(\"testTaskId\")\n\n        then:\n        0 * queueDAO._\n    }\n\n    def \"Verify getAllQueueDetails fails when QueueDAO is unavailable\"() {\n        when:\n        taskResource.all()\n\n        then:\n        1 * queueDAO.queuesDetail() >> { throw new IllegalStateException(\"Queue queuesDetail failed from Spy\") }\n        thrown(Exception)\n    }\n\n    def \"Verify getPollData doesn't involve QueueDAO\"() {\n        when:\n        taskResource.getPollData(\"integration_test_1\")\n\n        then:\n        0 * queueDAO.queuesDetail()\n    }\n\n    def \"Verify getAllPollData fails when QueueDAO is unavailable\"() {\n        when:\n        taskResource.getAllPollData()\n\n        then:\n        1 * queueDAO.queuesDetail() >> { throw new IllegalStateException(\"Queue queuesDetail failed from Spy\") }\n        thrown(Exception)\n    }\n\n    def \"Verify task search is not impacted by QueueDAO\"() {\n        when: \"We perform a search\"\n        taskResource.search(0, 1, \"\", \"\", \"\")\n\n        then: \"Verify it doesn't involve QueueDAO\"\n        0 * queueDAO._\n    }\n\n    def \"Verify task get external storage location is not impacted by QueueDAO\"() {\n        when:\n        taskResource.getExternalStorageLocation(\"\", ExternalPayloadStorage.Operation.READ as String, ExternalPayloadStorage.PayloadType.TASK_INPUT as String)\n\n        then: \"Verify it doesn't involve QueueDAO\"\n        0 * queueDAO._\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/resiliency/TaskResiliencySpec.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.resiliency\n\nimport org.springframework.beans.factory.annotation.Autowired\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.run.Workflow\nimport com.netflix.conductor.core.reconciliation.WorkflowRepairService\nimport com.netflix.conductor.test.base.AbstractResiliencySpecification\n\nimport spock.lang.Shared\n\nimport static com.netflix.conductor.test.util.WorkflowTestUtil.verifyPolledAndAcknowledgedTask\n\nclass TaskResiliencySpec extends AbstractResiliencySpecification {\n\n    @Autowired\n    WorkflowRepairService workflowRepairService\n\n    @Shared\n    def SIMPLE_TWO_TASK_WORKFLOW = 'integration_test_wf'\n\n    def setup() {\n        workflowTestUtil.taskDefinitions()\n        workflowTestUtil.registerWorkflows(\n                'simple_workflow_1_integration_test.json'\n        )\n    }\n\n    def \"Verify that a workflow recovers and completes on schedule task failure from queue push failure\"() {\n        when: \"Start a simple workflow\"\n        def workflowInstanceId = startWorkflow(SIMPLE_TWO_TASK_WORKFLOW, 1,\n                '', [:], null)\n\n        then: \"Retrieve the workflow\"\n        def workflow = workflowExecutionService.getExecutionStatus(workflowInstanceId, true)\n        workflow.status == Workflow.WorkflowStatus.RUNNING\n        workflow.tasks.size() == 1\n        workflow.tasks[0].taskType == 'integration_task_1'\n        workflow.tasks[0].status == Task.Status.SCHEDULED\n        def taskId = workflow.tasks[0].taskId\n\n        // Simulate queue push failure when creating a new task, after completing first task\n        when: \"The first task 'integration_task_1' is polled and completed\"\n        def task1Try1 = workflowTestUtil.pollAndCompleteTask('integration_task_1', 'task1.integration.worker')\n\n        then: \"Verify that the task was polled and acknowledged\"\n        1 * queueDAO.pop(_, 1, _) >> Collections.singletonList(taskId)\n        1 * queueDAO.ack(*_) >> true\n        1 * queueDAO.push(*_) >> { throw new IllegalStateException(\"Queue push failed from Spy\") }\n        verifyPolledAndAcknowledgedTask(task1Try1)\n\n        and: \"Ensure that the next task is SCHEDULED even after failing to push taskId message to queue\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n        }\n\n        when: \"The second task 'integration_task_2' is polled for\"\n        def task1Try2 = workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"Verify that the task was not polled, and the taskId doesn't exist in the queue\"\n        task1Try2[0] == null\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.RUNNING\n            tasks.size() == 2\n            tasks[0].taskType == 'integration_task_1'\n            tasks[0].status == Task.Status.COMPLETED\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.SCHEDULED\n            def currentTaskId = tasks[1].getTaskId()\n            !queueDAO.containsMessage(\"integration_task_2\", currentTaskId)\n        }\n\n        when: \"Running a repair and decide on the workflow\"\n        workflowRepairService.verifyAndRepairWorkflow(workflowInstanceId, true)\n        workflowExecutor.decide(workflowInstanceId)\n        workflowTestUtil.pollAndCompleteTask('integration_task_2', 'task2.integration.worker')\n\n        then: \"verify that the next scheduled task can be polled and executed successfully\"\n        with(workflowExecutionService.getExecutionStatus(workflowInstanceId, true)) {\n            status == Workflow.WorkflowStatus.COMPLETED\n            tasks.size() == 2\n            tasks[1].taskType == 'integration_task_2'\n            tasks[1].status == Task.Status.COMPLETED\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/groovy/com/netflix/conductor/test/util/WorkflowTestUtil.groovy",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.util\n\nimport javax.annotation.PostConstruct\n\nimport org.apache.commons.lang3.StringUtils\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.stereotype.Component\n\nimport com.netflix.conductor.common.metadata.tasks.Task\nimport com.netflix.conductor.common.metadata.tasks.TaskDef\nimport com.netflix.conductor.common.metadata.tasks.TaskResult\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef\nimport com.netflix.conductor.core.WorkflowContext\nimport com.netflix.conductor.core.exception.NotFoundException\nimport com.netflix.conductor.core.execution.WorkflowExecutor\nimport com.netflix.conductor.dao.QueueDAO\nimport com.netflix.conductor.model.WorkflowModel\nimport com.netflix.conductor.service.ExecutionService\nimport com.netflix.conductor.service.MetadataService\n\nimport com.fasterxml.jackson.databind.ObjectMapper\n\n/**\n * This is a helper class used to initialize task definitions required by the tests when loaded up.\n * The task definitions that are loaded up in {@link WorkflowTestUtil#taskDefinitions()} method as part of the post construct of the bean.\n * This class is intended to be used in the Spock integration tests and provides helper methods to:\n * <ul>\n *     <li> Terminate all the  running Workflows</li>\n *     <li> Get the persisted task definition based on the taskName</li>\n *     <li> pollAndFailTask </li>\n *     <li> pollAndCompleteTask </li>\n *     <li> verifyPolledAndAcknowledgedTask </li>\n * </ul>\n *\n * Usage: Autowire this class in any Spock based specification:\n * <code>\n * {@literal @}Autowired\n * WorkflowTestUtil workflowTestUtil\n * </code>\n */\n@Component\nclass WorkflowTestUtil {\n\n    private final MetadataService metadataService\n    private final ExecutionService workflowExecutionService\n    private final WorkflowExecutor workflowExecutor\n    private final QueueDAO queueDAO\n    private final ObjectMapper objectMapper\n    private static final int RETRY_COUNT = 1\n    private static final String TEMP_FILE_PATH = \"/input.json\"\n    private static final String DEFAULT_EMAIL_ADDRESS = \"test@harness.com\"\n\n    @Autowired\n    WorkflowTestUtil(MetadataService metadataService, ExecutionService workflowExecutionService,\n                     WorkflowExecutor workflowExecutor, QueueDAO queueDAO, ObjectMapper objectMapper) {\n        this.metadataService = metadataService\n        this.workflowExecutionService = workflowExecutionService\n        this.workflowExecutor = workflowExecutor\n        this.queueDAO = queueDAO\n        this.objectMapper = objectMapper\n    }\n\n    /**\n     * This function registers all the taskDefinitions required to enable spock based integration testing\n     */\n    @PostConstruct\n    void taskDefinitions() {\n        WorkflowContext.set(new WorkflowContext(\"integration_app\"))\n\n        (0..20).collect { \"integration_task_$it\" }\n                .findAll { !getPersistedTaskDefinition(it).isPresent() }\n                .collect { new TaskDef(it, it, DEFAULT_EMAIL_ADDRESS, 1, 120, 120) }\n                .forEach { metadataService.registerTaskDef([it]) }\n\n        (0..4).collect { \"integration_task_0_RT_$it\" }\n                .findAll { !getPersistedTaskDefinition(it).isPresent() }\n                .collect { new TaskDef(it, it, DEFAULT_EMAIL_ADDRESS, 0, 120, 120) }\n                .forEach { metadataService.registerTaskDef([it]) }\n\n        metadataService.registerTaskDef([new TaskDef('short_time_out', 'short_time_out', DEFAULT_EMAIL_ADDRESS, 1, 5, 5)])\n\n        //This taskWithResponseTimeOut is required by the integration test which exercises the response time out scenarios\n        TaskDef taskWithResponseTimeOut = new TaskDef()\n        taskWithResponseTimeOut.name = \"task_rt\"\n        taskWithResponseTimeOut.timeoutSeconds = 120\n        taskWithResponseTimeOut.retryCount = RETRY_COUNT\n        taskWithResponseTimeOut.retryDelaySeconds = 0\n        taskWithResponseTimeOut.responseTimeoutSeconds = 10\n        taskWithResponseTimeOut.ownerEmail = DEFAULT_EMAIL_ADDRESS\n\n        TaskDef optionalTask = new TaskDef()\n        optionalTask.setName(\"task_optional\")\n        optionalTask.setTimeoutSeconds(5)\n        optionalTask.setRetryCount(1)\n        optionalTask.setTimeoutPolicy(TaskDef.TimeoutPolicy.RETRY)\n        optionalTask.setRetryDelaySeconds(0)\n        optionalTask.setResponseTimeoutSeconds(5)\n        optionalTask.setOwnerEmail(DEFAULT_EMAIL_ADDRESS)\n\n        TaskDef simpleSubWorkflowTask = new TaskDef()\n        simpleSubWorkflowTask.setName('simple_task_in_sub_wf')\n        simpleSubWorkflowTask.setRetryCount(0)\n        simpleSubWorkflowTask.setOwnerEmail(DEFAULT_EMAIL_ADDRESS)\n\n        TaskDef subWorkflowTask = new TaskDef()\n        subWorkflowTask.setName('sub_workflow_task')\n        subWorkflowTask.setRetryCount(1)\n        subWorkflowTask.setResponseTimeoutSeconds(5)\n        subWorkflowTask.setRetryDelaySeconds(0)\n        subWorkflowTask.setOwnerEmail(DEFAULT_EMAIL_ADDRESS)\n\n        TaskDef waitTimeOutTask = new TaskDef()\n        waitTimeOutTask.name = 'waitTimeout'\n        waitTimeOutTask.timeoutSeconds = 2\n        waitTimeOutTask.responseTimeoutSeconds = 2\n        waitTimeOutTask.retryCount = 1\n        waitTimeOutTask.timeoutPolicy = TaskDef.TimeoutPolicy.RETRY\n        waitTimeOutTask.retryDelaySeconds = 10\n        waitTimeOutTask.ownerEmail = DEFAULT_EMAIL_ADDRESS\n\n        TaskDef userTask = new TaskDef()\n        userTask.setName(\"user_task\")\n        userTask.setTimeoutSeconds(20)\n        userTask.setResponseTimeoutSeconds(20)\n        userTask.setRetryCount(1)\n        userTask.setTimeoutPolicy(TaskDef.TimeoutPolicy.RETRY)\n        userTask.setRetryDelaySeconds(10)\n        userTask.setOwnerEmail(DEFAULT_EMAIL_ADDRESS)\n\n        TaskDef concurrentExecutionLimitedTask = new TaskDef()\n        concurrentExecutionLimitedTask.name = \"test_task_with_concurrency_limit\"\n        concurrentExecutionLimitedTask.concurrentExecLimit = 1\n        concurrentExecutionLimitedTask.ownerEmail = DEFAULT_EMAIL_ADDRESS\n\n        TaskDef rateLimitedTask = new TaskDef()\n        rateLimitedTask.name = 'test_task_with_rateLimits'\n        rateLimitedTask.rateLimitFrequencyInSeconds = 10\n        rateLimitedTask.rateLimitPerFrequency = 1\n        rateLimitedTask.ownerEmail = DEFAULT_EMAIL_ADDRESS\n\n        TaskDef rateLimitedSimpleTask = new TaskDef()\n        rateLimitedSimpleTask.name = 'test_simple_task_with_rateLimits'\n        rateLimitedSimpleTask.rateLimitFrequencyInSeconds = 10\n        rateLimitedSimpleTask.rateLimitPerFrequency = 1\n        rateLimitedSimpleTask.ownerEmail = DEFAULT_EMAIL_ADDRESS\n\n        TaskDef eventTaskX = new TaskDef()\n        eventTaskX.name = 'eventX'\n        eventTaskX.timeoutSeconds = 10\n        eventTaskX.responseTimeoutSeconds = 10\n        eventTaskX.ownerEmail = DEFAULT_EMAIL_ADDRESS\n\n        metadataService.registerTaskDef(\n                [taskWithResponseTimeOut, optionalTask, simpleSubWorkflowTask,\n                 subWorkflowTask, waitTimeOutTask, userTask, eventTaskX,\n                 rateLimitedTask, rateLimitedSimpleTask, concurrentExecutionLimitedTask]\n        )\n    }\n\n    /**\n     * This is an helper method that enables each test feature to run from a clean state\n     * This method is intended to be used in the cleanup() or cleanupSpec() method of any spock specification.\n     * By invoking this method all the running workflows are terminated.\n     * @throws Exception When unable to terminate any running workflow\n     */\n    void clearWorkflows() throws Exception {\n        List<String> workflowsWithVersion = metadataService.getWorkflowDefs()\n                .collect { workflowDef -> workflowDef.getName() + \":\" + workflowDef.getVersion() }\n        for (String workflowWithVersion : workflowsWithVersion) {\n            String workflowName = StringUtils.substringBefore(workflowWithVersion, \":\")\n            int version = Integer.parseInt(StringUtils.substringAfter(workflowWithVersion, \":\"))\n            List<String> running = workflowExecutionService.getRunningWorkflows(workflowName, version)\n            for (String workflowId : running) {\n                WorkflowModel workflow = workflowExecutor.getWorkflow(workflowId, false)\n                if (!workflow.getStatus().isTerminal()) {\n                    workflowExecutor.terminateWorkflow(workflowId, \"cleanup\")\n                }\n            }\n        }\n\n        queueDAO.queuesDetail().keySet()\n                .forEach { queueDAO.flush(it) }\n\n        new FileOutputStream(this.getClass().getResource(TEMP_FILE_PATH).getPath()).close()\n    }\n\n    /**\n     * A helper method to retrieve a task definition that is persisted\n     * @param taskDefName The name of the task for which the task definition is requested\n     * @return an Optional of the TaskDefinition\n     */\n    Optional<TaskDef> getPersistedTaskDefinition(String taskDefName) {\n        try {\n            return Optional.of(metadataService.getTaskDef(taskDefName))\n        } catch(NotFoundException nfe) {\n            return Optional.empty()\n        }\n    }\n\n    /**\n     * A helper methods that registers workflows based on the paths of the json file representing a workflow definition\n     * @param workflowJsonPaths a comma separated var ags of the paths of the workflow definitions\n     */\n    void registerWorkflows(String... workflowJsonPaths) {\n        workflowJsonPaths.collect { readFile(it) }\n                .forEach { metadataService.updateWorkflowDef(it) }\n    }\n\n    WorkflowDef readFile(String path) {\n        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)\n        return objectMapper.readValue(inputStream, WorkflowDef.class)\n    }\n\n    /**\n     * A helper method intended to be used in the <tt>when:</tt> block of the spock test feature\n     * This method is intended to be used to poll and update the task status as failed\n     * It also provides a delay to return if needed after the task has been updated to failed\n     * @param taskName name of the task that needs to be polled and failed\n     * @param workerId name of the worker id using which a task is polled\n     * @param failureReason the reason to fail the task that will added to the task update\n     * @param outputParams An optional output parameters if available will be added to the task before updating to failed\n     * @param waitAtEndSeconds an optional delay before the method returns, if the value is 0 skips the delay\n     * @return A Tuple of taskResult and acknowledgement of the poll\n     */\n    Tuple pollAndFailTask(String taskName, String workerId, String failureReason, Map<String, Object> outputParams = null, int waitAtEndSeconds = 0) {\n        def polledIntegrationTask = workflowExecutionService.poll(taskName, workerId)\n        def taskResult = new TaskResult(polledIntegrationTask)\n        taskResult.status = TaskResult.Status.FAILED\n        taskResult.reasonForIncompletion = failureReason\n        if (outputParams) {\n            outputParams.forEach { k, v ->\n                taskResult.outputData[k] = v\n            }\n        }\n        workflowExecutionService.updateTask(taskResult)\n        return waitAtEndSecondsAndReturn(waitAtEndSeconds, polledIntegrationTask)\n    }\n\n    /**\n     * A helper method to introduce delay and convert the polledIntegrationTask and ackPolledIntegrationTask\n     * into a tuple. This method is intended to be used by pollAndFailTask and pollAndCompleteTask\n     * @param waitAtEndSeconds The total seconds of delay before the method returns\n     * @param ackedTaskResult the task result created after ack\n     * @return A Tuple of polledTask and acknowledgement of the poll\n     */\n    static Tuple waitAtEndSecondsAndReturn(int waitAtEndSeconds, Task polledIntegrationTask) {\n        if (waitAtEndSeconds > 0) {\n            Thread.sleep(waitAtEndSeconds * 1000)\n        }\n        return new Tuple(polledIntegrationTask)\n    }\n\n    /**\n     * A helper method intended to be used in the <tt>when:</tt> block of the spock test feature\n     * This method is intended to be used to poll and update the task status as completed\n     * It also provides a delay to return if needed after the task has been updated to completed\n     * @param taskName name of the task that needs to be polled and completed\n     * @param workerId name of the worker id using which a task is polled\n     * @param outputParams An optional output parameters if available will be added to the task before updating to completed\n     * @param waitAtEndSeconds waitAtEndSeconds an optional delay before the method returns, if the value is 0 skips the delay\n     * @return A Tuple of polledTask and acknowledgement of the poll\n     */\n    Tuple pollAndCompleteTask(String taskName, String workerId, Map<String, Object> outputParams = null, int waitAtEndSeconds = 0) {\n        def polledIntegrationTask = workflowExecutionService.poll(taskName, workerId)\n        if (polledIntegrationTask == null) {\n            return new Tuple(null, null)\n        }\n        def taskResult = new TaskResult(polledIntegrationTask)\n        taskResult.status = TaskResult.Status.COMPLETED\n        if (outputParams) {\n            outputParams.forEach { k, v ->\n                taskResult.outputData[k] = v\n            }\n        }\n        workflowExecutionService.updateTask(taskResult)\n        return waitAtEndSecondsAndReturn(waitAtEndSeconds, polledIntegrationTask)\n    }\n\n    Tuple pollAndCompleteLargePayloadTask(String taskName, String workerId, String outputPayloadPath) {\n        def polledIntegrationTask = workflowExecutionService.poll(taskName, workerId)\n        def taskResult = new TaskResult(polledIntegrationTask)\n        taskResult.status = TaskResult.Status.COMPLETED\n        taskResult.outputData = null\n        taskResult.externalOutputPayloadStoragePath = outputPayloadPath\n        workflowExecutionService.updateTask(taskResult)\n        return new Tuple(polledIntegrationTask)\n    }\n\n    Tuple pollAndUpdateTask(String taskName, String workerId, String outputPayloadPath, Map<String, Object> outputParams = null, int waitAtEndSeconds = 0) {\n        def polledIntegrationTask = workflowExecutionService.poll(taskName, workerId)\n        def taskResult = new TaskResult(polledIntegrationTask)\n        taskResult.status = TaskResult.Status.IN_PROGRESS\n        taskResult.callbackAfterSeconds = 1\n        if (outputPayloadPath) {\n            taskResult.outputData = null\n            taskResult.externalOutputPayloadStoragePath = outputPayloadPath\n        } else if (outputParams) {\n            outputParams.forEach { k, v ->\n                taskResult.outputData[k] = v\n            }\n        }\n        workflowExecutionService.updateTask(taskResult)\n        return waitAtEndSecondsAndReturn(waitAtEndSeconds, polledIntegrationTask)\n    }\n\n    /**\n     * A helper method intended to be used in the <tt>then:</tt> block of the spock test feature, ideally intended to be called after either:\n     * pollAndCompleteTask function or pollAndFailTask function\n     * @param completedTaskAndAck A Tuple of polledTask and acknowledgement of the poll\n     * @param expectedTaskInputParams a map of input params that are verified against the polledTask that is part of the completedTaskAndAck tuple\n     */\n    static void verifyPolledAndAcknowledgedTask(Tuple completedTaskAndAck, Map<String, String> expectedTaskInputParams = null) {\n        assert completedTaskAndAck[0]: \"The task polled cannot be null\"\n        def polledIntegrationTask = completedTaskAndAck[0] as Task\n        assert polledIntegrationTask\n        if (expectedTaskInputParams) {\n            expectedTaskInputParams.forEach {\n                k, v ->\n                    assert polledIntegrationTask.inputData.containsKey(k)\n                    assert polledIntegrationTask.inputData[k] == v\n            }\n        }\n    }\n\n    static void verifyPolledAndAcknowledgedLargePayloadTask(Tuple completedTaskAndAck) {\n        assert completedTaskAndAck[0]: \"The task polled cannot be null\"\n        def polledIntegrationTask = completedTaskAndAck[0] as Task\n        assert polledIntegrationTask\n    }\n\n    static void verifyPayload(Map<String, Object> expected,  Map<String, Object> payload) {\n        expected.forEach {\n            k, v ->\n                assert payload.containsKey(k)\n                assert payload[k] == v\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/ConductorTestApp.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor;\n\nimport java.io.IOException;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;\n\n/** Copy of com.netflix.conductor.Conductor for use by @SpringBootTest in AbstractSpecification. */\n\n// Prevents from the datasource beans to be loaded, AS they are needed only for specific databases.\n// In case that SQL database is selected this class will be imported back in the appropriate\n// database persistence module.\n@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)\npublic class ConductorTestApp {\n\n    public static void main(String[] args) throws IOException {\n        SpringApplication.run(ConductorTestApp.class, args);\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/test/integration/AbstractEndToEndTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Optional;\n\nimport org.apache.http.HttpHost;\nimport org.elasticsearch.client.Request;\nimport org.elasticsearch.client.Response;\nimport org.elasticsearch.client.RestClient;\nimport org.elasticsearch.client.RestClientBuilder;\nimport org.junit.AfterClass;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.test.context.TestPropertySource;\nimport org.testcontainers.elasticsearch.ElasticsearchContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.Workflow;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\n\n@TestPropertySource(\n        properties = {\"conductor.indexing.enabled=true\", \"conductor.elasticsearch.version=6\"})\npublic abstract class AbstractEndToEndTest {\n\n    private static final Logger log = LoggerFactory.getLogger(AbstractEndToEndTest.class);\n\n    private static final String TASK_DEFINITION_PREFIX = \"task_\";\n    private static final String DEFAULT_DESCRIPTION = \"description\";\n    // Represents null value deserialized from the redis in memory db\n    private static final String DEFAULT_NULL_VALUE = \"null\";\n    protected static final String DEFAULT_EMAIL_ADDRESS = \"test@harness.com\";\n\n    private static final ElasticsearchContainer container =\n            new ElasticsearchContainer(\n                    DockerImageName.parse(\"docker.elastic.co/elasticsearch/elasticsearch-oss\")\n                            .withTag(\"6.8.17\")); // this should match the client version\n\n    private static RestClient restClient;\n\n    // Initialization happens in a static block so the container is initialized\n    // only once for all the sub-class tests in a CI environment\n    // container is stopped when JVM exits\n    // https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers\n    static {\n        container.start();\n        String httpHostAddress = container.getHttpHostAddress();\n        System.setProperty(\"conductor.elasticsearch.url\", \"http://\" + httpHostAddress);\n        log.info(\"Initialized Elasticsearch {}\", container.getContainerId());\n    }\n\n    @BeforeClass\n    public static void initializeEs() {\n        String httpHostAddress = container.getHttpHostAddress();\n        String host = httpHostAddress.split(\":\")[0];\n        int port = Integer.parseInt(httpHostAddress.split(\":\")[1]);\n\n        RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost(host, port, \"http\"));\n        restClient = restClientBuilder.build();\n    }\n\n    @AfterClass\n    public static void cleanupEs() throws Exception {\n        // deletes all indices\n        Response beforeResponse = restClient.performRequest(new Request(\"GET\", \"/_cat/indices\"));\n        Reader streamReader = new InputStreamReader(beforeResponse.getEntity().getContent());\n        BufferedReader bufferedReader = new BufferedReader(streamReader);\n\n        String line;\n        while ((line = bufferedReader.readLine()) != null) {\n            String[] fields = line.split(\"\\\\s\");\n            String endpoint = String.format(\"/%s\", fields[2]);\n\n            restClient.performRequest(new Request(\"DELETE\", endpoint));\n        }\n\n        if (restClient != null) {\n            restClient.close();\n        }\n    }\n\n    @Test\n    public void testEphemeralWorkflowsWithStoredTasks() {\n        String workflowExecutionName = \"testEphemeralWorkflow\";\n\n        createAndRegisterTaskDefinitions(\"storedTaskDef\", 5);\n        WorkflowDef workflowDefinition = createWorkflowDefinition(workflowExecutionName);\n        WorkflowTask workflowTask1 = createWorkflowTask(\"storedTaskDef1\");\n        WorkflowTask workflowTask2 = createWorkflowTask(\"storedTaskDef2\");\n        workflowDefinition.getTasks().addAll(Arrays.asList(workflowTask1, workflowTask2));\n\n        String workflowId = startWorkflow(workflowExecutionName, workflowDefinition);\n        assertNotNull(workflowId);\n\n        Workflow workflow = getWorkflow(workflowId, true);\n        WorkflowDef ephemeralWorkflow = workflow.getWorkflowDefinition();\n        assertNotNull(ephemeralWorkflow);\n        assertEquals(workflowDefinition, ephemeralWorkflow);\n    }\n\n    @Test\n    public void testEphemeralWorkflowsWithEphemeralTasks() {\n        String workflowExecutionName = \"ephemeralWorkflowWithEphemeralTasks\";\n\n        WorkflowDef workflowDefinition = createWorkflowDefinition(workflowExecutionName);\n        WorkflowTask workflowTask1 = createWorkflowTask(\"ephemeralTask1\");\n        TaskDef taskDefinition1 = createTaskDefinition(\"ephemeralTaskDef1\");\n        workflowTask1.setTaskDefinition(taskDefinition1);\n        WorkflowTask workflowTask2 = createWorkflowTask(\"ephemeralTask2\");\n        TaskDef taskDefinition2 = createTaskDefinition(\"ephemeralTaskDef2\");\n        workflowTask2.setTaskDefinition(taskDefinition2);\n        workflowDefinition.getTasks().addAll(Arrays.asList(workflowTask1, workflowTask2));\n\n        String workflowId = startWorkflow(workflowExecutionName, workflowDefinition);\n\n        assertNotNull(workflowId);\n\n        Workflow workflow = getWorkflow(workflowId, true);\n        WorkflowDef ephemeralWorkflow = workflow.getWorkflowDefinition();\n        assertNotNull(ephemeralWorkflow);\n        assertEquals(workflowDefinition, ephemeralWorkflow);\n\n        List<WorkflowTask> ephemeralTasks = ephemeralWorkflow.getTasks();\n        assertEquals(2, ephemeralTasks.size());\n        for (WorkflowTask ephemeralTask : ephemeralTasks) {\n            assertNotNull(ephemeralTask.getTaskDefinition());\n        }\n    }\n\n    @Test\n    public void testEphemeralWorkflowsWithEphemeralAndStoredTasks() {\n        createAndRegisterTaskDefinitions(\"storedTask\", 1);\n\n        WorkflowDef workflowDefinition =\n                createWorkflowDefinition(\"testEphemeralWorkflowsWithEphemeralAndStoredTasks\");\n\n        WorkflowTask workflowTask1 = createWorkflowTask(\"ephemeralTask1\");\n        TaskDef taskDefinition1 = createTaskDefinition(\"ephemeralTaskDef1\");\n        workflowTask1.setTaskDefinition(taskDefinition1);\n\n        WorkflowTask workflowTask2 = createWorkflowTask(\"storedTask0\");\n\n        workflowDefinition.getTasks().add(workflowTask1);\n        workflowDefinition.getTasks().add(workflowTask2);\n\n        String workflowExecutionName = \"ephemeralWorkflowWithEphemeralAndStoredTasks\";\n\n        String workflowId = startWorkflow(workflowExecutionName, workflowDefinition);\n        assertNotNull(workflowId);\n\n        Workflow workflow = getWorkflow(workflowId, true);\n        WorkflowDef ephemeralWorkflow = workflow.getWorkflowDefinition();\n        assertNotNull(ephemeralWorkflow);\n        assertEquals(workflowDefinition, ephemeralWorkflow);\n\n        TaskDef storedTaskDefinition = getTaskDefinition(\"storedTask0\");\n        List<WorkflowTask> tasks = ephemeralWorkflow.getTasks();\n        assertEquals(2, tasks.size());\n        assertEquals(workflowTask1, tasks.get(0));\n        TaskDef currentStoredTaskDefinition = tasks.get(1).getTaskDefinition();\n        assertNotNull(currentStoredTaskDefinition);\n        assertEquals(storedTaskDefinition, currentStoredTaskDefinition);\n    }\n\n    @Test\n    public void testEventHandler() {\n        String eventName = \"conductor:test_workflow:complete_task_with_event\";\n        EventHandler eventHandler = new EventHandler();\n        eventHandler.setName(\"test_complete_task_event\");\n        EventHandler.Action completeTaskAction = new EventHandler.Action();\n        completeTaskAction.setAction(EventHandler.Action.Type.complete_task);\n        completeTaskAction.setComplete_task(new EventHandler.TaskDetails());\n        completeTaskAction.getComplete_task().setTaskRefName(\"test_task\");\n        completeTaskAction.getComplete_task().setWorkflowId(\"test_id\");\n        completeTaskAction.getComplete_task().setOutput(new HashMap<>());\n        eventHandler.getActions().add(completeTaskAction);\n        eventHandler.setEvent(eventName);\n        eventHandler.setActive(true);\n        registerEventHandler(eventHandler);\n\n        Iterator<EventHandler> it = getEventHandlers(eventName, true);\n        EventHandler result = it.next();\n        assertFalse(it.hasNext());\n        assertEquals(eventHandler.getName(), result.getName());\n    }\n\n    protected WorkflowTask createWorkflowTask(String name) {\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(name);\n        workflowTask.setWorkflowTaskType(TaskType.SIMPLE);\n        workflowTask.setTaskReferenceName(name);\n        workflowTask.setDescription(getDefaultDescription(name));\n        workflowTask.setDynamicTaskNameParam(DEFAULT_NULL_VALUE);\n        workflowTask.setCaseValueParam(DEFAULT_NULL_VALUE);\n        workflowTask.setCaseExpression(DEFAULT_NULL_VALUE);\n        workflowTask.setDynamicForkTasksParam(DEFAULT_NULL_VALUE);\n        workflowTask.setDynamicForkTasksInputParamName(DEFAULT_NULL_VALUE);\n        workflowTask.setSink(DEFAULT_NULL_VALUE);\n        workflowTask.setEvaluatorType(DEFAULT_NULL_VALUE);\n        workflowTask.setExpression(DEFAULT_NULL_VALUE);\n        return workflowTask;\n    }\n\n    protected TaskDef createTaskDefinition(String name) {\n        TaskDef taskDefinition = new TaskDef();\n        taskDefinition.setName(name);\n        return taskDefinition;\n    }\n\n    protected WorkflowDef createWorkflowDefinition(String workflowName) {\n        WorkflowDef workflowDefinition = new WorkflowDef();\n        workflowDefinition.setName(workflowName);\n        workflowDefinition.setDescription(getDefaultDescription(workflowName));\n        workflowDefinition.setFailureWorkflow(DEFAULT_NULL_VALUE);\n        workflowDefinition.setOwnerEmail(DEFAULT_EMAIL_ADDRESS);\n        return workflowDefinition;\n    }\n\n    protected List<TaskDef> createAndRegisterTaskDefinitions(\n            String prefixTaskDefinition, int numberOfTaskDefinitions) {\n        String prefix = Optional.ofNullable(prefixTaskDefinition).orElse(TASK_DEFINITION_PREFIX);\n        List<TaskDef> definitions = new LinkedList<>();\n        for (int i = 0; i < numberOfTaskDefinitions; i++) {\n            TaskDef def =\n                    new TaskDef(\n                            prefix + i,\n                            \"task \" + i + DEFAULT_DESCRIPTION,\n                            DEFAULT_EMAIL_ADDRESS,\n                            3,\n                            60,\n                            60);\n            def.setTimeoutPolicy(TaskDef.TimeoutPolicy.RETRY);\n            definitions.add(def);\n        }\n        this.registerTaskDefinitions(definitions);\n        return definitions;\n    }\n\n    private String getDefaultDescription(String nameResource) {\n        return nameResource + \" \" + DEFAULT_DESCRIPTION;\n    }\n\n    protected abstract String startWorkflow(\n            String workflowExecutionName, WorkflowDef workflowDefinition);\n\n    protected abstract Workflow getWorkflow(String workflowId, boolean includeTasks);\n\n    protected abstract TaskDef getTaskDefinition(String taskName);\n\n    protected abstract void registerTaskDefinitions(List<TaskDef> taskDefinitionList);\n\n    protected abstract void registerWorkflowDefinition(WorkflowDef workflowDefinition);\n\n    protected abstract void registerEventHandler(EventHandler eventHandler);\n\n    protected abstract Iterator<EventHandler> getEventHandlers(String event, boolean activeOnly);\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/test/integration/grpc/AbstractGrpcEndToEndTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration.grpc;\n\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.client.grpc.EventClient;\nimport com.netflix.conductor.client.grpc.MetadataClient;\nimport com.netflix.conductor.client.grpc.TaskClient;\nimport com.netflix.conductor.client.grpc.WorkflowClient;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.Task.Status;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef.TimeoutPolicy;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.Workflow.WorkflowStatus;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.test.integration.AbstractEndToEndTest;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\"conductor.grpc-server.enabled=true\", \"conductor.grpc-server.port=8092\"})\n@TestPropertySource(locations = \"classpath:application-integrationtest.properties\")\npublic abstract class AbstractGrpcEndToEndTest extends AbstractEndToEndTest {\n\n    protected static TaskClient taskClient;\n    protected static WorkflowClient workflowClient;\n    protected static MetadataClient metadataClient;\n    protected static EventClient eventClient;\n\n    @Override\n    protected String startWorkflow(String workflowExecutionName, WorkflowDef workflowDefinition) {\n        StartWorkflowRequest workflowRequest =\n                new StartWorkflowRequest()\n                        .withName(workflowExecutionName)\n                        .withWorkflowDef(workflowDefinition);\n        return workflowClient.startWorkflow(workflowRequest);\n    }\n\n    @Override\n    protected Workflow getWorkflow(String workflowId, boolean includeTasks) {\n        return workflowClient.getWorkflow(workflowId, includeTasks);\n    }\n\n    @Override\n    protected TaskDef getTaskDefinition(String taskName) {\n        return metadataClient.getTaskDef(taskName);\n    }\n\n    @Override\n    protected void registerTaskDefinitions(List<TaskDef> taskDefinitionList) {\n        metadataClient.registerTaskDefs(taskDefinitionList);\n    }\n\n    @Override\n    protected void registerWorkflowDefinition(WorkflowDef workflowDefinition) {\n        metadataClient.registerWorkflowDef(workflowDefinition);\n    }\n\n    @Override\n    protected void registerEventHandler(EventHandler eventHandler) {\n        eventClient.registerEventHandler(eventHandler);\n    }\n\n    @Override\n    protected Iterator<EventHandler> getEventHandlers(String event, boolean activeOnly) {\n        return eventClient.getEventHandlers(event, activeOnly);\n    }\n\n    @Test\n    public void testAll() throws Exception {\n        assertNotNull(taskClient);\n        List<TaskDef> defs = new LinkedList<>();\n        for (int i = 0; i < 5; i++) {\n            TaskDef def = new TaskDef(\"t\" + i, \"task \" + i, DEFAULT_EMAIL_ADDRESS, 3, 60, 60);\n            def.setTimeoutPolicy(TimeoutPolicy.RETRY);\n            defs.add(def);\n        }\n        metadataClient.registerTaskDefs(defs);\n\n        for (int i = 0; i < 5; i++) {\n            final String taskName = \"t\" + i;\n            TaskDef def = metadataClient.getTaskDef(taskName);\n            assertNotNull(def);\n            assertEquals(taskName, def.getName());\n        }\n\n        WorkflowDef def = createWorkflowDefinition(\"test\");\n        WorkflowTask t0 = createWorkflowTask(\"t0\");\n        WorkflowTask t1 = createWorkflowTask(\"t1\");\n\n        def.getTasks().add(t0);\n        def.getTasks().add(t1);\n\n        metadataClient.registerWorkflowDef(def);\n        WorkflowDef found = metadataClient.getWorkflowDef(def.getName(), null);\n        assertNotNull(found);\n        assertEquals(def, found);\n\n        String correlationId = \"test_corr_id\";\n        StartWorkflowRequest startWf = new StartWorkflowRequest();\n        startWf.setName(def.getName());\n        startWf.setCorrelationId(correlationId);\n\n        String workflowId = workflowClient.startWorkflow(startWf);\n        assertNotNull(workflowId);\n\n        Workflow workflow = workflowClient.getWorkflow(workflowId, false);\n        assertEquals(0, workflow.getTasks().size());\n        assertEquals(workflowId, workflow.getWorkflowId());\n\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.RUNNING, workflow.getStatus());\n        assertEquals(1, workflow.getTasks().size());\n        assertEquals(t0.getTaskReferenceName(), workflow.getTasks().get(0).getReferenceTaskName());\n        assertEquals(workflowId, workflow.getWorkflowId());\n\n        List<String> runningIds =\n                workflowClient.getRunningWorkflow(def.getName(), def.getVersion());\n        assertNotNull(runningIds);\n        assertEquals(1, runningIds.size());\n        assertEquals(workflowId, runningIds.get(0));\n\n        List<Task> polled =\n                taskClient.batchPollTasksByTaskType(\"non existing task\", \"test\", 1, 100);\n        assertNotNull(polled);\n        assertEquals(0, polled.size());\n\n        polled = taskClient.batchPollTasksByTaskType(t0.getName(), \"test\", 1, 100);\n        assertNotNull(polled);\n        assertEquals(1, polled.size());\n        assertEquals(t0.getName(), polled.get(0).getTaskDefName());\n        Task task = polled.get(0);\n\n        task.getOutputData().put(\"key1\", \"value1\");\n        task.setStatus(Status.COMPLETED);\n        taskClient.updateTask(new TaskResult(task));\n\n        polled = taskClient.batchPollTasksByTaskType(t0.getName(), \"test\", 1, 100);\n        assertNotNull(polled);\n        assertTrue(polled.toString(), polled.isEmpty());\n\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.RUNNING, workflow.getStatus());\n        assertEquals(2, workflow.getTasks().size());\n        assertEquals(t0.getTaskReferenceName(), workflow.getTasks().get(0).getReferenceTaskName());\n        assertEquals(t1.getTaskReferenceName(), workflow.getTasks().get(1).getReferenceTaskName());\n        assertEquals(Status.COMPLETED, workflow.getTasks().get(0).getStatus());\n        assertEquals(Status.SCHEDULED, workflow.getTasks().get(1).getStatus());\n\n        Task taskById = taskClient.getTaskDetails(task.getTaskId());\n        assertNotNull(taskById);\n        assertEquals(task.getTaskId(), taskById.getTaskId());\n\n        Thread.sleep(1000);\n        SearchResult<WorkflowSummary> searchResult =\n                workflowClient.search(\"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResult);\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(workflow.getWorkflowId(), searchResult.getResults().get(0).getWorkflowId());\n\n        SearchResult<Workflow> searchResultV2 =\n                workflowClient.searchV2(\"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResultV2);\n        assertEquals(1, searchResultV2.getTotalHits());\n        assertEquals(workflow.getWorkflowId(), searchResultV2.getResults().get(0).getWorkflowId());\n\n        SearchResult<WorkflowSummary> searchResultAdvanced =\n                workflowClient.search(0, 1, null, null, \"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResultAdvanced);\n        assertEquals(1, searchResultAdvanced.getTotalHits());\n        assertEquals(\n                workflow.getWorkflowId(), searchResultAdvanced.getResults().get(0).getWorkflowId());\n\n        SearchResult<Workflow> searchResultV2Advanced =\n                workflowClient.searchV2(0, 1, null, null, \"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResultV2Advanced);\n        assertEquals(1, searchResultV2Advanced.getTotalHits());\n        assertEquals(\n                workflow.getWorkflowId(),\n                searchResultV2Advanced.getResults().get(0).getWorkflowId());\n\n        SearchResult<TaskSummary> taskSearchResult =\n                taskClient.search(\"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResult);\n        assertEquals(1, searchResultV2Advanced.getTotalHits());\n        assertEquals(t0.getName(), taskSearchResult.getResults().get(0).getTaskDefName());\n\n        SearchResult<TaskSummary> taskSearchResultAdvanced =\n                taskClient.search(0, 1, null, null, \"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResultAdvanced);\n        assertEquals(1, taskSearchResultAdvanced.getTotalHits());\n        assertEquals(t0.getName(), taskSearchResultAdvanced.getResults().get(0).getTaskDefName());\n\n        SearchResult<Task> taskSearchResultV2 =\n                taskClient.searchV2(\"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResultV2);\n        assertEquals(1, searchResultV2Advanced.getTotalHits());\n        assertEquals(\n                t0.getTaskReferenceName(),\n                taskSearchResultV2.getResults().get(0).getReferenceTaskName());\n\n        SearchResult<Task> taskSearchResultV2Advanced =\n                taskClient.searchV2(0, 1, null, null, \"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResultV2Advanced);\n        assertEquals(1, taskSearchResultV2Advanced.getTotalHits());\n        assertEquals(\n                t0.getTaskReferenceName(),\n                taskSearchResultV2Advanced.getResults().get(0).getReferenceTaskName());\n\n        workflowClient.terminateWorkflow(workflowId, \"terminate reason\");\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.TERMINATED, workflow.getStatus());\n\n        workflowClient.restart(workflowId, false);\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.RUNNING, workflow.getStatus());\n        assertEquals(1, workflow.getTasks().size());\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/test/integration/grpc/GrpcEndToEndTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration.grpc;\n\nimport org.junit.Before;\n\nimport com.netflix.conductor.client.grpc.EventClient;\nimport com.netflix.conductor.client.grpc.MetadataClient;\nimport com.netflix.conductor.client.grpc.TaskClient;\nimport com.netflix.conductor.client.grpc.WorkflowClient;\n\npublic class GrpcEndToEndTest extends AbstractGrpcEndToEndTest {\n\n    @Before\n    public void init() {\n        taskClient = new TaskClient(\"localhost\", 8092);\n        workflowClient = new WorkflowClient(\"localhost\", 8092);\n        metadataClient = new MetadataClient(\"localhost\", 8092);\n        eventClient = new EventClient(\"localhost\", 8092);\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/test/integration/http/AbstractHttpEndToEndTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration.http;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport org.springframework.boot.web.server.LocalServerPort;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport com.netflix.conductor.client.exception.ConductorClientException;\nimport com.netflix.conductor.client.http.EventClient;\nimport com.netflix.conductor.client.http.MetadataClient;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.http.WorkflowClient;\nimport com.netflix.conductor.common.metadata.events.EventHandler;\nimport com.netflix.conductor.common.metadata.tasks.Task;\nimport com.netflix.conductor.common.metadata.tasks.Task.Status;\nimport com.netflix.conductor.common.metadata.tasks.TaskDef;\nimport com.netflix.conductor.common.metadata.tasks.TaskResult;\nimport com.netflix.conductor.common.metadata.tasks.TaskType;\nimport com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.SearchResult;\nimport com.netflix.conductor.common.run.TaskSummary;\nimport com.netflix.conductor.common.run.Workflow;\nimport com.netflix.conductor.common.run.Workflow.WorkflowStatus;\nimport com.netflix.conductor.common.run.WorkflowSummary;\nimport com.netflix.conductor.common.validation.ValidationError;\nimport com.netflix.conductor.test.integration.AbstractEndToEndTest;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assert.fail;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)\n@TestPropertySource(locations = \"classpath:application-integrationtest.properties\")\npublic abstract class AbstractHttpEndToEndTest extends AbstractEndToEndTest {\n\n    @LocalServerPort protected int port;\n\n    protected static String apiRoot;\n\n    protected static TaskClient taskClient;\n    protected static WorkflowClient workflowClient;\n    protected static MetadataClient metadataClient;\n    protected static EventClient eventClient;\n\n    @Override\n    protected String startWorkflow(String workflowExecutionName, WorkflowDef workflowDefinition) {\n        StartWorkflowRequest workflowRequest =\n                new StartWorkflowRequest()\n                        .withName(workflowExecutionName)\n                        .withWorkflowDef(workflowDefinition);\n\n        return workflowClient.startWorkflow(workflowRequest);\n    }\n\n    @Override\n    protected Workflow getWorkflow(String workflowId, boolean includeTasks) {\n        return workflowClient.getWorkflow(workflowId, includeTasks);\n    }\n\n    @Override\n    protected TaskDef getTaskDefinition(String taskName) {\n        return metadataClient.getTaskDef(taskName);\n    }\n\n    @Override\n    protected void registerTaskDefinitions(List<TaskDef> taskDefinitionList) {\n        metadataClient.registerTaskDefs(taskDefinitionList);\n    }\n\n    @Override\n    protected void registerWorkflowDefinition(WorkflowDef workflowDefinition) {\n        metadataClient.registerWorkflowDef(workflowDefinition);\n    }\n\n    @Override\n    protected void registerEventHandler(EventHandler eventHandler) {\n        eventClient.registerEventHandler(eventHandler);\n    }\n\n    @Override\n    protected Iterator<EventHandler> getEventHandlers(String event, boolean activeOnly) {\n        return eventClient.getEventHandlers(event, activeOnly).iterator();\n    }\n\n    @Test\n    public void testAll() throws Exception {\n        createAndRegisterTaskDefinitions(\"t\", 5);\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"test\");\n        def.setOwnerEmail(DEFAULT_EMAIL_ADDRESS);\n        WorkflowTask t0 = new WorkflowTask();\n        t0.setName(\"t0\");\n        t0.setWorkflowTaskType(TaskType.SIMPLE);\n        t0.setTaskReferenceName(\"t0\");\n\n        WorkflowTask t1 = new WorkflowTask();\n        t1.setName(\"t1\");\n        t1.setWorkflowTaskType(TaskType.SIMPLE);\n        t1.setTaskReferenceName(\"t1\");\n\n        def.getTasks().add(t0);\n        def.getTasks().add(t1);\n\n        metadataClient.registerWorkflowDef(def);\n        WorkflowDef workflowDefinitionFromSystem =\n                metadataClient.getWorkflowDef(def.getName(), null);\n        assertNotNull(workflowDefinitionFromSystem);\n        assertEquals(def, workflowDefinitionFromSystem);\n\n        String correlationId = \"test_corr_id\";\n        StartWorkflowRequest startWorkflowRequest =\n                new StartWorkflowRequest()\n                        .withName(def.getName())\n                        .withCorrelationId(correlationId)\n                        .withPriority(50)\n                        .withInput(new HashMap<>());\n        String workflowId = workflowClient.startWorkflow(startWorkflowRequest);\n        assertNotNull(workflowId);\n\n        Workflow workflow = workflowClient.getWorkflow(workflowId, false);\n        assertEquals(0, workflow.getTasks().size());\n        assertEquals(workflowId, workflow.getWorkflowId());\n\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.RUNNING, workflow.getStatus());\n        assertEquals(1, workflow.getTasks().size());\n        assertEquals(t0.getTaskReferenceName(), workflow.getTasks().get(0).getReferenceTaskName());\n        assertEquals(workflowId, workflow.getWorkflowId());\n\n        int queueSize = taskClient.getQueueSizeForTask(workflow.getTasks().get(0).getTaskType());\n        assertEquals(1, queueSize);\n\n        List<String> runningIds =\n                workflowClient.getRunningWorkflow(def.getName(), def.getVersion());\n        assertNotNull(runningIds);\n        assertEquals(1, runningIds.size());\n        assertEquals(workflowId, runningIds.get(0));\n\n        List<Task> polled =\n                taskClient.batchPollTasksByTaskType(\"non existing task\", \"test\", 1, 100);\n        assertNotNull(polled);\n        assertEquals(0, polled.size());\n\n        polled = taskClient.batchPollTasksByTaskType(t0.getName(), \"test\", 1, 100);\n        assertNotNull(polled);\n        assertEquals(1, polled.size());\n        assertEquals(t0.getName(), polled.get(0).getTaskDefName());\n        Task task = polled.get(0);\n\n        task.getOutputData().put(\"key1\", \"value1\");\n        task.setStatus(Status.COMPLETED);\n        taskClient.updateTask(new TaskResult(task));\n\n        polled = taskClient.batchPollTasksByTaskType(t0.getName(), \"test\", 1, 100);\n        assertNotNull(polled);\n        assertTrue(polled.toString(), polled.isEmpty());\n\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.RUNNING, workflow.getStatus());\n        assertEquals(2, workflow.getTasks().size());\n        assertEquals(t0.getTaskReferenceName(), workflow.getTasks().get(0).getReferenceTaskName());\n        assertEquals(t1.getTaskReferenceName(), workflow.getTasks().get(1).getReferenceTaskName());\n        assertEquals(Task.Status.COMPLETED, workflow.getTasks().get(0).getStatus());\n        assertEquals(Task.Status.SCHEDULED, workflow.getTasks().get(1).getStatus());\n\n        Task taskById = taskClient.getTaskDetails(task.getTaskId());\n        assertNotNull(taskById);\n        assertEquals(task.getTaskId(), taskById.getTaskId());\n\n        queueSize = taskClient.getQueueSizeForTask(workflow.getTasks().get(1).getTaskType());\n        assertEquals(1, queueSize);\n\n        Thread.sleep(1000);\n        SearchResult<WorkflowSummary> searchResult =\n                workflowClient.search(\"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResult);\n        assertEquals(1, searchResult.getTotalHits());\n        assertEquals(workflow.getWorkflowId(), searchResult.getResults().get(0).getWorkflowId());\n\n        SearchResult<Workflow> searchResultV2 =\n                workflowClient.searchV2(\"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResultV2);\n        assertEquals(1, searchResultV2.getTotalHits());\n        assertEquals(workflow.getWorkflowId(), searchResultV2.getResults().get(0).getWorkflowId());\n\n        SearchResult<WorkflowSummary> searchResultAdvanced =\n                workflowClient.search(0, 1, null, null, \"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResultAdvanced);\n        assertEquals(1, searchResultAdvanced.getTotalHits());\n        assertEquals(\n                workflow.getWorkflowId(), searchResultAdvanced.getResults().get(0).getWorkflowId());\n\n        SearchResult<Workflow> searchResultV2Advanced =\n                workflowClient.searchV2(0, 1, null, null, \"workflowType='\" + def.getName() + \"'\");\n        assertNotNull(searchResultV2Advanced);\n        assertEquals(1, searchResultV2Advanced.getTotalHits());\n        assertEquals(\n                workflow.getWorkflowId(),\n                searchResultV2Advanced.getResults().get(0).getWorkflowId());\n\n        SearchResult<TaskSummary> taskSearchResult =\n                taskClient.search(\"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResult);\n        assertEquals(1, searchResultV2Advanced.getTotalHits());\n        assertEquals(t0.getName(), taskSearchResult.getResults().get(0).getTaskDefName());\n\n        SearchResult<TaskSummary> taskSearchResultAdvanced =\n                taskClient.search(0, 1, null, null, \"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResultAdvanced);\n        assertEquals(1, taskSearchResultAdvanced.getTotalHits());\n        assertEquals(t0.getName(), taskSearchResultAdvanced.getResults().get(0).getTaskDefName());\n\n        SearchResult<Task> taskSearchResultV2 =\n                taskClient.searchV2(\"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResultV2);\n        assertEquals(1, searchResultV2Advanced.getTotalHits());\n        assertEquals(\n                t0.getTaskReferenceName(),\n                taskSearchResultV2.getResults().get(0).getReferenceTaskName());\n\n        SearchResult<Task> taskSearchResultV2Advanced =\n                taskClient.searchV2(0, 1, null, null, \"taskType='\" + t0.getName() + \"'\");\n        assertNotNull(taskSearchResultV2Advanced);\n        assertEquals(1, taskSearchResultV2Advanced.getTotalHits());\n        assertEquals(\n                t0.getTaskReferenceName(),\n                taskSearchResultV2Advanced.getResults().get(0).getReferenceTaskName());\n\n        workflowClient.terminateWorkflow(workflowId, \"terminate reason\");\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.TERMINATED, workflow.getStatus());\n\n        workflowClient.restart(workflowId, false);\n        workflow = workflowClient.getWorkflow(workflowId, true);\n        assertNotNull(workflow);\n        assertEquals(WorkflowStatus.RUNNING, workflow.getStatus());\n        assertEquals(1, workflow.getTasks().size());\n\n        workflowClient.skipTaskFromWorkflow(workflowId, \"t1\");\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testMetadataWorkflowDefinition() {\n        String workflowDefName = \"testWorkflowDefMetadata\";\n        WorkflowDef def = new WorkflowDef();\n        def.setName(workflowDefName);\n        def.setVersion(1);\n        WorkflowTask t0 = new WorkflowTask();\n        t0.setName(\"t0\");\n        t0.setWorkflowTaskType(TaskType.SIMPLE);\n        t0.setTaskReferenceName(\"t0\");\n        WorkflowTask t1 = new WorkflowTask();\n        t1.setName(\"t1\");\n        t1.setWorkflowTaskType(TaskType.SIMPLE);\n        t1.setTaskReferenceName(\"t1\");\n        def.getTasks().add(t0);\n        def.getTasks().add(t1);\n\n        metadataClient.registerWorkflowDef(def);\n        metadataClient.unregisterWorkflowDef(workflowDefName, 1);\n\n        try {\n            metadataClient.getWorkflowDef(workflowDefName, 1);\n        } catch (ConductorClientException e) {\n            int statusCode = e.getStatus();\n            String errorMessage = e.getMessage();\n            boolean retryable = e.isRetryable();\n            assertEquals(404, statusCode);\n            assertEquals(\n                    \"No such workflow found by name: testWorkflowDefMetadata, version: 1\",\n                    errorMessage);\n            assertFalse(retryable);\n            throw e;\n        }\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testInvalidResource() {\n        MetadataClient metadataClient = new MetadataClient();\n        metadataClient.setRootURI(String.format(\"%sinvalid\", apiRoot));\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"testWorkflowDel\");\n        def.setVersion(1);\n        try {\n            metadataClient.registerWorkflowDef(def);\n        } catch (ConductorClientException e) {\n            int statusCode = e.getStatus();\n            boolean retryable = e.isRetryable();\n            assertEquals(404, statusCode);\n            assertFalse(retryable);\n            throw e;\n        }\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testUpdateWorkflow() {\n        TaskDef taskDef = new TaskDef();\n        taskDef.setName(\"taskUpdate\");\n        ArrayList<TaskDef> tasks = new ArrayList<>();\n        tasks.add(taskDef);\n        metadataClient.registerTaskDefs(tasks);\n\n        WorkflowDef def = new WorkflowDef();\n        def.setName(\"testWorkflowDel\");\n        def.setVersion(1);\n        WorkflowTask workflowTask = new WorkflowTask();\n        workflowTask.setName(\"taskUpdate\");\n        workflowTask.setTaskReferenceName(\"taskUpdate\");\n        List<WorkflowTask> workflowTaskList = new ArrayList<>();\n        workflowTaskList.add(workflowTask);\n        def.setTasks(workflowTaskList);\n        List<WorkflowDef> workflowList = new ArrayList<>();\n        workflowList.add(def);\n        metadataClient.registerWorkflowDef(def);\n\n        def.setVersion(2);\n        metadataClient.updateWorkflowDefs(workflowList);\n        WorkflowDef def1 = metadataClient.getWorkflowDef(def.getName(), 2);\n        assertNotNull(def1);\n        try {\n            metadataClient.getTaskDef(\"test\");\n        } catch (ConductorClientException e) {\n            int statuCode = e.getStatus();\n            assertEquals(404, statuCode);\n            assertEquals(\"No such taskType found by name: test\", e.getMessage());\n            assertFalse(e.isRetryable());\n            throw e;\n        }\n    }\n\n    @Test\n    public void testStartWorkflow() {\n        StartWorkflowRequest startWorkflowRequest = new StartWorkflowRequest();\n        try {\n            workflowClient.startWorkflow(startWorkflowRequest);\n            fail(\"StartWorkflow#name is null but NullPointerException was not thrown\");\n        } catch (NullPointerException e) {\n            assertEquals(\"Workflow name cannot be null or empty\", e.getMessage());\n        } catch (Exception e) {\n            fail(\"StartWorkflow#name is null but NullPointerException was not thrown\");\n        }\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testUpdateTask() {\n        TaskResult taskResult = new TaskResult();\n        try {\n            taskClient.updateTask(taskResult);\n        } catch (ConductorClientException e) {\n            assertEquals(400, e.getStatus());\n            assertEquals(\"Validation failed, check below errors for detail.\", e.getMessage());\n            assertFalse(e.isRetryable());\n            List<ValidationError> errors = e.getValidationErrors();\n            List<String> errorMessages =\n                    errors.stream().map(ValidationError::getMessage).collect(Collectors.toList());\n            assertEquals(2, errors.size());\n            assertTrue(errorMessages.contains(\"Workflow Id cannot be null or empty\"));\n            throw e;\n        }\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testGetWorfklowNotFound() {\n        try {\n            workflowClient.getWorkflow(\"w123\", true);\n        } catch (ConductorClientException e) {\n            assertEquals(404, e.getStatus());\n            assertEquals(\"No such workflow found by id: w123\", e.getMessage());\n            assertFalse(e.isRetryable());\n            throw e;\n        }\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testEmptyCreateWorkflowDef() {\n        try {\n            WorkflowDef workflowDef = new WorkflowDef();\n            metadataClient.registerWorkflowDef(workflowDef);\n        } catch (ConductorClientException e) {\n            assertEquals(400, e.getStatus());\n            assertEquals(\"Validation failed, check below errors for detail.\", e.getMessage());\n            assertFalse(e.isRetryable());\n            List<ValidationError> errors = e.getValidationErrors();\n            List<String> errorMessages =\n                    errors.stream().map(ValidationError::getMessage).collect(Collectors.toList());\n            assertTrue(errorMessages.contains(\"WorkflowDef name cannot be null or empty\"));\n            assertTrue(errorMessages.contains(\"WorkflowTask list cannot be empty\"));\n            throw e;\n        }\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testUpdateWorkflowDef() {\n        try {\n            WorkflowDef workflowDef = new WorkflowDef();\n            List<WorkflowDef> workflowDefList = new ArrayList<>();\n            workflowDefList.add(workflowDef);\n            metadataClient.updateWorkflowDefs(workflowDefList);\n        } catch (ConductorClientException e) {\n            assertEquals(400, e.getStatus());\n            assertEquals(\"Validation failed, check below errors for detail.\", e.getMessage());\n            assertFalse(e.isRetryable());\n            List<ValidationError> errors = e.getValidationErrors();\n            List<String> errorMessages =\n                    errors.stream().map(ValidationError::getMessage).collect(Collectors.toList());\n            assertEquals(3, errors.size());\n            assertTrue(errorMessages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(errorMessages.contains(\"WorkflowDef name cannot be null or empty\"));\n            assertTrue(errorMessages.contains(\"ownerEmail cannot be empty\"));\n            throw e;\n        }\n    }\n\n    @Test\n    public void testTaskByTaskId() {\n        try {\n            taskClient.getTaskDetails(\"test999\");\n        } catch (ConductorClientException e) {\n            assertEquals(404, e.getStatus());\n            assertEquals(\"No such task found by taskId: test999\", e.getMessage());\n        }\n    }\n\n    @Test\n    public void testListworkflowsByCorrelationId() {\n        workflowClient.getWorkflows(\"test\", \"test12\", false, false);\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testCreateInvalidWorkflowDef() {\n        try {\n            WorkflowDef workflowDef = new WorkflowDef();\n            List<WorkflowDef> workflowDefList = new ArrayList<>();\n            workflowDefList.add(workflowDef);\n            metadataClient.registerWorkflowDef(workflowDef);\n        } catch (ConductorClientException e) {\n            assertEquals(3, e.getValidationErrors().size());\n            assertEquals(400, e.getStatus());\n            assertEquals(\"Validation failed, check below errors for detail.\", e.getMessage());\n            assertFalse(e.isRetryable());\n            List<ValidationError> errors = e.getValidationErrors();\n            List<String> errorMessages =\n                    errors.stream().map(ValidationError::getMessage).collect(Collectors.toList());\n            assertTrue(errorMessages.contains(\"WorkflowDef name cannot be null or empty\"));\n            assertTrue(errorMessages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(errorMessages.contains(\"ownerEmail cannot be empty\"));\n            throw e;\n        }\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testUpdateTaskDefNameNull() {\n        TaskDef taskDef = new TaskDef();\n        try {\n            metadataClient.updateTaskDef(taskDef);\n        } catch (ConductorClientException e) {\n            assertEquals(2, e.getValidationErrors().size());\n            assertEquals(400, e.getStatus());\n            assertEquals(\"Validation failed, check below errors for detail.\", e.getMessage());\n            assertFalse(e.isRetryable());\n            List<ValidationError> errors = e.getValidationErrors();\n            List<String> errorMessages =\n                    errors.stream().map(ValidationError::getMessage).collect(Collectors.toList());\n            assertTrue(errorMessages.contains(\"TaskDef name cannot be null or empty\"));\n            assertTrue(errorMessages.contains(\"ownerEmail cannot be empty\"));\n            throw e;\n        }\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testGetTaskDefNotExisting() {\n        metadataClient.getTaskDef(\"\");\n    }\n\n    @Test(expected = ConductorClientException.class)\n    public void testUpdateWorkflowDefNameNull() {\n        WorkflowDef workflowDef = new WorkflowDef();\n        List<WorkflowDef> list = new ArrayList<>();\n        list.add(workflowDef);\n        try {\n            metadataClient.updateWorkflowDefs(list);\n        } catch (ConductorClientException e) {\n            assertEquals(3, e.getValidationErrors().size());\n            assertEquals(400, e.getStatus());\n            assertEquals(\"Validation failed, check below errors for detail.\", e.getMessage());\n            assertFalse(e.isRetryable());\n            List<ValidationError> errors = e.getValidationErrors();\n            List<String> errorMessages =\n                    errors.stream().map(ValidationError::getMessage).collect(Collectors.toList());\n            assertTrue(errorMessages.contains(\"WorkflowDef name cannot be null or empty\"));\n            assertTrue(errorMessages.contains(\"WorkflowTask list cannot be empty\"));\n            assertTrue(errorMessages.contains(\"ownerEmail cannot be empty\"));\n            throw e;\n        }\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/test/integration/http/HttpEndToEndTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.integration.http;\n\nimport org.junit.Before;\n\nimport com.netflix.conductor.client.http.EventClient;\nimport com.netflix.conductor.client.http.MetadataClient;\nimport com.netflix.conductor.client.http.TaskClient;\nimport com.netflix.conductor.client.http.WorkflowClient;\n\npublic class HttpEndToEndTest extends AbstractHttpEndToEndTest {\n\n    @Before\n    public void init() {\n        apiRoot = String.format(\"http://localhost:%d/api/\", port);\n\n        taskClient = new TaskClient();\n        taskClient.setRootURI(apiRoot);\n\n        workflowClient = new WorkflowClient();\n        workflowClient.setRootURI(apiRoot);\n\n        metadataClient = new MetadataClient();\n        metadataClient.setRootURI(apiRoot);\n\n        eventClient = new EventClient();\n        eventClient.setRootURI(apiRoot);\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/test/utils/MockExternalPayloadStorage.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.utils;\n\nimport java.io.*;\nimport java.nio.file.Files;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\nimport org.apache.commons.io.IOUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.common.metadata.workflow.SubWorkflowParams;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowDef;\nimport com.netflix.conductor.common.metadata.workflow.WorkflowTask;\nimport com.netflix.conductor.common.run.ExternalStorageLocation;\nimport com.netflix.conductor.common.utils.ExternalPayloadStorage;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SIMPLE;\nimport static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_SUB_WORKFLOW;\n\n/** A {@link ExternalPayloadStorage} implementation that stores payload in file. */\n@ConditionalOnProperty(name = \"conductor.external-payload-storage.type\", havingValue = \"mock\")\n@Component\npublic class MockExternalPayloadStorage implements ExternalPayloadStorage {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(MockExternalPayloadStorage.class);\n\n    private final ObjectMapper objectMapper;\n    private final File payloadDir;\n\n    @Autowired\n    public MockExternalPayloadStorage(ObjectMapper objectMapper) throws IOException {\n        this.objectMapper = objectMapper;\n        this.payloadDir = Files.createTempDirectory(\"payloads\").toFile();\n        LOGGER.info(\n                \"{} initialized in directory: {}\",\n                this.getClass().getSimpleName(),\n                payloadDir.getAbsolutePath());\n    }\n\n    @Override\n    public ExternalStorageLocation getLocation(\n            Operation operation, PayloadType payloadType, String path) {\n        ExternalStorageLocation location = new ExternalStorageLocation();\n        location.setPath(UUID.randomUUID() + \".json\");\n        return location;\n    }\n\n    @Override\n    public void upload(String path, InputStream payload, long payloadSize) {\n        File file = new File(payloadDir, path);\n        String filePath = file.getAbsolutePath();\n        try {\n            if (!file.exists() && file.createNewFile()) {\n                LOGGER.debug(\"Created file: {}\", filePath);\n            }\n            IOUtils.copy(payload, new FileOutputStream(file));\n            LOGGER.debug(\"Written to {}\", filePath);\n        } catch (IOException e) {\n            // just handle this exception here and return empty map so that test will fail in case\n            // this exception is thrown\n            LOGGER.error(\"Error writing to {}\", filePath);\n        } finally {\n            try {\n                if (payload != null) {\n                    payload.close();\n                }\n            } catch (IOException e) {\n                LOGGER.warn(\"Unable to close input stream when writing to file\");\n            }\n        }\n    }\n\n    @Override\n    public InputStream download(String path) {\n        try {\n            LOGGER.debug(\"Reading from {}\", path);\n            return new FileInputStream(new File(payloadDir, path));\n        } catch (IOException e) {\n            LOGGER.error(\"Error reading {}\", path, e);\n            return null;\n        }\n    }\n\n    public void upload(String path, Map<String, Object> payload) {\n        try {\n            InputStream bais = new ByteArrayInputStream(objectMapper.writeValueAsBytes(payload));\n            upload(path, bais, 0);\n        } catch (IOException e) {\n            LOGGER.error(\"Error serializing map to json\", e);\n        }\n    }\n\n    public InputStream readOutputDotJson() {\n        return MockExternalPayloadStorage.class.getResourceAsStream(\"/output.json\");\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public Map<String, Object> curateDynamicForkLargePayload() {\n        Map<String, Object> dynamicForkLargePayload = new HashMap<>();\n        try {\n            InputStream inputStream = readOutputDotJson();\n            Map<String, Object> largePayload = objectMapper.readValue(inputStream, Map.class);\n\n            WorkflowTask simpleWorkflowTask = new WorkflowTask();\n            simpleWorkflowTask.setName(\"integration_task_10\");\n            simpleWorkflowTask.setTaskReferenceName(\"t10\");\n            simpleWorkflowTask.setType(TASK_TYPE_SIMPLE);\n            simpleWorkflowTask.setInputParameters(\n                    Collections.singletonMap(\"p1\", \"${workflow.input.imageType}\"));\n\n            WorkflowDef subWorkflowDef = new WorkflowDef();\n            subWorkflowDef.setName(\"one_task_workflow\");\n            subWorkflowDef.setVersion(1);\n            subWorkflowDef.setTasks(Collections.singletonList(simpleWorkflowTask));\n\n            SubWorkflowParams subWorkflowParams = new SubWorkflowParams();\n            subWorkflowParams.setName(\"one_task_workflow\");\n            subWorkflowParams.setVersion(1);\n            subWorkflowParams.setWorkflowDef(subWorkflowDef);\n\n            WorkflowTask subWorkflowTask = new WorkflowTask();\n            subWorkflowTask.setName(\"large_payload_subworkflow\");\n            subWorkflowTask.setType(TASK_TYPE_SUB_WORKFLOW);\n            subWorkflowTask.setTaskReferenceName(\"large_payload_subworkflow\");\n            subWorkflowTask.setInputParameters(largePayload);\n            subWorkflowTask.setSubWorkflowParam(subWorkflowParams);\n\n            dynamicForkLargePayload.put(\"dynamicTasks\", List.of(subWorkflowTask));\n            dynamicForkLargePayload.put(\n                    \"dynamicTasksInput\", Map.of(\"large_payload_subworkflow\", largePayload));\n        } catch (IOException e) {\n            // just handle this exception here and return empty map so that test will fail in case\n            // this exception is thrown\n        }\n        return dynamicForkLargePayload;\n    }\n\n    public Map<String, Object> downloadPayload(String path) {\n        InputStream inputStream = download(path);\n        if (inputStream != null) {\n            try {\n                Map<String, Object> largePayload = objectMapper.readValue(inputStream, Map.class);\n                return largePayload;\n            } catch (IOException e) {\n                LOGGER.error(\"Error in downloading payload for path {}\", path, e);\n            }\n        }\n        return new HashMap<>();\n    }\n\n    public Map<String, Object> createLargePayload(int repeat) {\n        Map<String, Object> largePayload = new HashMap<>();\n        try {\n            InputStream inputStream = readOutputDotJson();\n            Map<String, Object> payload = objectMapper.readValue(inputStream, Map.class);\n            for (int i = 0; i < repeat; i++) {\n                largePayload.put(String.valueOf(i), payload);\n            }\n        } catch (IOException e) {\n            // just handle this exception here and return empty map so that test will fail in case\n            // this exception is thrown\n        }\n        return largePayload;\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/java/com/netflix/conductor/test/utils/UserTask.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.conductor.test.utils;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport com.netflix.conductor.core.execution.WorkflowExecutor;\nimport com.netflix.conductor.core.execution.tasks.WorkflowSystemTask;\nimport com.netflix.conductor.model.TaskModel;\nimport com.netflix.conductor.model.WorkflowModel;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.util.concurrent.Uninterruptibles;\n\n@Component(UserTask.NAME)\npublic class UserTask extends WorkflowSystemTask {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(UserTask.class);\n\n    public static final String NAME = \"USER_TASK\";\n\n    private final ObjectMapper objectMapper;\n\n    private static final TypeReference<Map<String, Map<String, List<Object>>>>\n            mapStringListObjects = new TypeReference<>() {};\n\n    @Autowired\n    public UserTask(ObjectMapper objectMapper) {\n        super(NAME);\n        this.objectMapper = objectMapper;\n        LOGGER.info(\"Initialized system task - {}\", getClass().getCanonicalName());\n    }\n\n    @Override\n    public void start(WorkflowModel workflow, TaskModel task, WorkflowExecutor executor) {\n        Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);\n\n        if (task.getWorkflowTask().isAsyncComplete()) {\n            task.setStatus(TaskModel.Status.IN_PROGRESS);\n        } else {\n            Map<String, Map<String, List<Object>>> map =\n                    objectMapper.convertValue(task.getInputData(), mapStringListObjects);\n            Map<String, Object> output = new HashMap<>();\n            Map<String, List<Object>> defaultLargeInput = new HashMap<>();\n            defaultLargeInput.put(\"TEST_SAMPLE\", Collections.singletonList(\"testDefault\"));\n            output.put(\n                    \"size\",\n                    map.getOrDefault(\"largeInput\", defaultLargeInput).get(\"TEST_SAMPLE\").size());\n            task.setOutputData(output);\n            task.setStatus(TaskModel.Status.COMPLETED);\n        }\n    }\n\n    @Override\n    public boolean isAsync() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/application-integrationtest.properties",
    "content": "#\n# /*\n#  * Copyright 2021 Netflix, Inc.\n#  * <p>\n#  * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n#  * the License. You may obtain a copy of the License at\n#  * <p>\n#  * http://www.apache.org/licenses/LICENSE-2.0\n#  * <p>\n#  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n#  * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n#  * specific language governing permissions and limitations under the License.\n#  */\n#\n\nconductor.db.type=memory\nconductor.workflow-execution-lock.type=local_only\nconductor.external-payload-storage.type=mock\nconductor.indexing.enabled=false\n\nconductor.app.stack=test\nconductor.app.appId=conductor\n\nconductor.app.workflow-offset-timeout=30s\n\nconductor.system-task-workers.enabled=false\nconductor.app.system-task-worker-callback-duration=0\n\nconductor.app.event-message-indexing-enabled=true\nconductor.app.event-execution-indexing-enabled=true\n\nconductor.workflow-reconciler.enabled=true\nconductor.workflow-repair-service.enabled=false\n\nconductor.app.workflow-execution-lock-enabled=false\n\nconductor.app.workflow-input-payload-size-threshold=10KB\nconductor.app.max-workflow-input-payload-size-threshold=10240KB\nconductor.app.workflow-output-payload-size-threshold=10KB\nconductor.app.max-workflow-output-payload-size-threshold=10240KB\nconductor.app.task-input-payload-size-threshold=10KB\nconductor.app.max-task-input-payload-size-threshold=10240KB\nconductor.app.task-output-payload-size-threshold=10KB\nconductor.app.max-task-output-payload-size-threshold=10240KB\nconductor.app.max-workflow-variables-payload-size-threshold=2KB\n\nconductor.redis.availability-zone=us-east-1c\nconductor.redis.data-center-region=us-east-1\nconductor.redis.workflow-namespace-prefix=integration-test\nconductor.redis.queue-namespace-prefix=integtest\n\nconductor.elasticsearch.index-prefix=conductor\nconductor.elasticsearch.cluster-health-color=yellow\n\nmanagement.metrics.export.datadog.enabled=false\n"
  },
  {
    "path": "test-harness/src/test/resources/concurrency_limited_task_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_concurrency_limits_workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"test_task_with_concurrency_limit\",\n      \"taskReferenceName\": \"test_task_with_concurrency_limit\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/conditional_switch_task_workflow_integration_test.json",
    "content": "{\n  \"name\": \"ConditionalTaskWF\",\n  \"description\": \"ConditionalTaskWF\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"conditional\",\n      \"taskReferenceName\": \"conditional\",\n      \"inputParameters\": {\n        \"case\": \"${workflow.input.param1}\"\n      },\n      \"type\": \"SWITCH\",\n      \"evaluatorType\": \"value-param\",\n      \"expression\": \"case\",\n      \"decisionCases\": {\n        \"nested\": [\n          {\n            \"name\": \"nestedCondition\",\n            \"taskReferenceName\": \"nestedCondition\",\n            \"inputParameters\": {\n              \"case\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SWITCH\",\n            \"evaluatorType\": \"value-param\",\n            \"expression\": \"case\",\n            \"decisionCases\": {\n              \"one\": [\n                {\n                  \"name\": \"integration_task_1\",\n                  \"taskReferenceName\": \"t1\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ],\n              \"two\": [\n                {\n                  \"name\": \"integration_task_2\",\n                  \"taskReferenceName\": \"t2\",\n                  \"inputParameters\": {\n                    \"tp1\": \"${workflow.input.param1}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ]\n            },\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"three\": [\n          {\n            \"name\": \"integration_task_3\",\n            \"taskReferenceName\": \"t3\",\n            \"inputParameters\": {\n              \"tp3\": \"workflow.input.param2\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [\n        {\n          \"name\": \"integration_task_10\",\n          \"taskReferenceName\": \"t10\",\n          \"inputParameters\": {\n            \"tp10\": \"workflow.input.param2\"\n          },\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"finalcondition\",\n      \"taskReferenceName\": \"finalCase\",\n      \"inputParameters\": {\n        \"finalCase\": \"${workflow.input.finalCase}\"\n      },\n      \"type\": \"SWITCH\",\n      \"evaluatorType\": \"value-param\",\n      \"expression\": \"finalCase\",\n      \"decisionCases\": {\n        \"notify\": [\n          {\n            \"name\": \"integration_task_4\",\n            \"taskReferenceName\": \"integration_task_4\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/conditional_system_task_workflow_integration_test.json",
    "content": "{\n  \"name\": \"ConditionalSystemWorkflow\",\n  \"description\": \"ConditionalSystemWorkflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"tp11\": \"${workflow.input.param1}\",\n        \"tp12\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"decision\",\n      \"taskReferenceName\": \"decision\",\n      \"inputParameters\": {\n        \"case\": \"${t1.output.case}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"case\",\n      \"decisionCases\": {\n        \"one\": [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"tp21\": \"${workflow.input.param1}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"two\": [\n          {\n            \"name\": \"user_task\",\n            \"taskReferenceName\": \"user_task\",\n            \"inputParameters\": {\n              \"largeInput\": \"${t1.output.op}\"\n            },\n            \"type\": \"USER_TASK\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_3\",\n      \"taskReferenceName\": \"t3\",\n      \"inputParameters\": {\n        \"tp31\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o2\": \"${t1.output.op}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/conditional_task_workflow_integration_test.json",
    "content": "{\n  \"name\": \"ConditionalTaskWF\",\n  \"description\": \"ConditionalTaskWF\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"conditional\",\n      \"taskReferenceName\": \"conditional\",\n      \"inputParameters\": {\n        \"case\": \"${workflow.input.param1}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"case\",\n      \"decisionCases\": {\n        \"nested\": [\n          {\n            \"name\": \"nestedCondition\",\n            \"taskReferenceName\": \"nestedCondition\",\n            \"inputParameters\": {\n              \"case\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"DECISION\",\n            \"caseValueParam\": \"case\",\n            \"decisionCases\": {\n              \"one\": [\n                {\n                  \"name\": \"integration_task_1\",\n                  \"taskReferenceName\": \"t1\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ],\n              \"two\": [\n                {\n                  \"name\": \"integration_task_2\",\n                  \"taskReferenceName\": \"t2\",\n                  \"inputParameters\": {\n                    \"tp1\": \"${workflow.input.param1}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ]\n            },\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"three\": [\n          {\n            \"name\": \"integration_task_3\",\n            \"taskReferenceName\": \"t3\",\n            \"inputParameters\": {\n              \"tp3\": \"workflow.input.param2\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [\n        {\n          \"name\": \"integration_task_10\",\n          \"taskReferenceName\": \"t10\",\n          \"inputParameters\": {\n            \"tp10\": \"workflow.input.param2\"\n          },\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"finalcondition\",\n      \"taskReferenceName\": \"finalCase\",\n      \"inputParameters\": {\n        \"finalCase\": \"${workflow.input.finalCase}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"finalCase\",\n      \"decisionCases\": {\n        \"notify\": [\n          {\n            \"name\": \"integration_task_4\",\n            \"taskReferenceName\": \"integration_task_4\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/decision_and_fork_join_integration_test.json",
    "content": "{\n  \"name\": \"ForkConditionalTest\",\n  \"description\": \"ForkConditionalTest\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"forkTask\",\n      \"taskReferenceName\": \"forkTask\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"decisionTask\",\n            \"taskReferenceName\": \"decisionTask\",\n            \"inputParameters\": {\n              \"case\": \"${workflow.input.case}\"\n            },\n            \"type\": \"DECISION\",\n            \"caseValueParam\": \"case\",\n            \"decisionCases\": {\n              \"c\": [\n                {\n                  \"name\": \"integration_task_1\",\n                  \"taskReferenceName\": \"t1\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"integration_task_2\",\n                  \"taskReferenceName\": \"t2\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ]\n            },\n            \"defaultCase\": [\n              {\n                \"name\": \"integration_task_5\",\n                \"taskReferenceName\": \"t5\",\n                \"inputParameters\": {\n                  \"p1\": \"${workflow.input.param1}\",\n                  \"p2\": \"${workflow.input.param2}\"\n                },\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"integration_task_20\",\n            \"taskReferenceName\": \"t20\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_10\",\n            \"taskReferenceName\": \"t10\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"joinTask\",\n      \"taskReferenceName\": \"joinTask\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"t20\",\n        \"t10\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/decision_and_terminate_integration_test.json",
    "content": "{\n  \"name\": \"ConditionalTerminateWorkflow\",\n  \"description\": \"ConditionalTerminateWorkflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"tp11\": \"${workflow.input.param1}\",\n        \"tp12\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"decision\",\n      \"taskReferenceName\": \"decision\",\n      \"inputParameters\": {\n        \"case\": \"${workflow.input.case}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"case\",\n      \"decisionCases\": {\n        \"one\": [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"tp21\": \"${workflow.input.param1}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"two\": [\n          {\n            \"name\": \"terminate\",\n            \"taskReferenceName\": \"terminate0\",\n            \"inputParameters\": {\n              \"terminationStatus\": \"FAILED\",\n              \"workflowOutput\": \"${t1.output.op}\"\n            },\n            \"type\": \"TERMINATE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_3\",\n      \"taskReferenceName\": \"t3\",\n      \"inputParameters\": {\n        \"tp31\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o2\": \"${t3.output.op}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/do_while_as_subtask_integration_test.json",
    "content": "{\n  \"name\": \"Do_While_SubTask\",\n  \"description\": \"Do_While_SubTask\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"fork\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"loopTask\",\n            \"taskReferenceName\": \"loopTask\",\n            \"inputParameters\": {\n              \"value\": \"${workflow.input.loop}\"\n            },\n            \"type\": \"DO_WHILE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopCondition\": \"if ($.loopTask['iteration'] < $.value) { true; } else { false;} \",\n            \"loopOver\": [\n              {\n                \"name\": \"integration_task_0\",\n                \"taskReferenceName\": \"integration_task_0\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              },\n              {\n                \"name\": \"integration_task_1\",\n                \"taskReferenceName\": \"integration_task_1\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ]\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"integration_task_2\",\n            \"inputParameters\": {},\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"loopTask\",\n        \"integration_task_2\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/do_while_five_loop_over_integration_test.json",
    "content": "{\n  \"name\": \"do_while_five_loop_over_integration_test\",\n  \"description\": \"do_while with a mix of 5, simple and system tasks\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"loopTask\",\n      \"taskReferenceName\": \"loopTask\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.input.loop}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.loopTask['iteration'] < $.value ) { true;} else {false;} \",\n      \"loopOver\": [\n        {\n          \"name\": \"LAMBDA_TASK\",\n          \"taskReferenceName\": \"lambda_locs\",\n          \"inputParameters\": {\n            \"scriptExpression\": \"return {locationRanId: 'some location id'}\"\n          },\n          \"type\": \"LAMBDA\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"jq_add_location\",\n          \"taskReferenceName\": \"jq_add_location\",\n          \"inputParameters\": {\n            \"locationIdValue\": \"${lambda_locs.output.result.locationRanId}\",\n            \"queryExpression\": \"{ out: ({ \\\"locationId\\\": .locationIdValue }) }\"\n          },\n          \"type\": \"JSON_JQ_TRANSFORM\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"integration_task_1\",\n          \"taskReferenceName\": \"integration_task_1\",\n          \"inputParameters\": {},\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"jq_create_hydrus_input\",\n          \"taskReferenceName\": \"jq_create_hydrus_input\",\n          \"inputParameters\": {\n            \"locationIdValue\": \"${lambda_locs.output.result.locationRanId}\",\n            \"queryExpression\": \"{ out: ({ \\\"locationId\\\": .locationIdValue }) }\"\n          },\n          \"type\": \"JSON_JQ_TRANSFORM\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"integration_task_2\",\n          \"taskReferenceName\": \"integration_task_2\",\n          \"inputParameters\": {},\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ]\n    },\n    {\n      \"name\": \"integration_task_3\",\n      \"taskReferenceName\": \"integration_task_3\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/do_while_integration_test.json",
    "content": "{\n  \"name\": \"Do_While_Workflow\",\n  \"description\": \"Do_While_Workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"loopTask\",\n      \"taskReferenceName\": \"loopTask\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.input.loop}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.loopTask['iteration'] < $.value) { true; } else { false;} \",\n      \"loopOver\": [\n        {\n          \"name\": \"integration_task_0\",\n          \"taskReferenceName\": \"integration_task_0\",\n          \"inputParameters\": {},\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"fork\",\n          \"taskReferenceName\": \"fork\",\n          \"inputParameters\": {},\n          \"type\": \"FORK_JOIN\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [\n            [\n              {\n                \"name\": \"integration_task_1\",\n                \"taskReferenceName\": \"integration_task_1\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ],\n            [\n              {\n                \"name\": \"integration_task_2\",\n                \"taskReferenceName\": \"integration_task_2\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ]\n          ],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"join\",\n          \"taskReferenceName\": \"join\",\n          \"inputParameters\": {},\n          \"type\": \"JOIN\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [\n            \"integration_task_1\",\n            \"integration_task_2\"\n          ],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ]\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/do_while_iteration_fix_test.json",
    "content": "{\n  \"name\": \"Do_While_Workflow_Iteration_Fix\",\n  \"description\": \"Do_While_Workflow_Iteration_Fix\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"loopTask\",\n      \"taskReferenceName\": \"loopTask\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.input.loop}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.loopTask['iteration'] < $.value) { true; } else { false;} \",\n      \"loopOver\": [\n        {\n          \"name\": \"form_uri\",\n          \"taskReferenceName\": \"form_uri\",\n          \"inputParameters\": {\n            \"index\" : \"${loopTask['iteration']}\",\n            \"scriptExpression\": \"return $.index - 1;\"\n          },\n          \"type\": \"LAMBDA\"\n        }\n      ]\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/do_while_multiple_integration_test.json",
    "content": "{\n  \"name\": \"Do_While_Multiple\",\n  \"description\": \"Do_While_Multiple\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"loopTask\",\n      \"taskReferenceName\": \"loopTask\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.input.loop}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.loopTask['iteration'] < $.value ) { true;} else {false;} \",\n      \"loopOver\": [\n        {\n          \"name\": \"integration_task_0\",\n          \"taskReferenceName\": \"integration_task_0\",\n          \"inputParameters\": {},\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"fork\",\n          \"taskReferenceName\": \"fork\",\n          \"inputParameters\": {},\n          \"type\": \"FORK_JOIN\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [\n            [\n              {\n                \"name\": \"integration_task_1\",\n                \"taskReferenceName\": \"integration_task_1\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ],\n            [\n              {\n                \"name\": \"integration_task_2\",\n                \"taskReferenceName\": \"integration_task_2\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ]\n          ],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"join\",\n          \"taskReferenceName\": \"join\",\n          \"inputParameters\": {},\n          \"type\": \"JOIN\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [\n            \"integration_task_1\",\n            \"integration_task_2\"\n          ],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ]\n    },\n    {\n      \"name\": \"loopTask2\",\n      \"taskReferenceName\": \"loopTask2\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.input.loop2}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.loopTask2['iteration'] < $.value) { true; } else { false; }\",\n      \"loopOver\": [\n        {\n          \"name\": \"integration_task_3\",\n          \"taskReferenceName\": \"integration_task_3\",\n          \"inputParameters\": {},\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ]\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/do_while_set_variable_fix.json",
    "content": "{\n  \"name\": \"do_while_Set_variable_fix\",\n  \"description\": \"do_while with set variable task fix\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"loopTask\",\n      \"taskReferenceName\": \"loopTask\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.variables.value}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.value > 0) { true; } else { false; } \",\n      \"loopOver\": [\n        {\n          \"name\": \"set_variable\",\n          \"taskReferenceName\": \"set_variable\",\n          \"inputParameters\": {\n            \"value\": \"0\"\n          },\n          \"type\": \"SET_VARIABLE\",\n          \"startDelay\": 0,\n          \"optional\": false,\n          \"asyncComplete\": false\n        }\n      ]\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/do_while_sub_workflow_integration_test.json",
    "content": "{\n  \"name\": \"Do_While_Sub_Workflow\",\n  \"description\": \"Do_While_Sub_Workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"loopTask\",\n      \"taskReferenceName\": \"loopTask\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.input.loop}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.loopTask['iteration'] < $.value) { true; } else { false;} \",\n      \"loopOver\": [\n        {\n          \"name\": \"integration_task_0\",\n          \"taskReferenceName\": \"integration_task_0\",\n          \"inputParameters\": {},\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"fork\",\n          \"taskReferenceName\": \"fork\",\n          \"inputParameters\": {},\n          \"type\": \"FORK_JOIN\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [\n            [\n              {\n                \"name\": \"integration_task_1\",\n                \"taskReferenceName\": \"integration_task_1\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ],\n            [\n              {\n                \"name\": \"integration_task_2\",\n                \"taskReferenceName\": \"integration_task_2\",\n                \"inputParameters\": {},\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ]\n          ],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"join\",\n          \"taskReferenceName\": \"join\",\n          \"inputParameters\": {},\n          \"type\": \"JOIN\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [\n            \"integration_task_1\",\n            \"integration_task_2\"\n          ],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"sub_workflow_task\",\n          \"taskReferenceName\": \"st1\",\n          \"inputParameters\": {},\n          \"type\": \"SUB_WORKFLOW\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"subWorkflowParam\": {\n            \"name\": \"sub_workflow\"\n          },\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ]\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/do_while_system_tasks.json",
    "content": "{\n  \"name\": \"do_while_system_tasks\",\n  \"description\": \"do_while with a mix of 5, simple and system tasks\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"loopTask\",\n      \"taskReferenceName\": \"loopTask\",\n      \"inputParameters\": {\n        \"value\": \"${workflow.input.loop}\"\n      },\n      \"type\": \"DO_WHILE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopCondition\": \"if ($.loopTask['iteration'] < $.value ) { true;} else {false;} \",\n      \"loopOver\": [\n        {\n          \"name\": \"LAMBDA_TASK\",\n          \"taskReferenceName\": \"lambda_locs\",\n          \"inputParameters\": {\n            \"scriptExpression\": \"return {locationRanId: 'some location id'}\"\n          },\n          \"type\": \"LAMBDA\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"jq_add_location\",\n          \"taskReferenceName\": \"jq_add_location\",\n          \"inputParameters\": {\n            \"locationIdValue\": \"${lambda_locs.output.result.locationRanId}\",\n            \"queryExpression\": \"{ out: ({ \\\"locationId\\\": .locationIdValue }) }\"\n          },\n          \"type\": \"JSON_JQ_TRANSFORM\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        },\n        {\n          \"name\": \"jq_create_hydrus_input\",\n          \"taskReferenceName\": \"jq_create_hydrus_input\",\n          \"inputParameters\": {\n            \"locationIdValue\": \"${lambda_locs.output.result.locationRanId}\",\n            \"queryExpression\": \"{ out: ({ \\\"locationId\\\": .locationIdValue }) }\"\n          },\n          \"type\": \"JSON_JQ_TRANSFORM\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ]\n    },\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"integration_task_1\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/do_while_with_decision_task.json",
    "content": "{\n  \"name\": \"DO_While_with_Decision_task\",\n  \"description\": \"Program for testing loop behaviour\",\n  \"version\": 1,\n  \"schemaVersion\": 2,\n  \"ownerEmail\": \"xyz@company.eu\",\n  \"tasks\": [\n    {\n      \"name\": \"LoopTask\",\n      \"taskReferenceName\": \"LoopTask\",\n      \"type\": \"DO_WHILE\",\n      \"inputParameters\": {\n        \"list\": \"${workflow.input.list}\"\n      },\n      \"loopCondition\": \"$.LoopTask['iteration'] < $.list.length\",\n      \"loopOver\": [\n        {\n          \"name\": \"GetNumberAtIndex\",\n          \"taskReferenceName\": \"GetNumberAtIndex\",\n          \"type\": \"INLINE\",\n          \"inputParameters\": {\n            \"evaluatorType\": \"javascript\",\n            \"list\": \"${workflow.input.list}\",\n            \"iterator\": \"${LoopTask.output.iteration}\",\n            \"expression\": \"function getElement() { return $.list.get($.iterator - 1); } getElement();\"\n          }\n        },\n        {\n          \"name\": \"SwitchTask\",\n          \"taskReferenceName\": \"SwitchTask\",\n          \"type\": \"SWITCH\",\n          \"evaluatorType\": \"javascript\",\n          \"inputParameters\": {\n            \"param\": \"${GetNumberAtIndex.output.result}\"\n          },\n          \"expression\": \"$.param > 0\",\n          \"decisionCases\": {\n            \"true\": [\n              {\n                \"name\": \"WaitTask\",\n                \"taskReferenceName\": \"WaitTask\",\n                \"type\": \"WAIT\",\n                \"inputParameters\": {\n                }\n              },\n              {\n                \"name\": \"ComputeNumber\",\n                \"taskReferenceName\": \"ComputeNumber\",\n                \"type\": \"INLINE\",\n                \"inputParameters\": {\n                  \"evaluatorType\": \"javascript\",\n                  \"number\": \"${GetNumberAtIndex.output.result.number}\",\n                  \"expression\": \"function compute() { return $.number+10; } compute();\"\n                }\n              }\n            ]\n          }\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "test-harness/src/test/resources/dynamic_fork_join_integration_test.json",
    "content": "{\n  \"name\": \"DynamicFanInOutTest\",\n  \"description\": \"DynamicFanInOutTest\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"dt1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"taskDefinition\": {\n        \"createdBy\": \"integration_app\",\n        \"name\": \"integration_task_1\",\n        \"description\": \"integration_task_1\",\n        \"retryCount\": 1,\n        \"timeoutSeconds\": 120,\n        \"inputKeys\": [],\n        \"outputKeys\": [],\n        \"timeoutPolicy\": \"TIME_OUT_WF\",\n        \"retryLogic\": \"FIXED\",\n        \"retryDelaySeconds\": 60,\n        \"responseTimeoutSeconds\": 3600,\n        \"inputTemplate\": {},\n        \"rateLimitPerFrequency\": 0,\n        \"rateLimitFrequencyInSeconds\": 1\n      },\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"dynamicfanouttask\",\n      \"inputParameters\": {\n        \"dynamicTasks\": \"${dt1.output.dynamicTasks}\",\n        \"dynamicTasksInput\": \"${dt1.output.dynamicTasksInput}\"\n      },\n      \"type\": \"FORK_JOIN_DYNAMIC\",\n      \"decisionCases\": {},\n      \"dynamicForkTasksParam\": \"dynamicTasks\",\n      \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"dynamicfanouttask_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_4\",\n      \"taskReferenceName\": \"task4\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"taskDefinition\": {\n        \"createdBy\": \"integration_app\",\n        \"name\": \"integration_task_4\",\n        \"description\": \"integration_task_4\",\n        \"retryCount\": 1,\n        \"timeoutSeconds\": 120,\n        \"inputKeys\": [],\n        \"outputKeys\": [],\n        \"timeoutPolicy\": \"TIME_OUT_WF\",\n        \"retryLogic\": \"FIXED\",\n        \"retryDelaySeconds\": 60,\n        \"responseTimeoutSeconds\": 3600,\n        \"inputTemplate\": {},\n        \"rateLimitPerFrequency\": 0,\n        \"rateLimitFrequencyInSeconds\": 1\n      },\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/event_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_event_workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"eventX\",\n      \"taskReferenceName\": \"wait0\",\n      \"inputParameters\": {},\n      \"type\": \"EVENT\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"sink\": \"conductor\",\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/exclusive_join_integration_test.json",
    "content": "{\n  \"name\": \"ExclusiveJoinTestWorkflow\",\n  \"description\": \"Exclusive Join Test Workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"task1\",\n      \"inputParameters\": {\n        \"payload\": \"${workflow.input.payload}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"optional\": false\n    },\n    {\n      \"name\": \"decide_task\",\n      \"taskReferenceName\": \"decision1\",\n      \"inputParameters\": {\n        \"decision_1\": \"${workflow.input.decision_1}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"decision_1\",\n      \"decisionCases\": {\n        \"true\": [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"task2\",\n            \"inputParameters\": {\n              \"payload\": \"${task1.output.payload}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"optional\": false\n          },\n          {\n            \"name\": \"decide_task\",\n            \"taskReferenceName\": \"decision2\",\n            \"inputParameters\": {\n              \"decision_2\": \"${workflow.input.decision_2}\"\n            },\n            \"type\": \"DECISION\",\n            \"caseValueParam\": \"decision_2\",\n            \"decisionCases\": {\n              \"true\": [\n                {\n                  \"name\": \"integration_task_3\",\n                  \"taskReferenceName\": \"task3\",\n                  \"inputParameters\": {\n                    \"payload\": \"${task2.output.payload}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false\n                }\n              ]\n            }\n          }\n        ],\n        \"false\": [\n          {\n            \"name\": \"integration_task_4\",\n            \"taskReferenceName\": \"task4\",\n            \"inputParameters\": {\n              \"payload\": \"${task1.output.payload}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"startDelay\": 0,\n            \"optional\": false\n          },\n          {\n            \"name\": \"decide_task\",\n            \"taskReferenceName\": \"decision3\",\n            \"inputParameters\": {\n              \"decision_3\": \"${workflow.input.decision_3}\"\n            },\n            \"type\": \"DECISION\",\n            \"caseValueParam\": \"decision_3\",\n            \"decisionCases\": {\n              \"true\": [\n                {\n                  \"name\": \"integration_task_5\",\n                  \"taskReferenceName\": \"task5\",\n                  \"inputParameters\": {\n                    \"payload\": \"${task4.output.payload}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false\n                }\n              ]\n            }\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"exclusive_join\",\n      \"taskReferenceName\": \"exclusiveJoin\",\n      \"type\": \"EXCLUSIVE_JOIN\",\n      \"joinOn\": [\n        \"task3\",\n        \"task5\"\n      ],\n      \"defaultExclusiveJoinTask\": [\n        \"task2\",\n        \"task4\",\n        \"task1\"\n      ]\n    }\n  ],\n  \"schemaVersion\": 2,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/failure_workflow_for_terminate_task_workflow.json",
    "content": "{\n  \"name\": \"failure_workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"lambda\",\n      \"taskReferenceName\": \"lambda0\",\n      \"inputParameters\": {\n        \"input\": \"${workflow.input}\",\n        \"scriptExpression\": \"if ($.input.a==1){return {testvalue: true}} else{return {testvalue: false}}\"\n      },\n      \"type\": \"LAMBDA\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/fork_join_integration_test.json",
    "content": "{\n  \"name\": \"FanInOutTest\",\n  \"description\": \"FanInOutTest\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"fanouttask\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"integration_task_1\",\n            \"taskReferenceName\": \"t1\",\n            \"inputParameters\": {\n              \"p1\": \"workflow.input.param1\",\n              \"p2\": \"workflow.input.param2\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"integration_task_3\",\n            \"taskReferenceName\": \"t3\",\n            \"inputParameters\": {\n              \"p1\": \"workflow.input.param1\",\n              \"p2\": \"workflow.input.param2\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"tp1\": \"workflow.input.param1\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"fanouttask_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"t3\",\n        \"t2\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_4\",\n      \"taskReferenceName\": \"t4\",\n      \"inputParameters\": {\n        \"tp1\": \"workflow.input.param1\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/fork_join_sub_workflow.json",
    "content": "{\n  \"name\": \"integration_test_fork_join_sw\",\n  \"description\": \"integration_test_fork_join_sw\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"fanouttask\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"sub_workflow_task\",\n            \"taskReferenceName\": \"st1\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_workflow\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"fanouttask_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"st1\",\n        \"t2\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/fork_join_with_no_task_retry_integration_test.json",
    "content": "{\n  \"name\": \"FanInOutTest_2\",\n  \"description\": \"FanInOutTest_2\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"fanouttask\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"integration_task_0_RT_1\",\n            \"taskReferenceName\": \"t1\",\n            \"inputParameters\": {\n              \"p1\": \"workflow.input.param1\",\n              \"p2\": \"workflow.input.param2\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"integration_task_0_RT_3\",\n            \"taskReferenceName\": \"t3\",\n            \"inputParameters\": {\n              \"p1\": \"workflow.input.param1\",\n              \"p2\": \"workflow.input.param2\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_0_RT_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"tp1\": \"workflow.input.param1\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"fanouttask_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"t3\",\n        \"t2\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_0_RT_4\",\n      \"taskReferenceName\": \"t4\",\n      \"inputParameters\": {\n        \"tp1\": \"workflow.input.param1\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/fork_join_with_optional_sub_workflow_forks_integration_test.json",
    "content": "{\n  \"name\": \"integration_test_fork_join_optional_sw\",\n  \"description\": \"integration_test_fork_join_optional_sw\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"fanouttask\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"st1\",\n            \"taskReferenceName\": \"st1\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_workflow\"\n            },\n            \"joinOn\": [],\n            \"optional\": true,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"st2\",\n            \"taskReferenceName\": \"st2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"sub_workflow\"\n            },\n            \"joinOn\": [],\n            \"optional\": true,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"fanouttask_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"st1\",\n        \"st2\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/hierarchical_fork_join_swf.json",
    "content": "{\n  \"name\": \"hierarchical_fork_join_swf\",\n  \"description\": \"hierarchical_fork_join_swf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork\",\n      \"taskReferenceName\": \"fanouttask\",\n      \"inputParameters\": {\n        \"param1\": \"${workflow.input.param1}\",\n        \"param2\": \"${workflow.input.param2}\",\n        \"subwf\": \"${workflow.input.nextSubwf}\"\n      },\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"sub_workflow_task\",\n            \"taskReferenceName\": \"st1\",\n            \"inputParameters\": {\n              \"param1\": \"${workflow.input.param1}\",\n              \"param2\": \"${workflow.input.param2}\",\n              \"subwf\": \"${workflow.input.nextSubwf}\"\n            },\n            \"type\": \"SUB_WORKFLOW\",\n            \"subWorkflowParam\": {\n              \"name\": \"${workflow.input.subwf}\",\n              \"version\": 1\n            },\n            \"retryCount\": 0\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"retryCount\": 0\n          }\n        ]\n      ]\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"fanouttask_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"joinOn\": [\n        \"st1\",\n        \"t2\"\n      ]\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\",\n    \"subwf\"\n  ],\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/input.json",
    "content": ""
  },
  {
    "path": "test-harness/src/test/resources/json_jq_transform_result_integration_test.json",
    "content": "{\n  \"name\": \"json_jq_transform_result_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"json_jq_1\",\n      \"taskReferenceName\": \"json_jq_1\",\n      \"description\": \"json_jq_1\",\n      \"inputParameters\": {\n        \"data\": [],\n        \"queryExpression\": \"if(.data | length >0) then \\\"EXISTS\\\" else \\\"CREATE\\\" end\"\n      },\n      \"type\": \"JSON_JQ_TRANSFORM\",\n      \"startDelay\": 0,\n      \"optional\": false,\n      \"asyncComplete\": false\n    },\n    {\n      \"name\": \"decide_1\",\n      \"taskReferenceName\": \"decide_1\",\n      \"inputParameters\": {\n        \"outcome\": \"${json_jq_1.output.result}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"outcome\",\n      \"decisionCases\": {\n        \"CREATE\": [\n          {\n            \"name\": \"json_jq_2\",\n            \"taskReferenceName\": \"json_jq_2\",\n            \"description\": \"json_jq_2\",\n            \"inputParameters\": {\n              \"inputData\": {\n                \"request\": {\n                  \"transitions\": [\n                    {\n                      \"name\": \"redeliver\"\n                    },\n                    {\n                      \"name\": \"redeliver_from_validation_error\"\n                    },\n                    {\n                      \"name\": \"redelivery\"\n                    }\n                  ]\n                }\n              },\n              \"queryExpression\": \".inputData.request.transitions | map(.name)\"\n            },\n            \"type\": \"JSON_JQ_TRANSFORM\",\n            \"startDelay\": 0,\n            \"optional\": false,\n            \"asyncComplete\": false\n          },\n          {\n            \"name\": \"decide_2\",\n            \"taskReferenceName\": \"decide_2\",\n            \"inputParameters\": {\n              \"requestedAction\": \"${workflow.input.requestedAction}\",\n              \"availableActions\": \"${json_jq_2.output.result}\"\n            },\n            \"type\": \"DECISION\",\n            \"caseExpression\": \"if ($.availableActions.indexOf($.requestedAction) >= 0) { \\\"true\\\" } else { \\\"false\\\" }\",\n            \"decisionCases\": {\n              \"false\": [\n                {\n                  \"name\": \"get_population_data\",\n                  \"taskReferenceName\": \"get_population_data\",\n                  \"inputParameters\": {\n                    \"http_request\": {\n                      \"uri\": \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n                      \"method\": \"GET\"\n                    }\n                  },\n                  \"type\": \"HTTP\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"asyncComplete\": false\n                }\n              ]\n            }\n          }\n        ]\n      }\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/nested_fork_join_integration_test.json",
    "content": "{\n  \"name\": \"FanInOutNestedTest\",\n  \"description\": \"FanInOutNestedTest\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork1\",\n      \"taskReferenceName\": \"fork1\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"integration_task_11\",\n            \"taskReferenceName\": \"t11\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\",\n              \"case\": \"${workflow.input.case}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"fork2\",\n            \"taskReferenceName\": \"fork2\",\n            \"inputParameters\": {},\n            \"type\": \"FORK_JOIN\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [\n              [\n                {\n                  \"name\": \"integration_task_12\",\n                  \"taskReferenceName\": \"t12\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"integration_task_14\",\n                  \"taskReferenceName\": \"t14\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ],\n              [\n                {\n                  \"name\": \"integration_task_13\",\n                  \"taskReferenceName\": \"t13\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"Decision\",\n                  \"taskReferenceName\": \"d1\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"DECISION\",\n                  \"caseValueParam\": \"case\",\n                  \"decisionCases\": {\n                    \"a\": [\n                      {\n                        \"name\": \"integration_task_16\",\n                        \"taskReferenceName\": \"t16\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      },\n                      {\n                        \"name\": \"integration_task_19\",\n                        \"taskReferenceName\": \"t19\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      },\n                      {\n                        \"name\": \"integration_task_20\",\n                        \"taskReferenceName\": \"t20\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      }\n                    ],\n                    \"b\": [\n                      {\n                        \"name\": \"integration_task_17\",\n                        \"taskReferenceName\": \"t17\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      },\n                      {\n                        \"name\": \"integration_task_20\",\n                        \"taskReferenceName\": \"t20b\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      }\n                    ]\n                  },\n                  \"defaultCase\": [\n                    {\n                      \"name\": \"integration_task_18\",\n                      \"taskReferenceName\": \"t18\",\n                      \"inputParameters\": {\n                        \"p1\": \"${workflow.input.param1}\",\n                        \"p2\": \"${workflow.input.param2}\",\n                        \"case\": \"${workflow.input.case}\"\n                      },\n                      \"type\": \"SIMPLE\",\n                      \"decisionCases\": {},\n                      \"defaultCase\": [],\n                      \"forkTasks\": [],\n                      \"startDelay\": 0,\n                      \"joinOn\": [],\n                      \"optional\": false,\n                      \"defaultExclusiveJoinTask\": [],\n                      \"asyncComplete\": false,\n                      \"loopOver\": []\n                    },\n                    {\n                      \"name\": \"integration_task_20\",\n                      \"taskReferenceName\": \"t20def\",\n                      \"inputParameters\": {\n                        \"p1\": \"${workflow.input.param1}\",\n                        \"p2\": \"${workflow.input.param2}\",\n                        \"case\": \"${workflow.input.case}\"\n                      },\n                      \"type\": \"SIMPLE\",\n                      \"decisionCases\": {},\n                      \"defaultCase\": [],\n                      \"forkTasks\": [],\n                      \"startDelay\": 0,\n                      \"joinOn\": [],\n                      \"optional\": false,\n                      \"defaultExclusiveJoinTask\": [],\n                      \"asyncComplete\": false,\n                      \"loopOver\": []\n                    }\n                  ],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ]\n            ],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"join2\",\n            \"taskReferenceName\": \"join2\",\n            \"inputParameters\": {},\n            \"type\": \"JOIN\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [\n              \"t14\",\n              \"t20\"\n            ],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join1\",\n      \"taskReferenceName\": \"join1\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"t11\",\n        \"join2\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_15\",\n      \"taskReferenceName\": \"t15\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\",\n        \"case\": \"${workflow.input.case}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/nested_fork_join_swf.json",
    "content": "{\n  \"name\": \"nested_fork_join_swf\",\n  \"description\": \"nested_fork_join_swf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"outer_fork\",\n      \"taskReferenceName\": \"outer_fork\",\n      \"inputParameters\": {\n        \"param1\": \"${workflow.input.param1}\",\n        \"param2\": \"${workflow.input.param2}\",\n        \"subwf\": \"${workflow.input.nextSubwf}\"\n      },\n      \"type\": \"FORK_JOIN\",\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"inner_fork\",\n            \"taskReferenceName\": \"inner_fork\",\n            \"inputParameters\": {\n              \"param1\": \"${workflow.input.param1}\",\n              \"param2\": \"${workflow.input.param2}\",\n              \"subwf\": \"${workflow.input.nextSubwf}\"\n            },\n            \"type\": \"FORK_JOIN\",\n            \"forkTasks\": [\n              [\n                {\n                  \"name\": \"sub_workflow_task\",\n                  \"taskReferenceName\": \"st1\",\n                  \"inputParameters\": {\n                    \"param1\": \"${workflow.input.param1}\",\n                    \"param2\": \"${workflow.input.param2}\",\n                    \"subwf\": \"${workflow.input.nextSubwf}\"\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"subWorkflowParam\": {\n                    \"name\": \"${workflow.input.subwf}\",\n                    \"version\": 1\n                  },\n                  \"retryCount\": 0\n                }\n              ],\n              [\n                {\n                  \"name\": \"integration_task_2\",\n                  \"taskReferenceName\": \"t2\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"retryCount\": 0\n                }\n              ]\n            ]\n          },\n          {\n            \"name\": \"inner_join\",\n            \"taskReferenceName\": \"inner_join\",\n            \"type\": \"JOIN\",\n            \"joinOn\": [\n              \"st1\",\n              \"t2\"\n            ]\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t3\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"retryCount\": 0\n          }\n        ]\n      ]\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"outer_join\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"joinOn\": [\n        \"inner_join\",\n        \"t3\"\n      ]\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\",\n    \"subwf\"\n  ],\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/nested_fork_join_with_sub_workflow_integration_test.json",
    "content": "{\n  \"name\": \"FanInOutNestedSubWorkflowTest\",\n  \"description\": \"FanInOutNestedSubWorkflowTest\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"fork1\",\n      \"taskReferenceName\": \"fork1\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"integration_task_11\",\n            \"taskReferenceName\": \"t11\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\",\n              \"case\": \"${workflow.input.case}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"fork2\",\n            \"taskReferenceName\": \"fork2\",\n            \"inputParameters\": {},\n            \"type\": \"FORK_JOIN\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [\n              [\n                {\n                  \"name\": \"integration_task_12\",\n                  \"taskReferenceName\": \"t12\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"integration_task_14\",\n                  \"taskReferenceName\": \"t14\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ],\n              [\n                {\n                  \"name\": \"integration_task_13\",\n                  \"taskReferenceName\": \"t13\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"Decision\",\n                  \"taskReferenceName\": \"d1\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\",\n                    \"case\": \"${workflow.input.case}\"\n                  },\n                  \"type\": \"DECISION\",\n                  \"caseValueParam\": \"case\",\n                  \"decisionCases\": {\n                    \"a\": [\n                      {\n                        \"name\": \"integration_task_16\",\n                        \"taskReferenceName\": \"t16\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      },\n                      {\n                        \"name\": \"integration_task_19\",\n                        \"taskReferenceName\": \"t19\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      },\n                      {\n                        \"name\": \"integration_task_20\",\n                        \"taskReferenceName\": \"t20\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      }\n                    ],\n                    \"b\": [\n                      {\n                        \"name\": \"integration_task_17\",\n                        \"taskReferenceName\": \"t17\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      },\n                      {\n                        \"name\": \"integration_task_20\",\n                        \"taskReferenceName\": \"t20b\",\n                        \"inputParameters\": {\n                          \"p1\": \"${workflow.input.param1}\",\n                          \"p2\": \"${workflow.input.param2}\",\n                          \"case\": \"${workflow.input.case}\"\n                        },\n                        \"type\": \"SIMPLE\",\n                        \"decisionCases\": {},\n                        \"defaultCase\": [],\n                        \"forkTasks\": [],\n                        \"startDelay\": 0,\n                        \"joinOn\": [],\n                        \"optional\": false,\n                        \"defaultExclusiveJoinTask\": [],\n                        \"asyncComplete\": false,\n                        \"loopOver\": []\n                      }\n                    ]\n                  },\n                  \"defaultCase\": [\n                    {\n                      \"name\": \"integration_task_18\",\n                      \"taskReferenceName\": \"t18\",\n                      \"inputParameters\": {\n                        \"p1\": \"${workflow.input.param1}\",\n                        \"p2\": \"${workflow.input.param2}\",\n                        \"case\": \"${workflow.input.case}\"\n                      },\n                      \"type\": \"SIMPLE\",\n                      \"decisionCases\": {},\n                      \"defaultCase\": [],\n                      \"forkTasks\": [],\n                      \"startDelay\": 0,\n                      \"joinOn\": [],\n                      \"optional\": false,\n                      \"defaultExclusiveJoinTask\": [],\n                      \"asyncComplete\": false,\n                      \"loopOver\": []\n                    },\n                    {\n                      \"name\": \"integration_task_20\",\n                      \"taskReferenceName\": \"t20def\",\n                      \"inputParameters\": {\n                        \"p1\": \"${workflow.input.param1}\",\n                        \"p2\": \"${workflow.input.param2}\",\n                        \"case\": \"${workflow.input.case}\"\n                      },\n                      \"type\": \"SIMPLE\",\n                      \"decisionCases\": {},\n                      \"defaultCase\": [],\n                      \"forkTasks\": [],\n                      \"startDelay\": 0,\n                      \"joinOn\": [],\n                      \"optional\": false,\n                      \"defaultExclusiveJoinTask\": [],\n                      \"asyncComplete\": false,\n                      \"loopOver\": []\n                    }\n                  ],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ]\n            ],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"join2\",\n            \"taskReferenceName\": \"join2\",\n            \"inputParameters\": {},\n            \"type\": \"JOIN\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [\n              \"t14\",\n              \"t20\"\n            ],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"sw1\",\n            \"taskReferenceName\": \"sw1\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"integration_test_wf\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"join1\",\n      \"taskReferenceName\": \"join1\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"t11\",\n        \"join2\",\n        \"sw1\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_15\",\n      \"taskReferenceName\": \"t15\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\",\n        \"case\": \"${workflow.input.case}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/output.json",
    "content": "{\n  \"imageType\": \"TEST_SAMPLE\",\n  \"case\": \"two\",\n  \"op\": {\n    \"TEST_SAMPLE\": [\n      {\n        \"sourceId\": \"1413900_10830\",\n        \"url\": \"file/location/a0bdc4d0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_50241\",\n        \"url\": \"file/location/cd4e00a0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-55ee8663-85c2-42d3-aca2-4076707e6d4e\",\n        \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n      },\n      {\n        \"sourceId\": \"generated-14056154-1544-4350-81db-b3751fe44777\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-0b0ae5ea-d5c5-410c-adc9-bf16d2909c2e\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-08869779-614d-417c-bfea-36a3f8f199da\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-e117db45-1c48-45d0-b751-89386eb2d81d\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f0221421-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/4a009209-002f-4b58-8b96-cb2198f8ba3c\"\n      },\n      {\n        \"sourceId\": \"f0252161-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/55b56298-5e7a-4949-b919-88c5c9557e8e\"\n      },\n      {\n        \"sourceId\": \"f038d070-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/3c4804f4-e826-436f-90c9-52b8d9266d52\"\n      },\n      {\n        \"sourceId\": \"f04e0621-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/689283a1-1816-48ef-83da-7f9ac874bf45\"\n      },\n      {\n        \"sourceId\": \"f04ddf10-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/586666ae-7321-445a-80b6-323c8c241ecd\"\n      },\n      {\n        \"sourceId\": \"f05950c0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/31795cc4-2590-4b20-a617-deaa18301f99\"\n      },\n      {\n        \"sourceId\": \"1413900_46819\",\n        \"url\": \"file/location/c74497a0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_11177\",\n        \"url\": \"file/location/a231c730-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_48713\",\n        \"url\": \"file/location/ca638ae0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_48525\",\n        \"url\": \"file/location/ca0c9140-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_73303\",\n        \"url\": \"file/location/d5943a40-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"1413900_55202\",\n        \"url\": \"file/location/d1a4d7a0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-61413adf-3c10-4484-b25d-e238df898f45\",\n        \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n      },\n      {\n        \"sourceId\": \"generated-addca397-f050-4339-ae86-9ba8c4e1b0d5\",\n        \"url\": \"file/sample/location/838a0ddb-a315-453a-8b8a-fa795f9d7691\"\n      },\n      {\n        \"sourceId\": \"generated-e4de9810-0f69-4593-8926-01ed82cbebcb\",\n        \"url\": \"file/sample/location/838a0ddb-a315-453a-8b8a-fa795f9d7691\"\n      },\n      {\n        \"sourceId\": \"generated-e16e2074-7af6-4700-ab05-ca41ba9c9ab4\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-341c86f8-57a5-40e1-8842-3eb41dd9f528\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-88c2ea9b-cef7-4120-8043-b92713d8fade\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-3f6a731f-3c92-4677-9923-f80b8a6be632\",\n        \"url\": \"file/sample/location/3881aea9-a731-4e22-9ead-2d6eccc51140\"\n      },\n      {\n        \"sourceId\": \"generated-1508b871-64de-47ce-8b07-76c5cb3f3e1e\",\n        \"url\": \"file/sample/location/a2e4195f-3900-45b4-9335-45f85fca6467\"\n      },\n      {\n        \"sourceId\": \"generated-1406dce8-7b9c-4956-a7e8-78721c476ce9\",\n        \"url\": \"file/sample/location/a2e4195f-3900-45b4-9335-45f85fca6467\"\n      },\n      {\n        \"sourceId\": \"f0206671-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/35ebee36-3072-44c5-abb5-702a5a3b1a91\"\n      },\n      {\n        \"sourceId\": \"f01f5501-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d3a9133d-c681-4910-a769-8195526ae634\"\n      },\n      {\n        \"sourceId\": \"f022b060-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/8fc1413d-170e-4644-a554-5e0c596b225c\"\n      },\n      {\n        \"sourceId\": \"f02fa8b1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/35bed0a2-7def-457b-bded-4f4d7d94f76e\"\n      },\n      {\n        \"sourceId\": \"f031f2a0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/a5a2ea1f-8d13-429c-a44d-3057d21f608a\"\n      },\n      {\n        \"sourceId\": \"f0424650-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/1c599ffc-4f10-4c0b-8d9a-ae41c7256113\"\n      },\n      {\n        \"sourceId\": \"f04ec970-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/8404a421-e1a6-41cf-af63-a35ccb474457\"\n      },\n      {\n        \"sourceId\": \"1413900_47197\",\n        \"url\": \"file/location/c81b6fa0-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-2a63c0c8-62ea-44a4-a33b-f0b3047e8b00\",\n        \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n      },\n      {\n        \"sourceId\": \"generated-b27face7-3589-4209-944a-5153b20c5996\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-144675b3-9321-48d2-8b5b-e19a40d30ef2\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-8cbe821e-b1fb-48ce-beb5-735319af4db6\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-ecc4ea47-9bad-4b91-97c7-35f4ea6fb479\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-c1eb9ed0-8560-4e09-a748-f926edb7cdc2\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-6bed81fd-c777-4c61-8da1-0bb7f7cf0082\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-852e5510-dd5d-4900-a614-854148fcc716\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-f4dedcb7-37c9-4ba9-ab37-64ec9be7c882\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f0259691-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/721bc0de-e75f-4386-8b2e-ca84eb653596\"\n      },\n      {\n        \"sourceId\": \"f02b3be1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d2043b17-8ce5-42ee-a5e4-81c68f0c4838\"\n      },\n      {\n        \"sourceId\": \"f02b62f0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/63931561-3b5b-4ffe-af47-da2c9de94684\"\n      },\n      {\n        \"sourceId\": \"f0315660-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d99ed629-2885-4e4a-8a1b-22e487b875fa\"\n      },\n      {\n        \"sourceId\": \"f0306c00-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/6f8e673a-7003-44aa-96b9-e2ed8a4654ff\"\n      },\n      {\n        \"sourceId\": \"f033c760-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/627c00f9-14b3-4057-b6e2-0f962ad0308e\"\n      },\n      {\n        \"sourceId\": \"f03526f1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/fafabaf9-fe58-4a9a-b555-026521aeb2fe\"\n      },\n      {\n        \"sourceId\": \"f03acc41-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/6c9fed2c-558a-4db3-8360-659b5e8c46e4\"\n      },\n      {\n        \"sourceId\": \"f0463df1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/e9fb83d2-5f14-4442-92b5-67e613f2e35f\"\n      },\n      {\n        \"sourceId\": \"f04fb3d0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/e7a0f82f-be8d-4ada-a4b1-13e8165e08be\"\n      },\n      {\n        \"sourceId\": \"f05272f0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/9aba488a-22b3-4932-85a7-52c461203541\"\n      },\n      {\n        \"sourceId\": \"f0581841-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/457415f6-6d0c-4304-8533-0d5b43fac564\"\n      },\n      {\n        \"sourceId\": \"generated-8fefb48c-6fde-4fd6-8f33-a1f3f3b62105\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-30c61aa5-f5bd-4077-8c32-336b87acbe96\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-d5da37db-d486-46d4-8f7d-1e0710a77eb5\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-77af26fe-9e22-48af-99e3-f63f10fbe6de\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-2e807016-3d11-4b60-bec7-c380a608b67d\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-615d02e9-62c2-43ab-9df7-753b6b8e2c22\",\n        \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n      },\n      {\n        \"sourceId\": \"generated-3e1600fd-a626-4ee6-972b-5f0187e96c38\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"generated-1dcb208c-6a58-4334-a60c-6fb54c8a2af5\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f024ac30-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/0af2107b-4231-4d23-bef3-4e417ac6c5d3\"\n      },\n      {\n        \"sourceId\": \"f0282ea1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/0f592681-fd23-4194-ae43-42f61c664485\"\n      },\n      {\n        \"sourceId\": \"f02c4d50-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/ec46b9a3-99af-410a-af7d-726f8854909f\"\n      },\n      {\n        \"sourceId\": \"f02b8a00-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/aed7e5da-b524-4d41-b264-28ce615ec826\"\n      },\n      {\n        \"sourceId\": \"f02b14d1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/b88c9055-ab0d-4d27-a405-265ba2a15f0c\"\n      },\n      {\n        \"sourceId\": \"f03044f1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/fb8c4df9-d59e-4ac3-880e-4ea94cd880a4\"\n      },\n      {\n        \"sourceId\": \"f034ffe1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/59f3fbe8-b300-4861-9b2f-dac7b15aea7d\"\n      },\n      {\n        \"sourceId\": \"f03c2bd0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/19a06d54-41ed-419d-9947-f10cd5f0d85c\"\n      },\n      {\n        \"sourceId\": \"f03fae41-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/a9a48a62-7d62-4f67-b281-cc6fdc1e722c\"\n      },\n      {\n        \"sourceId\": \"f0455390-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/0aeffc0a-a5ad-46ff-abab-1b3bc6a5840a\"\n      },\n      {\n        \"sourceId\": \"f04b1ff1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/9a08aaed-c125-48f7-9d1d-fd11266c2b12\"\n      },\n      {\n        \"sourceId\": \"f04cf4b1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/17a6e0f9-aa64-411f-9af7-837c84f7443f\"\n      },\n      {\n        \"sourceId\": \"f0511360-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/fb633c73-cb33-4806-bc08-049024644856\"\n      },\n      {\n        \"sourceId\": \"f0538460-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/a7012248-6769-42da-a6c8-d4b831f6efce\"\n      },\n      {\n        \"sourceId\": \"f058db91-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/bcf71522-6168-48c4-86c9-995bca60ae51\"\n      },\n      {\n        \"sourceId\": \"generated-adf005c4-95c1-4904-9968-09cc19a26bfe\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-c4d367a4-4cdc-412e-af79-09b227f2e3ba\",\n        \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n      },\n      {\n        \"sourceId\": \"generated-48dba018-f884-49db-b87e-67274e244c8f\",\n        \"url\": \"file/sample/location/4bce4154-fb4b-4f0a-887d-a0cd12d4d214\"\n      },\n      {\n        \"sourceId\": \"generated-26700b83-4892-420e-8b46-1ee21eba75fb\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"generated-632f3198-c0dc-4348-974f-51684d4e443e\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"generated-86e2dd1d-1aa4-4dbe-b37b-b488f5dd1c70\",\n        \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n      },\n      {\n        \"sourceId\": \"f04134e0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/ff8f59bf-7757-4d51-a7e4-619f3e8ffaf2\"\n      },\n      {\n        \"sourceId\": \"f04f65b0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/d66467d1-3ac6-4041-8d15-e722ee07231f\"\n      },\n      {\n        \"sourceId\": \"1413900_15255\",\n        \"url\": \"file/location/a9e20260-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-e953493b-cbe3-4319-885e-00c82089c76c\",\n        \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n      },\n      {\n        \"sourceId\": \"generated-65c54676-3adb-4ef0-b65e-8e2a49533cbf\",\n        \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n      },\n      {\n        \"sourceId\": \"f02ac6b0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/21568877-07a5-411f-9715-5e92806c4448\"\n      },\n      {\n        \"sourceId\": \"f02fcfc1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/f3b1f1a2-48d3-475d-a607-2e5a1fe532e7\"\n      },\n      {\n        \"sourceId\": \"f03526f0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/84a40c66-d925-4a4a-ba62-8491d26e29e9\"\n      },\n      {\n        \"sourceId\": \"f03e75c1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/e84c00e8-a148-46cf-9a0b-431c4c2aeb08\"\n      },\n      {\n        \"sourceId\": \"f0429471-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/178de9fa-7cc8-457a-8fb6-5c080e6163ea\"\n      },\n      {\n        \"sourceId\": \"f047eba0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/18d153aa-e13b-4264-ae03-f3da75eb425b\"\n      },\n      {\n        \"sourceId\": \"f04fdae0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/7c843e53-8d87-47cf-bca5-1a02e7f5e33f\"\n      },\n      {\n        \"sourceId\": \"f0553210-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/26bacd65-9082-4d83-9506-90e5f1ccd16a\"\n      },\n      {\n        \"sourceId\": \"1413900_84904\",\n        \"url\": \"file/location/d8f7b090-5315-11e8-bf88-0efd527701fc\"\n      },\n      {\n        \"sourceId\": \"generated-84adc784-8d7d-4088-ba51-16fde57fbc21\",\n        \"url\": \"file/sample/location/3881aea9-a731-4e22-9ead-2d6eccc51140\"\n      },\n      {\n        \"sourceId\": \"generated-9e49c58b-0b33-4daf-a39a-8fc91e302328\",\n        \"url\": \"file/sample/location/4bce4154-fb4b-4f0a-887d-a0cd12d4d214\"\n      },\n      {\n        \"sourceId\": \"f02dd3f1-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/8937b328-8f0d-4762-8d1f-7d7bc80c3d2e\"\n      },\n      {\n        \"sourceId\": \"f03240c0-86e8-11e8-af77-0a2ba4eae3ec\",\n        \"url\": \"file/test/location/aab6e386-4d59-4b40-b257-9aed12a45446\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "test-harness/src/test/resources/rate_limited_simple_task_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_rate_limit_simple_task_workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"test_simple_task_with_rateLimits\",\n      \"taskReferenceName\": \"test_simple_task_with_rateLimits\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/rate_limited_system_task_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_rate_limit_system_task_workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"test_task_with_rateLimits\",\n      \"taskReferenceName\": \"test_task_with_rateLimits\",\n      \"inputParameters\": {},\n      \"type\": \"USER_TASK\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/sequential_json_jq_transform_integration_test.json",
    "content": "{\n  \"name\": \"sequential_json_jq_transform_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"default_variables\",\n      \"taskReferenceName\": \"default_variables\",\n      \"description\": \"default_variables\",\n      \"inputParameters\": {\n        \"input\": \"${workflow.input}\",\n        \"queryExpression\": \"{ requestTransform: .input.requestTransform // \\\".body\\\"  , responseTransform: .input.responseTransform // \\\".response.body\\\", method: .input.method // \\\"GET\\\", document: .input.document // \\\"rgt_results\\\", successExpression: .input.successExpression // \\\"true\\\"   }\"\n      },\n      \"type\": \"JSON_JQ_TRANSFORM\",\n      \"startDelay\": 0,\n      \"optional\": false,\n      \"asyncComplete\": false\n    },\n    {\n      \"name\": \"request_transform\",\n      \"taskReferenceName\": \"request_transform\",\n      \"description\": \"request_transform\",\n      \"inputParameters\": {\n        \"body\": \"${workflow.input.body}\",\n        \"queryExpression\": \"${default_variables.output.result.requestTransform}\"\n      },\n      \"type\": \"JSON_JQ_TRANSFORM\",\n      \"startDelay\": 0,\n      \"optional\": false,\n      \"asyncComplete\": false\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/set_variable_workflow_integration_test.json",
    "content": "{\n  \"name\": \"set_variable_workflow_integration_test\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"simple\",\n      \"taskReferenceName\": \"simple\",\n      \"description\": \"simple\",\n      \"inputParameters\": {\n      },\n      \"type\": \"SIMPLE\",\n      \"startDelay\": 0,\n      \"optional\": false,\n      \"asyncComplete\": false\n    },\n    {\n      \"name\": \"set_variable\",\n      \"taskReferenceName\": \"set_variable_1\",\n      \"inputParameters\": {\n        \"var\": \"${workflow.input.var}\"\n      },\n      \"type\": \"SET_VARIABLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"wait\",\n      \"taskReferenceName\": \"wait0\",\n      \"inputParameters\": {},\n      \"type\": \"WAIT\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"variables\": \"${workflow.variables}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_decision_task_integration_test.json",
    "content": "{\n  \"name\": \"DecisionWorkflow\",\n  \"description\": \"DecisionWorkflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"decisionTask\",\n      \"taskReferenceName\": \"decisionTask\",\n      \"inputParameters\": {\n        \"case\": \"${workflow.input.case}\"\n      },\n      \"type\": \"DECISION\",\n      \"caseValueParam\": \"case\",\n      \"decisionCases\": {\n        \"c\": [\n          {\n            \"name\": \"integration_task_1\",\n            \"taskReferenceName\": \"t1\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [\n        {\n          \"name\": \"integration_task_5\",\n          \"taskReferenceName\": \"t5\",\n          \"inputParameters\": {\n            \"p1\": \"${workflow.input.param1}\",\n            \"p2\": \"${workflow.input.param2}\"\n          },\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_20\",\n      \"taskReferenceName\": \"t20\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_json_jq_transform_integration_test.json",
    "content": "{\n  \"name\": \"test_json_jq_transform_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"jq\",\n      \"taskReferenceName\": \"jq_1\",\n      \"inputParameters\": {\n        \"input\": \"${workflow.input}\",\n        \"queryExpression\": \".input as $_ | { out: ($_.in1.array + $_.in2.array) }\"\n      },\n      \"type\": \"JSON_JQ_TRANSFORM\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_lambda_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_lambda_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"lambda\",\n      \"taskReferenceName\": \"lambda0\",\n      \"inputParameters\": {\n        \"input\": \"${workflow.input}\",\n        \"scriptExpression\": \"if ($.input.a==1){return {testvalue: true}} else{return {testvalue: false} }\"\n      },\n      \"type\": \"LAMBDA\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_one_task_sub_workflow_integration_test.json",
    "content": "{\n  \"name\": \"sub_workflow\",\n  \"description\": \"sub_workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"simple_task_in_sub_wf\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_set_variable_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_set_variable_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"set_variable\",\n      \"taskReferenceName\": \"set_variable_1\",\n      \"inputParameters\": {\n        \"var\": \"${workflow.input.var}\"\n      },\n      \"type\": \"SET_VARIABLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"variables\": \"${workflow.variables}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_switch_task_integration_test.json",
    "content": "{\n  \"name\": \"SwitchWorkflow\",\n  \"description\": \"SwitchWorkflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"switchTask\",\n      \"taskReferenceName\": \"switchTask\",\n      \"inputParameters\": {\n        \"case\": \"${workflow.input.case}\"\n      },\n      \"type\": \"SWITCH\",\n      \"evaluatorType\": \"value-param\",\n      \"expression\": \"case\",\n      \"decisionCases\": {\n        \"c\": [\n          {\n            \"name\": \"integration_task_1\",\n            \"taskReferenceName\": \"t1\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [\n        {\n          \"name\": \"integration_task_5\",\n          \"taskReferenceName\": \"t5\",\n          \"inputParameters\": {\n            \"p1\": \"${workflow.input.param1}\",\n            \"p2\": \"${workflow.input.param2}\"\n          },\n          \"type\": \"SIMPLE\",\n          \"decisionCases\": {},\n          \"defaultCase\": [],\n          \"forkTasks\": [],\n          \"startDelay\": 0,\n          \"joinOn\": [],\n          \"optional\": false,\n          \"defaultExclusiveJoinTask\": [],\n          \"asyncComplete\": false,\n          \"loopOver\": []\n        }\n      ],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_20\",\n      \"taskReferenceName\": \"t20\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/simple_wait_task_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_wait_timeout\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"waitTimeout\",\n      \"taskReferenceName\": \"wait0\",\n      \"inputParameters\": {},\n      \"type\": \"WAIT\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_workflow_1_input_template_integration_test.json",
    "content": "{\n  \"name\": \"integration_test_template_wf\",\n  \"description\": \"Test a simple workflow with an input template\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\",\n        \"p3\": \"${CPEWF_TASK_ID}\",\n        \"someNullKey\": null\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\",\n    \"param3\",\n    \"param4\"\n  ],\n  \"inputTemplate\": {\n    \"param1\": {\n      \"nested_object\": {\n        \"nested_key\": \"nested_value\"\n      }\n    },\n    \"param2\": [\"list\", \"of\", \"strings\"],\n    \"param3\": \"string\"\n  },\n  \"outputParameters\": {\n    \"output\": \"${t1.output.op}\",\n    \"param1\": \"${workflow.input.param1}\",\n    \"param2\": \"${workflow.input.param2}\",\n    \"param3\": \"${workflow.input.param3}\"\n  },\n  \"failureWorkflow\": \"$workflow.input.failureWfName\",\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/simple_workflow_1_integration_test.json",
    "content": "{\n  \"name\": \"integration_test_wf\",\n  \"description\": \"integration_test_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\",\n        \"p3\": \"${CPEWF_TASK_ID}\",\n        \"someNullKey\": null\n      },\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {\n        \"tp1\": \"${workflow.input.param1}\",\n        \"tp2\": \"${t1.output.op}\",\n        \"tp3\": \"${CPEWF_TASK_ID}\"\n      },\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o1\": \"${workflow.input.param1}\",\n    \"o2\": \"${t2.output.uuid}\",\n    \"o3\": \"${t1.output.op}\"\n  },\n  \"failureWorkflow\": \"$workflow.input.failureWfName\",\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/simple_workflow_3_integration_test.json",
    "content": "{\n  \"name\": \"integration_test_wf3\",\n  \"description\": \"integration_test_wf3\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\",\n        \"someNullKey\": null\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {\n        \"tp1\": \"${workflow.input.param1}\",\n        \"tp2\": \"${t1.output.op}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_3\",\n      \"taskReferenceName\": \"t3\",\n      \"inputParameters\": {\n        \"tp1\": \"${workflow.input.param1}\",\n        \"tp2\": \"${t1.output.op}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_workflow_with_async_complete_system_task_integration_test.json",
    "content": "{\n  \"name\": \"async_complete_integration_test_wf\",\n  \"description\": \"async_complete_integration_test_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\",\n        \"p3\": \"${CPEWF_TASK_ID}\",\n        \"someNullKey\": null\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"user_task\",\n      \"taskReferenceName\": \"user_task\",\n      \"inputParameters\": {\n        \"input\": \"${t1.output.op}\"\n      },\n      \"type\": \"USER_TASK\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": true,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o1\": \"${workflow.input.param1}\",\n    \"o2\": \"${user_task.output.uuid}\",\n    \"o3\": \"${t1.output.op}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_workflow_with_optional_task_integration_test.json",
    "content": "{\n  \"name\": \"optional_task_wf\",\n  \"description\": \"optional_task_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"task_optional\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": true,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {\n        \"tp1\": \"${workflow.input.param1}\",\n        \"tp2\": \"${t1.output.op}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o1\": \"${workflow.input.param1}\",\n    \"o2\": \"${t2.output.uuid}\",\n    \"o3\": \"${t1.output.op}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_workflow_with_resp_time_out_integration_test.json",
    "content": "{\n  \"name\": \"RTOWF\",\n  \"description\": \"RTOWF\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"task_rt\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {\n        \"tp1\": \"${workflow.input.param1}\",\n        \"tp2\": \"${t1.output.op}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o1\": \"${workflow.input.param1}\",\n    \"o2\": \"${t2.output.uuid}\",\n    \"o3\": \"${t1.output.op}\"\n  },\n  \"failureWorkflow\": \"$workflow.input.failureWfName\",\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/simple_workflow_with_sub_workflow_inline_def_integration_test.json",
    "content": "{\n  \"name\": \"WorkflowWithInlineSubWorkflow\",\n  \"description\": \"WorkflowWithInlineSubWorkflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"tp11\": \"${workflow.input.param1}\",\n        \"tp12\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"swt\",\n      \"taskReferenceName\": \"swt\",\n      \"inputParameters\": {\n        \"op\": \"${t1.output.op}\",\n        \"imageType\": \"${t1.output.imageType}\"\n      },\n      \"type\": \"SUB_WORKFLOW\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"subWorkflowParam\": {\n        \"name\": \"one_task_workflow\",\n        \"version\": 1,\n        \"workflowDefinition\": {\n          \"name\": \"one_task_workflow\",\n          \"version\": 1,\n          \"tasks\": [\n            {\n              \"name\": \"integration_task_3\",\n              \"taskReferenceName\": \"t3\",\n              \"inputParameters\": {\n                \"p1\": \"${workflow.input.imageType}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ],\n          \"inputParameters\": [\n            \"imageType\",\n            \"op\"\n          ],\n          \"outputParameters\": {\n            \"op\": \"${t3.output.op}\"\n          },\n          \"schemaVersion\": 2,\n          \"restartable\": true,\n          \"workflowStatusListenerEnabled\": false,\n          \"timeoutPolicy\": \"ALERT_ONLY\",\n          \"timeoutSeconds\": 0\n        }\n      },\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {\n        \"op\": \"${t1.output.op}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o3\": \"${t1.output.op}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/start_workflow_input.json",
    "content": "{\n  \"startWorkflow\": {\n    \"name\": \"integration_test_wf\",\n    \"input\": {\n      \"op\": {\n        \"TEST_SAMPLE\": [\n          {\n            \"sourceId\": \"1413900_10830\",\n            \"url\": \"file/location/a0bdc4d0-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"1413900_50241\",\n            \"url\": \"file/location/cd4e00a0-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"generated-55ee8663-85c2-42d3-aca2-4076707e6d4e\",\n            \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n          },\n          {\n            \"sourceId\": \"generated-14056154-1544-4350-81db-b3751fe44777\",\n            \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n          },\n          {\n            \"sourceId\": \"generated-0b0ae5ea-d5c5-410c-adc9-bf16d2909c2e\",\n            \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n          },\n          {\n            \"sourceId\": \"generated-08869779-614d-417c-bfea-36a3f8f199da\",\n            \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n          },\n          {\n            \"sourceId\": \"generated-e117db45-1c48-45d0-b751-89386eb2d81d\",\n            \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n          },\n          {\n            \"sourceId\": \"f0221421-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/4a009209-002f-4b58-8b96-cb2198f8ba3c\"\n          },\n          {\n            \"sourceId\": \"f0252161-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/55b56298-5e7a-4949-b919-88c5c9557e8e\"\n          },\n          {\n            \"sourceId\": \"f038d070-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/3c4804f4-e826-436f-90c9-52b8d9266d52\"\n          },\n          {\n            \"sourceId\": \"f04e0621-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/689283a1-1816-48ef-83da-7f9ac874bf45\"\n          },\n          {\n            \"sourceId\": \"f04ddf10-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/586666ae-7321-445a-80b6-323c8c241ecd\"\n          },\n          {\n            \"sourceId\": \"f05950c0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/31795cc4-2590-4b20-a617-deaa18301f99\"\n          },\n          {\n            \"sourceId\": \"1413900_46819\",\n            \"url\": \"file/location/c74497a0-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"1413900_11177\",\n            \"url\": \"file/location/a231c730-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"1413900_48713\",\n            \"url\": \"file/location/ca638ae0-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"1413900_48525\",\n            \"url\": \"file/location/ca0c9140-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"1413900_73303\",\n            \"url\": \"file/location/d5943a40-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"1413900_55202\",\n            \"url\": \"file/location/d1a4d7a0-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"generated-61413adf-3c10-4484-b25d-e238df898f45\",\n            \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n          },\n          {\n            \"sourceId\": \"generated-addca397-f050-4339-ae86-9ba8c4e1b0d5\",\n            \"url\": \"file/sample/location/838a0ddb-a315-453a-8b8a-fa795f9d7691\"\n          },\n          {\n            \"sourceId\": \"generated-e4de9810-0f69-4593-8926-01ed82cbebcb\",\n            \"url\": \"file/sample/location/838a0ddb-a315-453a-8b8a-fa795f9d7691\"\n          },\n          {\n            \"sourceId\": \"generated-e16e2074-7af6-4700-ab05-ca41ba9c9ab4\",\n            \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n          },\n          {\n            \"sourceId\": \"generated-341c86f8-57a5-40e1-8842-3eb41dd9f528\",\n            \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n          },\n          {\n            \"sourceId\": \"generated-88c2ea9b-cef7-4120-8043-b92713d8fade\",\n            \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n          },\n          {\n            \"sourceId\": \"generated-3f6a731f-3c92-4677-9923-f80b8a6be632\",\n            \"url\": \"file/sample/location/3881aea9-a731-4e22-9ead-2d6eccc51140\"\n          },\n          {\n            \"sourceId\": \"generated-1508b871-64de-47ce-8b07-76c5cb3f3e1e\",\n            \"url\": \"file/sample/location/a2e4195f-3900-45b4-9335-45f85fca6467\"\n          },\n          {\n            \"sourceId\": \"generated-1406dce8-7b9c-4956-a7e8-78721c476ce9\",\n            \"url\": \"file/sample/location/a2e4195f-3900-45b4-9335-45f85fca6467\"\n          },\n          {\n            \"sourceId\": \"f0206671-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/35ebee36-3072-44c5-abb5-702a5a3b1a91\"\n          },\n          {\n            \"sourceId\": \"f01f5501-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/d3a9133d-c681-4910-a769-8195526ae634\"\n          },\n          {\n            \"sourceId\": \"f022b060-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/8fc1413d-170e-4644-a554-5e0c596b225c\"\n          },\n          {\n            \"sourceId\": \"f02fa8b1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/35bed0a2-7def-457b-bded-4f4d7d94f76e\"\n          },\n          {\n            \"sourceId\": \"f031f2a0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/a5a2ea1f-8d13-429c-a44d-3057d21f608a\"\n          },\n          {\n            \"sourceId\": \"f0424650-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/1c599ffc-4f10-4c0b-8d9a-ae41c7256113\"\n          },\n          {\n            \"sourceId\": \"f04ec970-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/8404a421-e1a6-41cf-af63-a35ccb474457\"\n          },\n          {\n            \"sourceId\": \"1413900_47197\",\n            \"url\": \"file/location/c81b6fa0-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"generated-2a63c0c8-62ea-44a4-a33b-f0b3047e8b00\",\n            \"url\": \"file/sample/location/e008d018-63d7-44b2-b07e-c7435430ac71\"\n          },\n          {\n            \"sourceId\": \"generated-b27face7-3589-4209-944a-5153b20c5996\",\n            \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n          },\n          {\n            \"sourceId\": \"generated-144675b3-9321-48d2-8b5b-e19a40d30ef2\",\n            \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n          },\n          {\n            \"sourceId\": \"generated-8cbe821e-b1fb-48ce-beb5-735319af4db6\",\n            \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n          },\n          {\n            \"sourceId\": \"generated-ecc4ea47-9bad-4b91-97c7-35f4ea6fb479\",\n            \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n          },\n          {\n            \"sourceId\": \"generated-c1eb9ed0-8560-4e09-a748-f926edb7cdc2\",\n            \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n          },\n          {\n            \"sourceId\": \"generated-6bed81fd-c777-4c61-8da1-0bb7f7cf0082\",\n            \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n          },\n          {\n            \"sourceId\": \"generated-852e5510-dd5d-4900-a614-854148fcc716\",\n            \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n          },\n          {\n            \"sourceId\": \"generated-f4dedcb7-37c9-4ba9-ab37-64ec9be7c882\",\n            \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n          },\n          {\n            \"sourceId\": \"f0259691-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/721bc0de-e75f-4386-8b2e-ca84eb653596\"\n          },\n          {\n            \"sourceId\": \"f02b3be1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/d2043b17-8ce5-42ee-a5e4-81c68f0c4838\"\n          },\n          {\n            \"sourceId\": \"f02b62f0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/63931561-3b5b-4ffe-af47-da2c9de94684\"\n          },\n          {\n            \"sourceId\": \"f0315660-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/d99ed629-2885-4e4a-8a1b-22e487b875fa\"\n          },\n          {\n            \"sourceId\": \"f0306c00-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/6f8e673a-7003-44aa-96b9-e2ed8a4654ff\"\n          },\n          {\n            \"sourceId\": \"f033c760-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/627c00f9-14b3-4057-b6e2-0f962ad0308e\"\n          },\n          {\n            \"sourceId\": \"f03526f1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/fafabaf9-fe58-4a9a-b555-026521aeb2fe\"\n          },\n          {\n            \"sourceId\": \"f03acc41-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/6c9fed2c-558a-4db3-8360-659b5e8c46e4\"\n          },\n          {\n            \"sourceId\": \"f0463df1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/e9fb83d2-5f14-4442-92b5-67e613f2e35f\"\n          },\n          {\n            \"sourceId\": \"f04fb3d0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/e7a0f82f-be8d-4ada-a4b1-13e8165e08be\"\n          },\n          {\n            \"sourceId\": \"f05272f0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/9aba488a-22b3-4932-85a7-52c461203541\"\n          },\n          {\n            \"sourceId\": \"f0581841-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/457415f6-6d0c-4304-8533-0d5b43fac564\"\n          },\n          {\n            \"sourceId\": \"generated-8fefb48c-6fde-4fd6-8f33-a1f3f3b62105\",\n            \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n          },\n          {\n            \"sourceId\": \"generated-30c61aa5-f5bd-4077-8c32-336b87acbe96\",\n            \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n          },\n          {\n            \"sourceId\": \"generated-d5da37db-d486-46d4-8f7d-1e0710a77eb5\",\n            \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n          },\n          {\n            \"sourceId\": \"generated-77af26fe-9e22-48af-99e3-f63f10fbe6de\",\n            \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n          },\n          {\n            \"sourceId\": \"generated-2e807016-3d11-4b60-bec7-c380a608b67d\",\n            \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n          },\n          {\n            \"sourceId\": \"generated-615d02e9-62c2-43ab-9df7-753b6b8e2c22\",\n            \"url\": \"file/sample/location/519f6c80-96ef-440f-9d37-ccf36c7d1e5d\"\n          },\n          {\n            \"sourceId\": \"generated-3e1600fd-a626-4ee6-972b-5f0187e96c38\",\n            \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n          },\n          {\n            \"sourceId\": \"generated-1dcb208c-6a58-4334-a60c-6fb54c8a2af5\",\n            \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n          },\n          {\n            \"sourceId\": \"f024ac30-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/0af2107b-4231-4d23-bef3-4e417ac6c5d3\"\n          },\n          {\n            \"sourceId\": \"f0282ea1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/0f592681-fd23-4194-ae43-42f61c664485\"\n          },\n          {\n            \"sourceId\": \"f02c4d50-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/ec46b9a3-99af-410a-af7d-726f8854909f\"\n          },\n          {\n            \"sourceId\": \"f02b8a00-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/aed7e5da-b524-4d41-b264-28ce615ec826\"\n          },\n          {\n            \"sourceId\": \"f02b14d1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/b88c9055-ab0d-4d27-a405-265ba2a15f0c\"\n          },\n          {\n            \"sourceId\": \"f03044f1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/fb8c4df9-d59e-4ac3-880e-4ea94cd880a4\"\n          },\n          {\n            \"sourceId\": \"f034ffe1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/59f3fbe8-b300-4861-9b2f-dac7b15aea7d\"\n          },\n          {\n            \"sourceId\": \"f03c2bd0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/19a06d54-41ed-419d-9947-f10cd5f0d85c\"\n          },\n          {\n            \"sourceId\": \"f03fae41-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/a9a48a62-7d62-4f67-b281-cc6fdc1e722c\"\n          },\n          {\n            \"sourceId\": \"f0455390-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/0aeffc0a-a5ad-46ff-abab-1b3bc6a5840a\"\n          },\n          {\n            \"sourceId\": \"f04b1ff1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/9a08aaed-c125-48f7-9d1d-fd11266c2b12\"\n          },\n          {\n            \"sourceId\": \"f04cf4b1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/17a6e0f9-aa64-411f-9af7-837c84f7443f\"\n          },\n          {\n            \"sourceId\": \"f0511360-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/fb633c73-cb33-4806-bc08-049024644856\"\n          },\n          {\n            \"sourceId\": \"f0538460-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/a7012248-6769-42da-a6c8-d4b831f6efce\"\n          },\n          {\n            \"sourceId\": \"f058db91-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/bcf71522-6168-48c4-86c9-995bca60ae51\"\n          },\n          {\n            \"sourceId\": \"generated-adf005c4-95c1-4904-9968-09cc19a26bfe\",\n            \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n          },\n          {\n            \"sourceId\": \"generated-c4d367a4-4cdc-412e-af79-09b227f2e3ba\",\n            \"url\": \"file/sample/location/3d927190-1c4d-4af2-91cf-2968d3ccfe70\"\n          },\n          {\n            \"sourceId\": \"generated-48dba018-f884-49db-b87e-67274e244c8f\",\n            \"url\": \"file/sample/location/4bce4154-fb4b-4f0a-887d-a0cd12d4d214\"\n          },\n          {\n            \"sourceId\": \"generated-26700b83-4892-420e-8b46-1ee21eba75fb\",\n            \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n          },\n          {\n            \"sourceId\": \"generated-632f3198-c0dc-4348-974f-51684d4e443e\",\n            \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n          },\n          {\n            \"sourceId\": \"generated-86e2dd1d-1aa4-4dbe-b37b-b488f5dd1c70\",\n            \"url\": \"file/sample/location/e87da4d1-72da-47a3-801d-43e01c050c89\"\n          },\n          {\n            \"sourceId\": \"f04134e0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/ff8f59bf-7757-4d51-a7e4-619f3e8ffaf2\"\n          },\n          {\n            \"sourceId\": \"f04f65b0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/d66467d1-3ac6-4041-8d15-e722ee07231f\"\n          },\n          {\n            \"sourceId\": \"1413900_15255\",\n            \"url\": \"file/location/a9e20260-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"generated-e953493b-cbe3-4319-885e-00c82089c76c\",\n            \"url\": \"file/sample/location/ec16facd-86e3-4c3f-8dfb-7a2ad3a4e18c\"\n          },\n          {\n            \"sourceId\": \"generated-65c54676-3adb-4ef0-b65e-8e2a49533cbf\",\n            \"url\": \"file/sample/location/07ec28a1-189e-4f2a-9dd5-f3ca68ce977d\"\n          },\n          {\n            \"sourceId\": \"f02ac6b0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/21568877-07a5-411f-9715-5e92806c4448\"\n          },\n          {\n            \"sourceId\": \"f02fcfc1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/f3b1f1a2-48d3-475d-a607-2e5a1fe532e7\"\n          },\n          {\n            \"sourceId\": \"f03526f0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/84a40c66-d925-4a4a-ba62-8491d26e29e9\"\n          },\n          {\n            \"sourceId\": \"f03e75c1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/e84c00e8-a148-46cf-9a0b-431c4c2aeb08\"\n          },\n          {\n            \"sourceId\": \"f0429471-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/178de9fa-7cc8-457a-8fb6-5c080e6163ea\"\n          },\n          {\n            \"sourceId\": \"f047eba0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/18d153aa-e13b-4264-ae03-f3da75eb425b\"\n          },\n          {\n            \"sourceId\": \"f04fdae0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/7c843e53-8d87-47cf-bca5-1a02e7f5e33f\"\n          },\n          {\n            \"sourceId\": \"f0553210-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/26bacd65-9082-4d83-9506-90e5f1ccd16a\"\n          },\n          {\n            \"sourceId\": \"1413900_84904\",\n            \"url\": \"file/location/d8f7b090-5315-11e8-bf88-0efd527701fc\"\n          },\n          {\n            \"sourceId\": \"generated-84adc784-8d7d-4088-ba51-16fde57fbc21\",\n            \"url\": \"file/sample/location/3881aea9-a731-4e22-9ead-2d6eccc51140\"\n          },\n          {\n            \"sourceId\": \"generated-9e49c58b-0b33-4daf-a39a-8fc91e302328\",\n            \"url\": \"file/sample/location/4bce4154-fb4b-4f0a-887d-a0cd12d4d214\"\n          },\n          {\n            \"sourceId\": \"f02dd3f1-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/8937b328-8f0d-4762-8d1f-7d7bc80c3d2e\"\n          },\n          {\n            \"sourceId\": \"f03240c0-86e8-11e8-af77-0a2ba4eae3ec\",\n            \"url\": \"file/test/location/aab6e386-4d59-4b40-b257-9aed12a45446\"\n          }\n        ]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/switch_and_fork_join_integration_test.json",
    "content": "{\n  \"name\": \"ForkConditionalTest\",\n  \"description\": \"ForkConditionalTest\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"forkTask\",\n      \"taskReferenceName\": \"forkTask\",\n      \"inputParameters\": {},\n      \"type\": \"FORK_JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"switchTask\",\n            \"taskReferenceName\": \"switchTask\",\n            \"inputParameters\": {\n              \"case\": \"${workflow.input.case}\"\n            },\n            \"type\": \"SWITCH\",\n            \"evaluatorType\": \"value-param\",\n            \"expression\": \"case\",\n            \"decisionCases\": {\n              \"c\": [\n                {\n                  \"name\": \"integration_task_1\",\n                  \"taskReferenceName\": \"t1\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"integration_task_2\",\n                  \"taskReferenceName\": \"t2\",\n                  \"inputParameters\": {\n                    \"p1\": \"${workflow.input.param1}\",\n                    \"p2\": \"${workflow.input.param2}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ]\n            },\n            \"defaultCase\": [\n              {\n                \"name\": \"integration_task_5\",\n                \"taskReferenceName\": \"t5\",\n                \"inputParameters\": {\n                  \"p1\": \"${workflow.input.param1}\",\n                  \"p2\": \"${workflow.input.param2}\"\n                },\n                \"type\": \"SIMPLE\",\n                \"decisionCases\": {},\n                \"defaultCase\": [],\n                \"forkTasks\": [],\n                \"startDelay\": 0,\n                \"joinOn\": [],\n                \"optional\": false,\n                \"defaultExclusiveJoinTask\": [],\n                \"asyncComplete\": false,\n                \"loopOver\": []\n              }\n            ],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"integration_task_20\",\n            \"taskReferenceName\": \"t20\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        [\n          {\n            \"name\": \"integration_task_10\",\n            \"taskReferenceName\": \"t10\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      ],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"joinTask\",\n      \"taskReferenceName\": \"joinTask\",\n      \"inputParameters\": {},\n      \"type\": \"JOIN\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [\n        \"t20\",\n        \"t10\"\n      ],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/switch_and_terminate_integration_test.json",
    "content": "{\n  \"name\": \"ConditionalTerminateWorkflow\",\n  \"description\": \"ConditionalTerminateWorkflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"tp11\": \"${workflow.input.param1}\",\n        \"tp12\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"switch\",\n      \"taskReferenceName\": \"switch\",\n      \"inputParameters\": {\n        \"case\": \"${workflow.input.case}\"\n      },\n      \"type\": \"SWITCH\",\n      \"evaluatorType\": \"value-param\",\n      \"expression\": \"case\",\n      \"decisionCases\": {\n        \"one\": [\n          {\n            \"name\": \"integration_task_2\",\n            \"taskReferenceName\": \"t2\",\n            \"inputParameters\": {\n              \"tp21\": \"${workflow.input.param1}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"two\": [\n          {\n            \"name\": \"terminate\",\n            \"taskReferenceName\": \"terminate0\",\n            \"inputParameters\": {\n              \"terminationStatus\": \"FAILED\",\n              \"workflowOutput\": \"${t1.output.op}\"\n            },\n            \"type\": \"TERMINATE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_3\",\n      \"taskReferenceName\": \"t3\",\n      \"inputParameters\": {\n        \"tp31\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"outputParameters\": {\n    \"o2\": \"${t3.output.op}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/switch_with_no_default_case_integration_test.json",
    "content": "{\n  \"name\": \"SwitchWithNoDefaultCaseWF\",\n  \"description\": \"switch_with_no_default_case\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"switchTask\",\n      \"taskReferenceName\": \"switchTask\",\n      \"inputParameters\": {\n        \"case\": \"${workflow.input.case}\"\n      },\n      \"type\": \"SWITCH\",\n      \"evaluatorType\": \"value-param\",\n      \"expression\": \"case\",\n      \"decisionCases\": {\n        \"c\": [\n          {\n            \"name\": \"integration_task_1\",\n            \"taskReferenceName\": \"t1\",\n            \"inputParameters\": {\n              \"p1\": \"${workflow.input.param1}\",\n              \"p2\": \"${workflow.input.param2}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      },\n      \"startDelay\": 0,\n      \"optional\": false,\n      \"asyncComplete\": false\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\"\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/terminate_task_completed_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_terminate_task_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"lambda\",\n      \"taskReferenceName\": \"lambda0\",\n      \"inputParameters\": {\n        \"input\": \"${workflow.input}\",\n        \"scriptExpression\": \"if ($.input.a==1){return {testvalue: true}} else{return {testvalue: false}}\"\n      },\n      \"type\": \"LAMBDA\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"terminate\",\n      \"taskReferenceName\": \"terminate0\",\n      \"inputParameters\": {\n        \"terminationStatus\": \"COMPLETED\",\n        \"workflowOutput\": \"${lambda0.output}\"\n      },\n      \"type\": \"TERMINATE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"o1\": \"${lambda0.output}\",\n    \"o2\": \"${t2.output}\"\n  },\n  \"failureWorkflow\": \"failure_workflow\",\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/terminate_task_failed_workflow_integration.json",
    "content": "{\n  \"name\": \"test_terminate_task_failed_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"lambda\",\n      \"taskReferenceName\": \"lambda0\",\n      \"inputParameters\": {\n        \"input\": \"${workflow.input}\",\n        \"scriptExpression\": \"if ($.input.a==1){return {testvalue: true}} else{return {testvalue: false}}\"\n      },\n      \"type\": \"LAMBDA\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"terminate\",\n      \"taskReferenceName\": \"terminate0\",\n      \"inputParameters\": {\n        \"terminationStatus\": \"FAILED\",\n        \"terminationReason\": \"Early exit in terminate\",\n        \"workflowOutput\": \"${lambda0.output}\"\n      },\n      \"type\": \"TERMINATE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"failureWorkflow\": \"failure_workflow\",\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/terminate_task_parent_workflow.json",
    "content": "{\n  \"name\": \"test_terminate_task_parent_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"test_forkjoin\",\n      \"taskReferenceName\": \"forkx\",\n      \"type\": \"FORK_JOIN\",\n      \"forkTasks\": [\n        [\n          {\n            \"name\": \"test_lambda_task1\",\n            \"taskReferenceName\": \"lambdaTask1\",\n            \"inputParameters\": {\n              \"lambdaValue\": \"${workflow.input.lambdaValue}\",\n              \"scriptExpression\": \"var i = 10; if ($.lambdaValue == 1){ return {testvalue: 'Lambda value was 1', iValue: i} } else { return {testvalue: 'Lambda value was NOT 1', iValue: i + 3} }\"\n            },\n            \"type\": \"LAMBDA\"\n          },\n          {\n            \"name\": \"test_terminate_subworkflow\",\n            \"taskReferenceName\": \"test_terminate_subworkflow\",\n            \"inputParameters\": {\n            },\n            \"type\": \"SUB_WORKFLOW\",\n            \"subWorkflowParam\": {\n              \"name\": \"test_terminate_task_sub_wf\"\n            }\n          }\n        ],\n        [\n          {\n            \"name\": \"test_lambda_task2\",\n            \"taskReferenceName\": \"lambdaTask2\",\n            \"inputParameters\": {\n              \"lambdaValue\": \"${workflow.input.lambdaValue}\",\n              \"scriptExpression\": \"var i = 10; if ($.lambdaValue == 1){ return {testvalue: 'Lambda value was 1', iValue: i} } else { return {testvalue: 'Lambda value was NOT 1', iValue: i + 3} }\"\n            },\n            \"type\": \"LAMBDA\"\n          },\n          {\n            \"name\": \"test_wait_task\",\n            \"taskReferenceName\": \"basicJavaA\",\n            \"type\": \"WAIT\"\n          },\n          {\n            \"name\": \"terminate\",\n            \"taskReferenceName\": \"terminate0\",\n            \"inputParameters\": {\n              \"terminationStatus\": \"COMPLETED\",\n              \"workflowOutput\": \"some output\"\n            },\n            \"type\": \"TERMINATE\",\n            \"startDelay\": 0,\n            \"optional\": false\n          },\n          {\n            \"name\": \"test_second_wait_task\",\n            \"taskReferenceName\": \"basicJavaB\",\n            \"type\": \"WAIT\"\n          }\n        ]\n      ]\n    },\n    {\n      \"name\": \"join\",\n      \"taskReferenceName\": \"thejoin\",\n      \"type\": \"JOIN\",\n      \"joinOn\": [\n        \"test_terminate_subworkflow\",\n        \"basicJavaB\"\n      ]\n    }\n  ],\n  \"schemaVersion\": 2,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/terminate_task_sub_workflow.json",
    "content": "{\n  \"name\": \"test_terminate_task_sub_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_3\",\n      \"taskReferenceName\": \"t3\",\n      \"type\": \"SIMPLE\"\n    }\n  ],\n  \"schemaVersion\": 2,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/test_task_failed_parent_workflow.json",
    "content": "{\n  \"name\": \"test_task_failed_parent_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"test_lambda_task1\",\n      \"taskReferenceName\": \"lambdaTask1\",\n      \"inputParameters\": {\n        \"lambdaValue\": \"${workflow.input.lambdaValue}\",\n        \"scriptExpression\": \"var i = 10; if ($.lambdaValue == 1){ return {testvalue: 'Lambda value was 1', iValue: i} } else { return {testvalue: 'Lambda value was NOT 1', iValue: i + 3} }\"\n      },\n      \"type\": \"LAMBDA\"\n    },\n    {\n      \"name\": \"test_task_failed_sub_wf\",\n      \"taskReferenceName\": \"test_task_failed_sub_wf\",\n      \"inputParameters\": {\n      },\n      \"type\": \"SUB_WORKFLOW\",\n      \"subWorkflowParam\": {\n        \"name\": \"test_task_failed_sub_wf\"\n      }\n    },\n    {\n      \"name\": \"test_lambda_task2\",\n      \"taskReferenceName\": \"lambdaTask2\",\n      \"inputParameters\": {\n        \"lambdaValue\": \"${workflow.input.lambdaValue}\",\n        \"scriptExpression\": \"var i = 10; if ($.lambdaValue == 1){ return {testvalue: 'Lambda value was 1', iValue: i} } else { return {testvalue: 'Lambda value was NOT 1', iValue: i + 3} }\"\n      },\n      \"type\": \"LAMBDA\"\n    }\n  ],\n  \"schemaVersion\": 2,\n  \"ownerEmail\": \"test@harness.com\",\n  \"failureWorkflow\": \"failure_workflow\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/test_task_failed_sub_workflow.json",
    "content": "{\n  \"name\": \"test_task_failed_sub_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"lambda\",\n      \"taskReferenceName\": \"lambda0\",\n      \"inputParameters\": {\n        \"input\": \"${workflow.input}\",\n        \"scriptExpression\": \"if ($.input.a==1){return {testvalue: true}} else{return {testvalue: false}}\"\n      },\n      \"type\": \"LAMBDA\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"terminate\",\n      \"taskReferenceName\": \"terminate0\",\n      \"inputParameters\": {\n        \"terminationStatus\": \"FAILED\",\n        \"workflowOutput\": \"${lambda0.output}\"\n      },\n      \"type\": \"TERMINATE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_2\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/wait_workflow_integration_test.json",
    "content": "{\n  \"name\": \"test_wait_workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"wait\",\n      \"taskReferenceName\": \"wait0\",\n      \"inputParameters\": {},\n      \"type\": \"WAIT\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {},\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}"
  },
  {
    "path": "test-harness/src/test/resources/workflow_that_starts_another_workflow.json",
    "content": "{\n  \"name\": \"workflow_that_starts_another_workflow\",\n  \"description\": \"A workflow that uses START_WORKFLOW task to start another workflow\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"start_workflow\",\n      \"taskReferenceName\": \"st\",\n      \"inputParameters\": {\n        \"startWorkflow\": \"${workflow.input.startWorkflow}\"\n      },\n      \"type\": \"START_WORKFLOW\"\n    }\n  ],\n  \"inputParameters\": [\"start_workflow\"],\n  \"outputParameters\": {},\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/workflow_with_sub_workflow_1_integration_test.json",
    "content": "{\n  \"name\": \"integration_test_wf_with_sub_wf\",\n  \"description\": \"integration_test_wf_with_sub_wf\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"inputParameters\": {\n        \"p1\": \"${workflow.input.param1}\",\n        \"p2\": \"${workflow.input.param2}\",\n        \"someNullKey\": null\n      },\n      \"type\": \"SIMPLE\",\n      \"decisionCases\": {},\n      \"defaultCase\": [],\n      \"forkTasks\": [],\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": []\n    },\n    {\n      \"name\": \"sub_workflow_task\",\n      \"taskReferenceName\": \"t2\",\n      \"inputParameters\": {\n        \"param1\": \"${workflow.input.param1}\",\n        \"param2\": \"${workflow.input.param2}\",\n        \"subwf\": \"${workflow.input.nextSubwf}\"\n      },\n      \"type\": \"SUB_WORKFLOW\",\n      \"subWorkflowParam\": {\n        \"name\": \"${workflow.input.subwf}\",\n        \"version\": 1\n      },\n      \"startDelay\": 0,\n      \"joinOn\": [],\n      \"optional\": false,\n      \"defaultExclusiveJoinTask\": [],\n      \"asyncComplete\": false,\n      \"loopOver\": [],\n      \"retryCount\": 0\n    }\n  ],\n  \"inputParameters\": [\n    \"param1\",\n    \"param2\"\n  ],\n  \"failureWorkflow\": \"$workflow.input.failureWfName\",\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"ownerEmail\": \"test@harness.com\"\n}\n"
  },
  {
    "path": "test-harness/src/test/resources/workflow_with_synchronous_system_task.json",
    "content": "{\n  \"name\": \"workflow_with_synchronous_system_task\",\n  \"description\": \"A workflow with a simple task followed a synchronous task\",\n  \"version\": 1,\n  \"tasks\": [\n    {\n      \"name\": \"integration_task_1\",\n      \"taskReferenceName\": \"t1\",\n      \"type\": \"SIMPLE\"\n    },\n    {\n      \"name\": \"jsonjq\",\n      \"taskReferenceName\": \"jsonjq\",\n      \"inputParameters\": {\n        \"queryExpression\": \".tp2.TEST_SAMPLE | length\",\n        \"tp1\": \"${workflow.input.param1}\",\n        \"tp2\": \"${t1.output.op}\"\n      },\n      \"type\": \"JSON_JQ_TRANSFORM\"\n    }\n  ],\n  \"inputParameters\": [],\n  \"outputParameters\": {\n    \"data\": \"${jsonjq.output.resources}\"\n  },\n  \"schemaVersion\": 2,\n  \"restartable\": true,\n  \"workflowStatusListenerEnabled\": false,\n  \"ownerEmail\": \"example@email.com\",\n  \"timeoutPolicy\": \"ALERT_ONLY\",\n  \"timeoutSeconds\": 0,\n  \"variables\": {},\n  \"inputTemplate\": {}\n}\n"
  },
  {
    "path": "ui/.eslintrc",
    "content": "{\n  \"extends\": [\"react-app\", \"plugin:cypress/recommended\"],\n  \"rules\": {\n    \"import/no-anonymous-default-export\": 0\n  }\n}\n"
  },
  {
    "path": "ui/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n/cypress/screenshots\n/cypress/videos\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n"
  },
  {
    "path": "ui/.prettierignore",
    "content": "build"
  },
  {
    "path": "ui/.prettierrc.json",
    "content": "{}\n"
  },
  {
    "path": "ui/README.md",
    "content": "## Conductor UI\n\nThe UI is a standard `create-react-app` React Single Page Application (SPA). To get started, with Node 14 and `yarn` installed, first run `yarn install` from within the `/ui` directory to retrieve package dependencies.\n\nFor more information regarding CRA configuration and usage, see the official [doc site](https://create-react-app.dev/).\n\n> ### For upgrading users\n>\n> The UI is designed to operate directly with the Conductor Server API. A Node `express` backend is no longer required.\n\n### Development Server\n\nTo run the UI on the bundled development server, run `yarn run start`. Navigate your browser to `http://localhost:5000`.\n\n#### Reverse Proxy configuration\n\nThe default setup expects that the Conductor Server API will be available at `localhost:8080/api`. You may select an alternate port and hostname, or rewrite the API path by editing `setupProxy.js`. Note that `setupProxy.js` is used ONLY by the development server.\n\n### Hosting for Production\n\nThere is no need to \"build\" the project unless you require compiled assets to host on a production web server. In this case, the project can be built with the command `yarn build`. The assets will be produced to `/build`.\n\nYour hosting environment should make the Conductor Server API available on the same domain. This avoids complexities regarding cross-origin data fetching. The default path prefix is `/api`. If a different prefix is desired, `plugins/fetch.js` can be modified to customize the API fetch behavior.\n\nSee `docker/serverAndUI` for an `nginx` based example.\n\n#### Different host path\nThe static UI would work when rendered from any host route.\nThe default is '/'. You can customize this by setting the 'homepage' field in package.json\nRefer\n- https://docs.npmjs.com/cli/v9/configuring-npm/package-json#homepage\n- https://create-react-app.dev/docs/deployment/#building-for-relative-paths\n\n\n### Customization Hooks\n\nFor ease of maintenance, a number of touch points for customization have been removed to `/plugins`.\n\n- `AppBarModules.jsx`\n- `AppLogo.jsx`\n- `env.js`\n- `fetch.js`\n\n### Authentication\n\nWe recommend that authentication & authorization be de-coupled from the UI and handled at the web server/access gateway.\n\n#### Examples (WIP)\n\n- Basic Auth (username/password) with `nginx`\n- Commercial IAM Vendor\n- Node `express` server with `passport.js`\n"
  },
  {
    "path": "ui/cypress/e2e/spec.cy.js",
    "content": "describe(\"Landing Page\", () => {\n  beforeEach(() => {\n    cy.intercept(\"/api/workflow/search?**\", { fixture: \"workflowSearch.json\" });\n    cy.intercept(\"/api/tasks/search?**\", { fixture: \"taskSearch.json\" });\n    cy.intercept(\"/api/metadata/workflow\", {\n      fixture: \"metadataWorkflow.json\",\n    });\n    cy.intercept(\"/api/metadata/taskdefs\", { fixture: \"metadataTasks.json\" });\n  });\n\n  it(\"Homepage preloads with default query\", () => {\n    cy.visit(\"/\");\n    cy.contains(\"Search Execution\");\n    cy.contains(\"Page 1 of 5\");\n    cy.get(\".rdt_TableCell\").contains(\"feature_value_compute_workflow\");\n  });\n\n  it(\"Workflow name dropdown\", () => {\n    cy.get(\".MuiAutocomplete-inputRoot input\").first().click();\n    cy.get(\"li.MuiAutocomplete-option\")\n      .contains(\"Do_While_Workflow_Iteration_Fix\")\n      .click();\n    cy.get(\".MuiAutocomplete-tag\").contains(\"Do_While_Workflow_Iteration_Fix\");\n  });\n\n  it(\"Switch to Task Tab - No results\", () => {\n    cy.get(\"a.MuiTab-root\").contains(\"Tasks\").click();\n    cy.contains(\"Task Name\");\n    cy.contains(\"There are no records to display\");\n  });\n\n  it(\"Task Name Dropdown\", () => {\n    cy.get(\".MuiAutocomplete-inputRoot input\").first().click();\n    cy.get(\"li.MuiAutocomplete-option\").contains(\"example_task_2\").click();\n    cy.get(\".MuiAutocomplete-tag\").contains(\"example_task_2\");\n  });\n\n  it(\"Execute Task Search\", () => {\n    cy.get(\"button\").contains(\"Search\").click();\n    cy.contains(\"Page 1 of 1\");\n    cy.get(\".rdt_TableCell\").contains(\"36d24c5c-9c26-46cf-9709-e1bc6963b8a5\");\n  });\n});\n"
  },
  {
    "path": "ui/cypress/fixtures/doWhile/doWhileSwitch.json",
    "content": "{\n  \"ownerApp\": \"nq_mwi_conductor_ui_server\",\n  \"createTime\": 1660252744369,\n  \"status\": \"COMPLETED\",\n  \"endTime\": 1660252745449,\n  \"workflowId\": \"9aaf69a6-9c61-4460-93b5-0a657a084ba4\",\n  \"tasks\": [\n    {\n      \"taskType\": \"INLINE\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"evaluatorType\": \"javascript\",\n        \"expression\": \"1\",\n        \"value\": null\n      },\n      \"referenceTaskName\": \"inline_task_outside\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"pollCount\": 0,\n      \"taskDefName\": \"inline_task_outside\",\n      \"scheduledTime\": 1660252744439,\n      \"startTime\": 1660252744437,\n      \"endTime\": 1660252744504,\n      \"updateTime\": 1660252744446,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"9aaf69a6-9c61-4460-93b5-0a657a084ba4\",\n      \"workflowType\": \"LoopTestWithSwitch\",\n      \"taskId\": \"07ae873e-5316-4e89-9c1e-a9cab711f1a2\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"inline_task_outside\",\n        \"taskReferenceName\": \"inline_task_outside\",\n        \"inputParameters\": {\n          \"value\": \"${workflow.input.value}\",\n          \"evaluatorType\": \"javascript\",\n          \"expression\": \"1\"\n        },\n        \"type\": \"INLINE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -2,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"DO_WHILE\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"value\": null\n      },\n      \"referenceTaskName\": \"LoopTask\",\n      \"retryCount\": 0,\n      \"seq\": 2,\n      \"pollCount\": 0,\n      \"taskDefName\": \"Loop Task\",\n      \"scheduledTime\": 1660252744620,\n      \"startTime\": 1660252744618,\n      \"endTime\": 1660252745337,\n      \"updateTime\": 1660252744808,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"9aaf69a6-9c61-4460-93b5-0a657a084ba4\",\n      \"workflowType\": \"LoopTestWithSwitch\",\n      \"taskId\": \"790126b0-81e8-4286-ac65-d1f4c8eca271\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"1\": {\n          \"inline_task\": {\n            \"result\": {\n              \"result\": \"NODE_2\"\n            }\n          },\n          \"switch_task\": {\n            \"evaluationResult\": [\"null\"]\n          }\n        },\n        \"iteration\": 1\n      },\n      \"workflowTask\": {\n        \"name\": \"Loop Task\",\n        \"taskReferenceName\": \"LoopTask\",\n        \"inputParameters\": {\n          \"value\": \"${workflow.input.value}\"\n        },\n        \"type\": \"DO_WHILE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false,\n        \"loopCondition\": \"false\",\n        \"loopOver\": [\n          {\n            \"name\": \"inline_task\",\n            \"taskReferenceName\": \"inline_task\",\n            \"inputParameters\": {\n              \"value\": \"${workflow.input.value}\",\n              \"evaluatorType\": \"javascript\",\n              \"expression\": \"function e() { if ($.value == 1){return {\\\"result\\\": 'NODE_1'}} else { return {\\\"result\\\": 'NODE_2'}}} e();\"\n            },\n            \"type\": \"INLINE\",\n            \"startDelay\": 0,\n            \"optional\": false,\n            \"asyncComplete\": false\n          },\n          {\n            \"name\": \"switch_task\",\n            \"taskReferenceName\": \"switch_task\",\n            \"inputParameters\": {\n              \"switchCaseValue\": \"${inline_task_1.output.result.result}\"\n            },\n            \"type\": \"SWITCH\",\n            \"decisionCases\": {\n              \"NODE_1\": [\n                {\n                  \"name\": \"Set_NODE_1\",\n                  \"taskReferenceName\": \"Set_NODE_1\",\n                  \"inputParameters\": {\n                    \"node\": \"NODE_1\"\n                  },\n                  \"type\": \"SET_VARIABLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"asyncComplete\": false\n                }\n              ],\n              \"NODE_2\": [\n                {\n                  \"name\": \"Set_NODE_2\",\n                  \"taskReferenceName\": \"Set_NODE_2\",\n                  \"inputParameters\": {\n                    \"node\": \"NODE_2\"\n                  },\n                  \"type\": \"SET_VARIABLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"asyncComplete\": false\n                }\n              ]\n            },\n            \"startDelay\": 0,\n            \"optional\": false,\n            \"asyncComplete\": false,\n            \"evaluatorType\": \"value-param\",\n            \"expression\": \"switchCaseValue\"\n          }\n        ]\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 1,\n      \"workflowPriority\": 0,\n      \"iteration\": 1,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -2,\n      \"loopOverTask\": true\n    },\n    {\n      \"taskType\": \"INLINE\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"evaluatorType\": \"javascript\",\n        \"expression\": \"function e() { if ($.value == 1){return {\\\"result\\\": 'NODE_1'}} else { return {\\\"result\\\": 'NODE_2'}}} e();\",\n        \"value\": null\n      },\n      \"referenceTaskName\": \"inline_task__1\",\n      \"retryCount\": 0,\n      \"seq\": 3,\n      \"pollCount\": 0,\n      \"taskDefName\": \"inline_task\",\n      \"scheduledTime\": 1660252744696,\n      \"startTime\": 1660252744693,\n      \"endTime\": 1660252744931,\n      \"updateTime\": 1660252744702,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"9aaf69a6-9c61-4460-93b5-0a657a084ba4\",\n      \"workflowType\": \"LoopTestWithSwitch\",\n      \"taskId\": \"27f7fbc4-325b-43c4-872f-37dc64c9dab0\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": {\n          \"result\": \"NODE_2\"\n        }\n      },\n      \"workflowTask\": {\n        \"name\": \"inline_task\",\n        \"taskReferenceName\": \"inline_task\",\n        \"inputParameters\": {\n          \"value\": \"${workflow.input.value}\",\n          \"evaluatorType\": \"javascript\",\n          \"expression\": \"function e() { if ($.value == 1){return {\\\"result\\\": 'NODE_1'}} else { return {\\\"result\\\": 'NODE_2'}}} e();\"\n        },\n        \"type\": \"INLINE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 1,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -3,\n      \"loopOverTask\": true\n    },\n    {\n      \"taskType\": \"SWITCH\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"case\": \"null\"\n      },\n      \"referenceTaskName\": \"switch_task__1\",\n      \"retryCount\": 0,\n      \"seq\": 4,\n      \"pollCount\": 0,\n      \"taskDefName\": \"SWITCH\",\n      \"scheduledTime\": 1660252745049,\n      \"startTime\": 1660252745047,\n      \"endTime\": 1660252745163,\n      \"updateTime\": 1660252745056,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"9aaf69a6-9c61-4460-93b5-0a657a084ba4\",\n      \"workflowType\": \"LoopTestWithSwitch\",\n      \"taskId\": \"2e2a0836-a2e6-4902-9e41-9bbc2c75e0ed\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"evaluationResult\": [\"null\"]\n      },\n      \"workflowTask\": {\n        \"name\": \"switch_task\",\n        \"taskReferenceName\": \"switch_task\",\n        \"inputParameters\": {\n          \"switchCaseValue\": \"${inline_task_1.output.result.result}\"\n        },\n        \"type\": \"SWITCH\",\n        \"decisionCases\": {\n          \"NODE_1\": [\n            {\n              \"name\": \"Set_NODE_1\",\n              \"taskReferenceName\": \"Set_NODE_1\",\n              \"inputParameters\": {\n                \"node\": \"NODE_1\"\n              },\n              \"type\": \"SET_VARIABLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            }\n          ],\n          \"NODE_2\": [\n            {\n              \"name\": \"Set_NODE_2\",\n              \"taskReferenceName\": \"Set_NODE_2\",\n              \"inputParameters\": {\n                \"node\": \"NODE_2\"\n              },\n              \"type\": \"SET_VARIABLE\",\n              \"startDelay\": 0,\n              \"optional\": false,\n              \"asyncComplete\": false\n            }\n          ]\n        },\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false,\n        \"evaluatorType\": \"value-param\",\n        \"expression\": \"switchCaseValue\"\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 1,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -2,\n      \"loopOverTask\": true\n    }\n  ],\n  \"input\": {},\n  \"output\": {\n    \"evaluationResult\": [\"null\"]\n  },\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [],\n  \"workflowDefinition\": {\n    \"createTime\": 1660244498873,\n    \"updateTime\": 1660252731854,\n    \"name\": \"LoopTestWithSwitch\",\n    \"description\": \"Loop Test With Switch WF\",\n    \"version\": 3,\n    \"tasks\": [\n      {\n        \"name\": \"inline_task_outside\",\n        \"taskReferenceName\": \"inline_task_outside\",\n        \"inputParameters\": {\n          \"value\": \"${workflow.input.value}\",\n          \"evaluatorType\": \"javascript\",\n          \"expression\": \"1\"\n        },\n        \"type\": \"INLINE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false\n      },\n      {\n        \"name\": \"Loop Task\",\n        \"taskReferenceName\": \"LoopTask\",\n        \"inputParameters\": {\n          \"value\": \"${workflow.input.value}\"\n        },\n        \"type\": \"DO_WHILE\",\n        \"startDelay\": 0,\n        \"optional\": false,\n        \"asyncComplete\": false,\n        \"loopCondition\": \"false\",\n        \"loopOver\": [\n          {\n            \"name\": \"inline_task\",\n            \"taskReferenceName\": \"inline_task\",\n            \"inputParameters\": {\n              \"value\": \"${workflow.input.value}\",\n              \"evaluatorType\": \"javascript\",\n              \"expression\": \"function e() { if ($.value == 1){return {\\\"result\\\": 'NODE_1'}} else { return {\\\"result\\\": 'NODE_2'}}} e();\"\n            },\n            \"type\": \"INLINE\",\n            \"startDelay\": 0,\n            \"optional\": false,\n            \"asyncComplete\": false\n          },\n          {\n            \"name\": \"switch_task\",\n            \"taskReferenceName\": \"switch_task\",\n            \"inputParameters\": {\n              \"switchCaseValue\": \"${inline_task_1.output.result.result}\"\n            },\n            \"type\": \"SWITCH\",\n            \"decisionCases\": {\n              \"NODE_1\": [\n                {\n                  \"name\": \"Set_NODE_1\",\n                  \"taskReferenceName\": \"Set_NODE_1\",\n                  \"inputParameters\": {\n                    \"node\": \"NODE_1\"\n                  },\n                  \"type\": \"SET_VARIABLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"asyncComplete\": false\n                }\n              ],\n              \"NODE_2\": [\n                {\n                  \"name\": \"Set_NODE_2\",\n                  \"taskReferenceName\": \"Set_NODE_2\",\n                  \"inputParameters\": {\n                    \"node\": \"NODE_2\"\n                  },\n                  \"type\": \"SET_VARIABLE\",\n                  \"startDelay\": 0,\n                  \"optional\": false,\n                  \"asyncComplete\": false\n                }\n              ]\n            },\n            \"startDelay\": 0,\n            \"optional\": false,\n            \"asyncComplete\": false,\n            \"evaluatorType\": \"value-param\",\n            \"expression\": \"switchCaseValue\"\n          }\n        ]\n      }\n    ],\n    \"inputParameters\": [],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": true,\n    \"ownerEmail\": \"abc@example.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1660252744369,\n  \"workflowName\": \"LoopTestWithSwitch\",\n  \"workflowVersion\": 3\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/dynamicFork/externalizedInput.json",
    "content": "{\n  \"ownerApp\": \"nq_mwi_conductor_ui_server\",\n  \"createTime\": 1656008300448,\n  \"status\": \"COMPLETED\",\n  \"endTime\": 1656008301210,\n  \"workflowId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n  \"tasks\": [\n    {\n      \"taskType\": \"FORK\",\n      \"status\": \"COMPLETED\",\n      \"externalInputPayloadStoragePath\": \"task/input/c8569b00-62d9-4a4b-b918-93a4bf4e6004.json\",\n      \"referenceTaskName\": \"dynamic_tasks\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"pollCount\": 0,\n      \"taskDefName\": \"FORK\",\n      \"scheduledTime\": 1656008300534,\n      \"startTime\": 1656008300525,\n      \"endTime\": 1656008300525,\n      \"updateTime\": 1656008300549,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"b49dc1be-66eb-4816-8ee1-6aaea25f14ba\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {},\n      \"workflowTask\": {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -9,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 46,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"first_task\",\n      \"retryCount\": 0,\n      \"seq\": 2,\n      \"pollCount\": 0,\n      \"taskDefName\": \"first_task\",\n      \"scheduledTime\": 1656008300535,\n      \"startTime\": 1656008300527,\n      \"endTime\": 1656008300922,\n      \"updateTime\": 1656008300628,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"6ce064a7-7ef3-413c-b6af-318cb7e6751e\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 45\n      },\n      \"workflowTask\": {\n        \"name\": \"first_task\",\n        \"taskReferenceName\": \"first_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -8,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 234,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"second_task\",\n      \"retryCount\": 0,\n      \"seq\": 3,\n      \"pollCount\": 0,\n      \"taskDefName\": \"second_task\",\n      \"scheduledTime\": 1656008300537,\n      \"startTime\": 1656008300529,\n      \"endTime\": 1656008300977,\n      \"updateTime\": 1656008300683,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"b936ea24-9c3e-4651-8702-2ff5aa4dd579\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 233\n      },\n      \"workflowTask\": {\n        \"name\": \"second_task\",\n        \"taskReferenceName\": \"second_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -8,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 12,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"third_task\",\n      \"retryCount\": 0,\n      \"seq\": 4,\n      \"pollCount\": 0,\n      \"taskDefName\": \"third_task\",\n      \"scheduledTime\": 1656008300540,\n      \"startTime\": 1656008300531,\n      \"endTime\": 1656008301031,\n      \"updateTime\": 1656008300760,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"bf2963cd-e545-4a26-b533-2ae760e77634\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 11\n      },\n      \"workflowTask\": {\n        \"name\": \"third_task\",\n        \"taskReferenceName\": \"third_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -9,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"JOIN\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"joinOn\": [\"first_task\", \"second_task\", \"third_task\"]\n      },\n      \"referenceTaskName\": \"join_dynamic\",\n      \"retryCount\": 0,\n      \"seq\": 5,\n      \"pollCount\": 0,\n      \"taskDefName\": \"JOIN\",\n      \"scheduledTime\": 1656008300542,\n      \"startTime\": 1656008300531,\n      \"endTime\": 1656008301085,\n      \"updateTime\": 1656008300831,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"25ddfe4d-eaf0-4171-964f-9b53ad06002b\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"second_task\": {\n          \"result\": 233\n        },\n        \"third_task\": {\n          \"result\": 11\n        },\n        \"first_task\": {\n          \"result\": 45\n        }\n      },\n      \"workflowTask\": {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -11,\n      \"loopOverTask\": false\n    }\n  ],\n  \"input\": {\n    \"tasksJSON\": [\n      {\n        \"name\": \"first_task\",\n        \"taskReferenceName\": \"first_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"second_task\",\n        \"taskReferenceName\": \"second_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"third_task\",\n        \"taskReferenceName\": \"third_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"tasksInputJSON\": {\n      \"first_task\": {\n        \"number\": 46\n      },\n      \"second_task\": {\n        \"number\": 234\n      },\n      \"third_task\": {\n        \"number\": 12\n      }\n    }\n  },\n  \"output\": {\n    \"second_task\": {\n      \"result\": 233\n    },\n    \"third_task\": {\n      \"result\": 11\n    },\n    \"first_task\": {\n      \"result\": 45\n    }\n  },\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [],\n  \"workflowDefinition\": {\n    \"createTime\": 1656005417724,\n    \"updateTime\": 1656005671608,\n    \"name\": \"example_dynamic_tasks\",\n    \"description\": \"A workflow that allows dynamic execution of tasks\",\n    \"version\": 2,\n    \"tasks\": [\n      {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [\"tasksJSON\", \"tasksInputJSON\"],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": true,\n    \"ownerEmail\": \"mwi-workflow-dev@netflix.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1656008300448,\n  \"workflowName\": \"example_dynamic_tasks\",\n  \"workflowVersion\": 2\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/dynamicFork/noneSpawned.json",
    "content": "{\n  \"ownerApp\": \"peterl@netflix.com\",\n  \"createTime\": 1656096815470,\n  \"status\": \"COMPLETED\",\n  \"endTime\": 1656096815832,\n  \"workflowId\": \"fe4efd7b-73ea-4c48-8147-840fa4e1e63b\",\n  \"tasks\": [\n    {\n      \"taskType\": \"FORK\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"forkedTaskDefs\": [],\n        \"forkedTasks\": []\n      },\n      \"referenceTaskName\": \"dynamic_tasks\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"pollCount\": 0,\n      \"taskDefName\": \"FORK\",\n      \"scheduledTime\": 1656096815568,\n      \"startTime\": 1656096815566,\n      \"endTime\": 1656096815566,\n      \"updateTime\": 1656096815577,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"fe4efd7b-73ea-4c48-8147-840fa4e1e63b\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"01a706f7-c28d-4287-a179-8075d16ff201\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {},\n      \"workflowTask\": {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -2,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"JOIN\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"joinOn\": []\n      },\n      \"referenceTaskName\": \"join_dynamic\",\n      \"retryCount\": 0,\n      \"seq\": 2,\n      \"pollCount\": 0,\n      \"taskDefName\": \"JOIN\",\n      \"scheduledTime\": 1656096815570,\n      \"startTime\": 1656096815566,\n      \"endTime\": 1656096815708,\n      \"updateTime\": 1656096815631,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"fe4efd7b-73ea-4c48-8147-840fa4e1e63b\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"00781ceb-1931-4537-a4f5-ab38b04015f1\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {},\n      \"workflowTask\": {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -4,\n      \"loopOverTask\": false\n    }\n  ],\n  \"input\": {\n    \"tasksJSON\": [],\n    \"tasksInputJSON\": {}\n  },\n  \"output\": {},\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [],\n  \"workflowDefinition\": {\n    \"createTime\": 1656005417724,\n    \"updateTime\": 1656005671608,\n    \"name\": \"example_dynamic_tasks\",\n    \"description\": \"A workflow that allows dynamic execution of tasks\",\n    \"version\": 2,\n    \"tasks\": [\n      {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [\"tasksJSON\", \"tasksInputJSON\"],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": true,\n    \"ownerEmail\": \"mwi-workflow-dev@netflix.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1656096815470,\n  \"workflowName\": \"example_dynamic_tasks\",\n  \"workflowVersion\": 2\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/dynamicFork/notExecuted.json",
    "content": "{\n  \"ownerApp\": \"nq_mwi_conductor_ui_server\",\n  \"createTime\": 1656017015654,\n  \"status\": \"COMPLETED\",\n  \"endTime\": 1656017016239,\n  \"workflowId\": \"5daaf83f-e1f4-454f-9293-4d0443c6c729\",\n  \"tasks\": [\n    {\n      \"taskType\": \"SWITCH\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"case\": \"false\"\n      },\n      \"referenceTaskName\": \"switch_task\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"pollCount\": 0,\n      \"taskDefName\": \"SWITCH\",\n      \"scheduledTime\": 1656017015966,\n      \"startTime\": 1656017015955,\n      \"endTime\": 1656017016105,\n      \"updateTime\": 1656017015987,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"5daaf83f-e1f4-454f-9293-4d0443c6c729\",\n      \"workflowType\": \"example_dynamic_tasks_switch\",\n      \"taskId\": \"d0d6ab7b-ac8f-4754-9020-3ea13429d92b\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"evaluationResult\": [\"false\"]\n      },\n      \"workflowTask\": {\n        \"name\": \"switch_task\",\n        \"taskReferenceName\": \"switch_task\",\n        \"inputParameters\": {\n          \"switchCaseValue\": \"${workflow.input.runFork}\"\n        },\n        \"type\": \"SWITCH\",\n        \"decisionCases\": {\n          \"true\": [\n            {\n              \"name\": \"dynamic_tasks\",\n              \"taskReferenceName\": \"dynamic_tasks\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n                \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"decisionCases\": {},\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            },\n            {\n              \"name\": \"join\",\n              \"taskReferenceName\": \"join_dynamic\",\n              \"inputParameters\": {},\n              \"type\": \"JOIN\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ]\n        },\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": [],\n        \"evaluatorType\": \"value-param\",\n        \"expression\": \"switchCaseValue\"\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -11,\n      \"loopOverTask\": false\n    }\n  ],\n  \"input\": {\n    \"runFork\": false\n  },\n  \"output\": {\n    \"evaluationResult\": [\"false\"]\n  },\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [],\n  \"workflowDefinition\": {\n    \"createTime\": 1656015554295,\n    \"updateTime\": 1656015597435,\n    \"name\": \"example_dynamic_tasks_switch\",\n    \"description\": \"A workflow that allows dynamic execution of tasks\",\n    \"version\": 2,\n    \"tasks\": [\n      {\n        \"name\": \"switch_task\",\n        \"taskReferenceName\": \"switch_task\",\n        \"inputParameters\": {\n          \"switchCaseValue\": \"${workflow.input.runFork}\"\n        },\n        \"type\": \"SWITCH\",\n        \"decisionCases\": {\n          \"true\": [\n            {\n              \"name\": \"dynamic_tasks\",\n              \"taskReferenceName\": \"dynamic_tasks\",\n              \"inputParameters\": {\n                \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n                \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n              },\n              \"type\": \"FORK_JOIN_DYNAMIC\",\n              \"decisionCases\": {},\n              \"dynamicForkTasksParam\": \"dynamicTasks\",\n              \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            },\n            {\n              \"name\": \"join\",\n              \"taskReferenceName\": \"join_dynamic\",\n              \"inputParameters\": {},\n              \"type\": \"JOIN\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ]\n        },\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": [],\n        \"evaluatorType\": \"value-param\",\n        \"expression\": \"switchCaseValue\"\n      }\n    ],\n    \"inputParameters\": [\"tasksJSON\", \"tasksInputJSON\"],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": false,\n    \"ownerEmail\": \"peterl@netflix.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1656017015654,\n  \"workflowName\": \"example_dynamic_tasks_switch\",\n  \"workflowVersion\": 2\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/dynamicFork/oneFailed.json",
    "content": "{\n  \"ownerApp\": \"nq_mwi_conductor_ui_server\",\n  \"createTime\": 1656008463986,\n  \"status\": \"FAILED\",\n  \"endTime\": 1656008464720,\n  \"workflowId\": \"d4b14434-73a7-4be9-b085-d4b40b30856e\",\n  \"tasks\": [\n    {\n      \"taskType\": \"FORK\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"forkedTaskDefs\": [\n          {\n            \"name\": \"first_task\",\n            \"taskReferenceName\": \"first_task\",\n            \"inputParameters\": {\n              \"number\": \"${number}\",\n              \"scriptExpression\": \"return $.number - 1;\"\n            },\n            \"type\": \"LAMBDA\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"second_task\",\n            \"taskReferenceName\": \"second_task\",\n            \"inputParameters\": {\n              \"number\": \"${number}\",\n              \"scriptExpression\": null\n            },\n            \"type\": \"LAMBDA\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"third_task\",\n            \"taskReferenceName\": \"third_task\",\n            \"inputParameters\": {\n              \"number\": \"${number}\",\n              \"scriptExpression\": \"return $.number - 1;\"\n            },\n            \"type\": \"LAMBDA\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"forkedTasks\": [\"first_task\", \"second_task\", \"third_task\"]\n      },\n      \"referenceTaskName\": \"dynamic_tasks\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"pollCount\": 0,\n      \"taskDefName\": \"FORK\",\n      \"scheduledTime\": 1656008464075,\n      \"startTime\": 1656008464065,\n      \"endTime\": 1656008464065,\n      \"updateTime\": 1656008464094,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"d4b14434-73a7-4be9-b085-d4b40b30856e\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"743d552b-a683-473d-831e-bb7fae622e08\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {},\n      \"workflowTask\": {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -10,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 46,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"first_task\",\n      \"retryCount\": 0,\n      \"seq\": 2,\n      \"pollCount\": 0,\n      \"taskDefName\": \"first_task\",\n      \"scheduledTime\": 1656008464077,\n      \"startTime\": 1656008464069,\n      \"endTime\": 1656008464374,\n      \"updateTime\": 1656008464148,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"d4b14434-73a7-4be9-b085-d4b40b30856e\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"4ccaa5a8-59d0-40d7-b5f8-918f22c2536f\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 45\n      },\n      \"workflowTask\": {\n        \"name\": \"first_task\",\n        \"taskReferenceName\": \"first_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -8,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"FAILED\",\n      \"inputData\": {\n        \"number\": 234,\n        \"scriptExpression\": null\n      },\n      \"referenceTaskName\": \"second_task\",\n      \"retryCount\": 0,\n      \"seq\": 3,\n      \"pollCount\": 0,\n      \"taskDefName\": \"second_task\",\n      \"scheduledTime\": 1656008464081,\n      \"startTime\": 1656008464072,\n      \"endTime\": 1656008464428,\n      \"updateTime\": 1656008464202,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"d4b14434-73a7-4be9-b085-d4b40b30856e\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"33e96a6e-5096-4004-ac29-87e0732232f5\",\n      \"reasonForIncompletion\": \"Empty 'scriptExpression' in Lambda task's input parameters. A non-empty String value must be provided.\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {},\n      \"workflowTask\": {\n        \"name\": \"second_task\",\n        \"taskReferenceName\": \"second_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": null\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -9,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 12,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"third_task\",\n      \"retryCount\": 0,\n      \"seq\": 4,\n      \"pollCount\": 0,\n      \"taskDefName\": \"third_task\",\n      \"scheduledTime\": 1656008464083,\n      \"startTime\": 1656008464073,\n      \"endTime\": 1656008464482,\n      \"updateTime\": 1656008464257,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"d4b14434-73a7-4be9-b085-d4b40b30856e\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"951ef8b2-fa61-4896-bdf9-e781fded8e82\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 11\n      },\n      \"workflowTask\": {\n        \"name\": \"third_task\",\n        \"taskReferenceName\": \"third_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -10,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"JOIN\",\n      \"status\": \"FAILED\",\n      \"inputData\": {\n        \"joinOn\": [\"first_task\", \"second_task\", \"third_task\"]\n      },\n      \"referenceTaskName\": \"join_dynamic\",\n      \"retryCount\": 0,\n      \"seq\": 5,\n      \"pollCount\": 0,\n      \"taskDefName\": \"JOIN\",\n      \"scheduledTime\": 1656008464086,\n      \"startTime\": 1656008464073,\n      \"endTime\": 1656008464537,\n      \"updateTime\": 1656008464312,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"d4b14434-73a7-4be9-b085-d4b40b30856e\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"6471a9e8-3049-40f2-9ab8-c75876c9c1a3\",\n      \"reasonForIncompletion\": \"Empty 'scriptExpression' in Lambda task's input parameters. A non-empty String value must be provided. \",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"first_task\": {\n          \"result\": 45\n        }\n      },\n      \"workflowTask\": {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -13,\n      \"loopOverTask\": false\n    }\n  ],\n  \"input\": {\n    \"tasksJSON\": [\n      {\n        \"name\": \"first_task\",\n        \"taskReferenceName\": \"first_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"second_task\",\n        \"taskReferenceName\": \"second_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": null\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"third_task\",\n        \"taskReferenceName\": \"third_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"tasksInputJSON\": {\n      \"first_task\": {\n        \"number\": 46\n      },\n      \"second_task\": {\n        \"number\": 234\n      },\n      \"third_task\": {\n        \"number\": 12\n      }\n    }\n  },\n  \"output\": {\n    \"first_task\": {\n      \"result\": 45\n    }\n  },\n  \"reasonForIncompletion\": \"Empty 'scriptExpression' in Lambda task's input parameters. A non-empty String value must be provided.\",\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [\"second_task\", \"join_dynamic\"],\n  \"workflowDefinition\": {\n    \"createTime\": 1656005417724,\n    \"updateTime\": 1656005671608,\n    \"name\": \"example_dynamic_tasks\",\n    \"description\": \"A workflow that allows dynamic execution of tasks\",\n    \"version\": 2,\n    \"tasks\": [\n      {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [\"tasksJSON\", \"tasksInputJSON\"],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": true,\n    \"ownerEmail\": \"mwi-workflow-dev@netflix.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1656008463986,\n  \"workflowName\": \"example_dynamic_tasks\",\n  \"workflowVersion\": 2\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/dynamicFork/success.json",
    "content": "{\n  \"ownerApp\": \"nq_mwi_conductor_ui_server\",\n  \"createTime\": 1656008300448,\n  \"status\": \"COMPLETED\",\n  \"endTime\": 1656008301210,\n  \"workflowId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n  \"tasks\": [\n    {\n      \"taskType\": \"FORK\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"forkedTaskDefs\": [\n          {\n            \"name\": \"first_task\",\n            \"taskReferenceName\": \"first_task\",\n            \"inputParameters\": {\n              \"number\": \"${number}\",\n              \"scriptExpression\": \"return $.number - 1;\"\n            },\n            \"type\": \"LAMBDA\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"second_task\",\n            \"taskReferenceName\": \"second_task\",\n            \"inputParameters\": {\n              \"number\": \"${number}\",\n              \"scriptExpression\": \"return $.number - 1;\"\n            },\n            \"type\": \"LAMBDA\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"third_task\",\n            \"taskReferenceName\": \"third_task\",\n            \"inputParameters\": {\n              \"number\": \"${number}\",\n              \"scriptExpression\": \"return $.number - 1;\"\n            },\n            \"type\": \"LAMBDA\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"forkedTasks\": [\"first_task\", \"second_task\", \"third_task\"]\n      },\n      \"referenceTaskName\": \"dynamic_tasks\",\n      \"retryCount\": 0,\n      \"seq\": 1,\n      \"pollCount\": 0,\n      \"taskDefName\": \"FORK\",\n      \"scheduledTime\": 1656008300534,\n      \"startTime\": 1656008300525,\n      \"endTime\": 1656008300525,\n      \"updateTime\": 1656008300549,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"b49dc1be-66eb-4816-8ee1-6aaea25f14ba\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {},\n      \"workflowTask\": {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -9,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 46,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"first_task\",\n      \"retryCount\": 0,\n      \"seq\": 2,\n      \"pollCount\": 0,\n      \"taskDefName\": \"first_task\",\n      \"scheduledTime\": 1656008300535,\n      \"startTime\": 1656008300527,\n      \"endTime\": 1656008300922,\n      \"updateTime\": 1656008300628,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"6ce064a7-7ef3-413c-b6af-318cb7e6751e\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 45\n      },\n      \"workflowTask\": {\n        \"name\": \"first_task\",\n        \"taskReferenceName\": \"first_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -8,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 234,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"second_task\",\n      \"retryCount\": 0,\n      \"seq\": 3,\n      \"pollCount\": 0,\n      \"taskDefName\": \"second_task\",\n      \"scheduledTime\": 1656008300537,\n      \"startTime\": 1656008300529,\n      \"endTime\": 1656008300977,\n      \"updateTime\": 1656008300683,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"b936ea24-9c3e-4651-8702-2ff5aa4dd579\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 233\n      },\n      \"workflowTask\": {\n        \"name\": \"second_task\",\n        \"taskReferenceName\": \"second_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -8,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"LAMBDA\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"number\": 12,\n        \"scriptExpression\": \"return $.number - 1;\"\n      },\n      \"referenceTaskName\": \"third_task\",\n      \"retryCount\": 0,\n      \"seq\": 4,\n      \"pollCount\": 0,\n      \"taskDefName\": \"third_task\",\n      \"scheduledTime\": 1656008300540,\n      \"startTime\": 1656008300531,\n      \"endTime\": 1656008301031,\n      \"updateTime\": 1656008300760,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"bf2963cd-e545-4a26-b533-2ae760e77634\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"result\": 11\n      },\n      \"workflowTask\": {\n        \"name\": \"third_task\",\n        \"taskReferenceName\": \"third_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -9,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"JOIN\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"joinOn\": [\"first_task\", \"second_task\", \"third_task\"]\n      },\n      \"referenceTaskName\": \"join_dynamic\",\n      \"retryCount\": 0,\n      \"seq\": 5,\n      \"pollCount\": 0,\n      \"taskDefName\": \"JOIN\",\n      \"scheduledTime\": 1656008300542,\n      \"startTime\": 1656008300531,\n      \"endTime\": 1656008301085,\n      \"updateTime\": 1656008300831,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"e66254b6-388d-43a6-b890-c518df832e51\",\n      \"workflowType\": \"example_dynamic_tasks\",\n      \"taskId\": \"25ddfe4d-eaf0-4171-964f-9b53ad06002b\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"second_task\": {\n          \"result\": 233\n        },\n        \"third_task\": {\n          \"result\": 11\n        },\n        \"first_task\": {\n          \"result\": 45\n        }\n      },\n      \"workflowTask\": {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": -11,\n      \"loopOverTask\": false\n    }\n  ],\n  \"input\": {\n    \"tasksJSON\": [\n      {\n        \"name\": \"first_task\",\n        \"taskReferenceName\": \"first_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"second_task\",\n        \"taskReferenceName\": \"second_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"third_task\",\n        \"taskReferenceName\": \"third_task\",\n        \"inputParameters\": {\n          \"number\": \"${number}\",\n          \"scriptExpression\": \"return $.number - 1;\"\n        },\n        \"type\": \"LAMBDA\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"tasksInputJSON\": {\n      \"first_task\": {\n        \"number\": 46\n      },\n      \"second_task\": {\n        \"number\": 234\n      },\n      \"third_task\": {\n        \"number\": 12\n      }\n    }\n  },\n  \"output\": {\n    \"second_task\": {\n      \"result\": 233\n    },\n    \"third_task\": {\n      \"result\": 11\n    },\n    \"first_task\": {\n      \"result\": 45\n    }\n  },\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [],\n  \"workflowDefinition\": {\n    \"createTime\": 1656005417724,\n    \"updateTime\": 1656005671608,\n    \"name\": \"example_dynamic_tasks\",\n    \"description\": \"A workflow that allows dynamic execution of tasks\",\n    \"version\": 2,\n    \"tasks\": [\n      {\n        \"name\": \"dynamic_tasks\",\n        \"taskReferenceName\": \"dynamic_tasks\",\n        \"inputParameters\": {\n          \"dynamicTasks\": \"${workflow.input.tasksJSON}\",\n          \"dynamicTasksInput\": \"${workflow.input.tasksInputJSON}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"dynamicTasks\",\n        \"dynamicForkTasksInputParamName\": \"dynamicTasksInput\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"join\",\n        \"taskReferenceName\": \"join_dynamic\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [\"tasksJSON\", \"tasksInputJSON\"],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": true,\n    \"ownerEmail\": \"mwi-workflow-dev@netflix.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1656008300448,\n  \"workflowName\": \"example_dynamic_tasks\",\n  \"workflowVersion\": 2\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/dynamicFork.json",
    "content": "{\n  \"ownerApp\": \"\",\n  \"createTime\": 1608153919527,\n  \"status\": \"TERMINATED\",\n  \"endTime\": 1608173713271,\n  \"workflowId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n  \"parentWorkflowId\": \"9f9057d0-86c4-464f-b4d0-1606e66798fd\",\n  \"parentWorkflowTaskId\": \"1f908fd3-b02f-47f1-b223-7625bc2da1a3\",\n  \"tasks\": [\n    {\n      \"taskType\": \"FORK\",\n      \"status\": \"COMPLETED\",\n      \"inputData\": {\n        \"forkedTaskDefs\": [\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_4cf45860-3fe3-11eb-8740-12f4b5a75f47_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_4f936d40-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_5256abf0-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_54ebd5c0-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_57784d00-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_5c51e890-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"processshot\",\n            \"taskReferenceName\": \"processshot_5ec00260-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n            \"inputParameters\": {},\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.shotprocessing_v2\"\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"forkedTasks\": [\n          \"processshot_4cf45860-3fe3-11eb-8740-12f4b5a75f47_1.2\",\n          \"processshot_4f936d40-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n          \"processshot_5256abf0-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n          \"processshot_54ebd5c0-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n          \"processshot_57784d00-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n          \"processshot_59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n          \"processshot_5c51e890-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n          \"processshot_5ec00260-3fe3-11eb-bd3b-1230be2091b7_1.2\"\n        ]\n      },\n      \"referenceTaskName\": \"shot_processing\",\n      \"retryCount\": 0,\n      \"seq\": 52,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 0,\n      \"taskDefName\": \"FORK\",\n      \"scheduledTime\": 1608154318377,\n      \"startTime\": 0,\n      \"endTime\": 1608154318334,\n      \"updateTime\": 1608154318402,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": true,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"taskId\": \"1a859277-c27e-4d28-bb57-271ea5a16f96\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {},\n      \"workflowTask\": {\n        \"name\": \"asset_processing\",\n        \"taskReferenceName\": \"shot_processing\",\n        \"inputParameters\": {\n          \"taskDefs\": \"${prepareShotProcessingTasks.output.result.taskDefs}\",\n          \"taskInputs\": \"${prepareShotProcessingTasks.output.result.taskInputs}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"taskDefs\",\n        \"dynamicForkTasksInputParamName\": \"taskInputs\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 0,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n• *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"4cf51bb0-3fe3-11eb-8740-12f4b5a75f47\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"4cf45860-3fe3-11eb-8740-12f4b5a75f47\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"4cf45860-3fe3-11eb-8740-12f4b5a75f47:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_4cf45860-3fe3-11eb-8740-12f4b5a75f47_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 53,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318379,\n      \"startTime\": 1608154318538,\n      \"endTime\": 1608173718867,\n      \"updateTime\": 1608154318720,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"30f507f2-fc34-4123-9ffb-07af344a56b0\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"5f8285f0-72de-4233-a201-1599b558e645\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_4cf45860-3fe3-11eb-8740-12f4b5a75f47_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"5f8285f0-72de-4233-a201-1599b558e645\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211706,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n• *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"4f943090-3fe3-11eb-9af1-12a7f1c641e3\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"4f936d40-3fe3-11eb-9af1-12a7f1c641e3\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"4f936d40-3fe3-11eb-9af1-12a7f1c641e3:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_4f936d40-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 54,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318382,\n      \"startTime\": 1608154318569,\n      \"endTime\": 1608173719056,\n      \"updateTime\": 1608154318774,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"fa90e44a-d68c-40b8-84a7-ebbcd21b94e3\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"ef25bcc5-f5e6-4172-99f5-7fb76b1f11f8\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_4f936d40-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"ef25bcc5-f5e6-4172-99f5-7fb76b1f11f8\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211652,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n• *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"52576f40-3fe3-11eb-8a3e-12ffdb69dc47\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"5256abf0-3fe3-11eb-8a3e-12ffdb69dc47\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"5256abf0-3fe3-11eb-8a3e-12ffdb69dc47:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_5256abf0-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 55,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318384,\n      \"startTime\": 1608154318582,\n      \"endTime\": 1608173719208,\n      \"updateTime\": 1608154318774,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"0cde3f5c-3fb0-4c4e-b74c-3a7bcabf892c\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"28564fa1-c0d3-45fa-948d-969c09f49af5\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_5256abf0-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"28564fa1-c0d3-45fa-948d-969c09f49af5\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211652,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n• *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"54ec9910-3fe3-11eb-bd3b-1230be2091b7\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"54ebd5c0-3fe3-11eb-bd3b-1230be2091b7\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"54ebd5c0-3fe3-11eb-bd3b-1230be2091b7:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_54ebd5c0-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 56,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318386,\n      \"startTime\": 1608154318637,\n      \"endTime\": 1608173719368,\n      \"updateTime\": 1608154318882,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"88595b13-34e2-4442-994d-2875f21f44f7\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"ebce2351-2ce2-4920-899d-fffbf66be4f4\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_54ebd5c0-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"ebce2351-2ce2-4920-899d-fffbf66be4f4\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211544,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n• *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"57784d02-3fe3-11eb-9af1-12a7f1c641e3\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"57784d00-3fe3-11eb-9af1-12a7f1c641e3\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"57784d00-3fe3-11eb-9af1-12a7f1c641e3:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_57784d00-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 57,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318387,\n      \"startTime\": 1608154318657,\n      \"endTime\": 1608173719530,\n      \"updateTime\": 1608154318987,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"f2e835e5-ce1b-4d9b-91f7-5ffa5b43a40e\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"7e326487-f315-43b6-aab4-279c82155132\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_57784d00-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"7e326487-f315-43b6-aab4-279c82155132\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211439,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n• *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"59f3ad42-3fe3-11eb-8a3e-12ffdb69dc47\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 58,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318390,\n      \"startTime\": 1608154318739,\n      \"endTime\": 1608173719684,\n      \"updateTime\": 1608154318987,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"74fbb03c-5a2c-46e9-b429-19053cd1a3ec\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"2be7d404-3732-4e90-88e5-b0b221aeeda1\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"2be7d404-3732-4e90-88e5-b0b221aeeda1\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211439,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n��� *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"5c52abe1-3fe3-11eb-9af1-12a7f1c641e3\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"5c51e890-3fe3-11eb-9af1-12a7f1c641e3\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"5c51e890-3fe3-11eb-9af1-12a7f1c641e3:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_5c51e890-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 59,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318393,\n      \"startTime\": 1608154318748,\n      \"endTime\": 1608173719850,\n      \"updateTime\": 1608154319021,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"abb3eebf-8430-446a-a5fa-dd26cf367a95\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"8f3e3098-72c7-40b7-8547-4cc2293a6def\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_5c51e890-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"8f3e3098-72c7-40b7-8547-4cc2293a6def\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211405,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"SUB_WORKFLOW\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"playlistId\": 375594,\n        \"metadata\": {\n          \"submission_note\": null,\n          \"version_name\": null,\n          \"scope_of_work\": null,\n          \"reason_for_review\": \"• *SUBMITTING FOR* - null\\n• *SCOPE OF WORK* - null\\n• *NOTES* - null\",\n          \"vendor\": null,\n          \"link\": null,\n          \"submitting_for\": null,\n          \"sort_order\": null\n        },\n        \"subWorkflowTaskToDomain\": null,\n        \"subWorkflowName\": \"vfxmediareview.shotprocessing_v2\",\n        \"reviewAssetId\": {\n          \"versionId\": \"1.4\",\n          \"id\": \"5ec02971-3fe3-11eb-bd3b-1230be2091b7\"\n        },\n        \"vendorId\": null,\n        \"shotname\": null,\n        \"topLevelAssetId\": {\n          \"versionId\": \"1.2\",\n          \"id\": \"5ec00260-3fe3-11eb-bd3b-1230be2091b7\"\n        },\n        \"vendorName\": \"Netflix\",\n        \"reviewProjectId\": \"222\",\n        \"subWorkflowVersion\": 1,\n        \"playlistName\": \"HUB-3087_20201216_01\",\n        \"subWorkflowDefinition\": null,\n        \"workflowInput\": {},\n        \"sgAmpRefId\": \"5ec00260-3fe3-11eb-bd3b-1230be2091b7:1.2\",\n        \"reviewServer\": \"netflix-review-staging.shotgunstudio.com\"\n      },\n      \"referenceTaskName\": \"processshot_5ec00260-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n      \"retryCount\": 0,\n      \"seq\": 60,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 1,\n      \"taskDefName\": \"processshot\",\n      \"scheduledTime\": 1608154318394,\n      \"startTime\": 1608154318871,\n      \"endTime\": 1608173719989,\n      \"updateTime\": 1608154319182,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"69630514-33df-42ba-805e-af17760ba111\",\n      \"callbackAfterSeconds\": 30,\n      \"outputData\": {\n        \"subWorkflowId\": \"b53c817d-19a5-436e-b26c-0b57d334b122\"\n      },\n      \"workflowTask\": {\n        \"name\": \"processshot\",\n        \"taskReferenceName\": \"processshot_5ec00260-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n        \"inputParameters\": {},\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotprocessing_v2\"\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subWorkflowId\": \"b53c817d-19a5-436e-b26c-0b57d334b122\",\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 47847211244,\n      \"loopOverTask\": false\n    },\n    {\n      \"taskType\": \"JOIN\",\n      \"status\": \"CANCELED\",\n      \"inputData\": {\n        \"joinOn\": [\n          \"processshot_4cf45860-3fe3-11eb-8740-12f4b5a75f47_1.2\",\n          \"processshot_4f936d40-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n          \"processshot_5256abf0-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n          \"processshot_54ebd5c0-3fe3-11eb-bd3b-1230be2091b7_1.2\",\n          \"processshot_57784d00-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n          \"processshot_59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47_1.2\",\n          \"processshot_5c51e890-3fe3-11eb-9af1-12a7f1c641e3_1.2\",\n          \"processshot_5ec00260-3fe3-11eb-bd3b-1230be2091b7_1.2\"\n        ]\n      },\n      \"referenceTaskName\": \"shot_processing_join\",\n      \"retryCount\": 0,\n      \"seq\": 61,\n      \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"pollCount\": 0,\n      \"taskDefName\": \"JOIN\",\n      \"scheduledTime\": 1608154318395,\n      \"startTime\": 1608154318407,\n      \"endTime\": 1608173719994,\n      \"updateTime\": 1608154318407,\n      \"startDelayInSeconds\": 0,\n      \"retried\": false,\n      \"executed\": false,\n      \"callbackFromWorker\": true,\n      \"responseTimeoutSeconds\": 0,\n      \"workflowInstanceId\": \"637364c4-31bf-4c50-8c81-c04d1dafe27f\",\n      \"workflowType\": \"pipelines.vfxmediareview\",\n      \"taskId\": \"80a2e505-2f54-4d40-bcee-17f8c6a39b71\",\n      \"callbackAfterSeconds\": 0,\n      \"outputData\": {\n        \"processshot_59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47_1.2\": {},\n        \"processshot_5c51e890-3fe3-11eb-9af1-12a7f1c641e3_1.2\": {},\n        \"processshot_54ebd5c0-3fe3-11eb-bd3b-1230be2091b7_1.2\": {},\n        \"processshot_4cf45860-3fe3-11eb-8740-12f4b5a75f47_1.2\": {},\n        \"processshot_5ec00260-3fe3-11eb-bd3b-1230be2091b7_1.2\": {},\n        \"processshot_4f936d40-3fe3-11eb-9af1-12a7f1c641e3_1.2\": {},\n        \"processshot_5256abf0-3fe3-11eb-8a3e-12ffdb69dc47_1.2\": {},\n        \"processshot_57784d00-3fe3-11eb-9af1-12a7f1c641e3_1.2\": {}\n      },\n      \"workflowTask\": {\n        \"name\": \"shot_processing_join\",\n        \"taskReferenceName\": \"shot_processing_join\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      \"rateLimitPerFrequency\": 0,\n      \"rateLimitFrequencyInSeconds\": 0,\n      \"workflowPriority\": 0,\n      \"iteration\": 0,\n      \"subworkflowChanged\": false,\n      \"taskDefinition\": null,\n      \"queueWaitTime\": 12,\n      \"loopOverTask\": false\n    }\n  ],\n  \"input\": {\n    \"pipelineInput\": {\n      \"pipelineConfig\": {\n        \"requestNamespace\": \"pipelines\",\n        \"requestType\": \"vfxmediareview\",\n        \"type\": \"vfxmediareview\"\n      },\n      \"primaryRequestNamespace\": \"pipelines\",\n      \"primaryRequestId\": \"20fa83b7-9b91-406a-9644-4fd62aea8b4b\",\n      \"user\": \"jcronk@netflix.com\",\n      \"inputParameters\": {\n        \"submissionId\": \"HUB-3087_20201216_01\",\n        \"ownerUser\": \"jcronk@netflix.com\",\n        \"submissionNodeId\": \"229590a0-3fe5-11eb-9910-12d0bc41bfa1\",\n        \"vendorId\": null,\n        \"reviewType\": \"PRODUCTION\",\n        \"movieId\": 81112280,\n        \"projectId\": \"92311fe0-5bd7-11e9-b8ed-0e4d3942d506\"\n      },\n      \"pipelineId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n      \"primaryRequestType\": \"vfxmediareview\"\n    }\n  },\n  \"output\": {\n    \"processshot_59f3ad40-3fe3-11eb-8a3e-12ffdb69dc47_1.2\": {},\n    \"processshot_5c51e890-3fe3-11eb-9af1-12a7f1c641e3_1.2\": {},\n    \"processshot_54ebd5c0-3fe3-11eb-bd3b-1230be2091b7_1.2\": {},\n    \"processshot_4cf45860-3fe3-11eb-8740-12f4b5a75f47_1.2\": {},\n    \"processshot_5ec00260-3fe3-11eb-bd3b-1230be2091b7_1.2\": {},\n    \"processshot_4f936d40-3fe3-11eb-9af1-12a7f1c641e3_1.2\": {},\n    \"processshot_5256abf0-3fe3-11eb-8a3e-12ffdb69dc47_1.2\": {},\n    \"processshot_57784d00-3fe3-11eb-9af1-12a7f1c641e3_1.2\": {}\n  },\n  \"correlationId\": \"6980f3f8-9077-45ca-9f0f-5d9545799a61\",\n  \"reasonForIncompletion\": \"Parent workflow has been terminated with status TIMED_OUT\",\n  \"taskToDomain\": {},\n  \"failedReferenceTaskNames\": [],\n  \"workflowDefinition\": {\n    \"updateTime\": 1608073180721,\n    \"name\": \"pipelines.vfxmediareview\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"stl.pipeline.init\",\n        \"taskReferenceName\": \"initializePipeline\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082385855,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.init\",\n          \"description\": \"Initial task for all pipeline workflows\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\"],\n          \"outputKeys\": [\"pipeline\"],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.get\",\n        \"taskReferenceName\": \"pipelineData\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082385634,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.get\",\n          \"description\": \"Read pipeline given pipeline id\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\"],\n          \"outputKeys\": [\"pipeline\"],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"reviewType\",\n        \"inputParameters\": {\n          \"inputData\": \"${pipelineData.output.request.request.data.reviewType}\",\n          \"expression\": \". | ascii_downcase\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.getProjectSchema\",\n        \"taskReferenceName\": \"reviewServerSchema\",\n        \"inputParameters\": {\n          \"schemaGroup\": \"PIPELINE\",\n          \"schemaType\": \"${reviewType.output.result}review\",\n          \"projectId\": \"${pipelineData.output.request.request.data.projectId}\",\n          \"user\": \"contenthub-system-user@netflix.com\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1588011760479,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.getProjectSchema\",\n          \"description\": \"Get Project Schema\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"requestId\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"ownerEmail\": \"cpe-che-backend@netflix.com\",\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"reviewServerConfig\",\n        \"inputParameters\": {\n          \"inputData\": \"${reviewServerSchema.output.output}\",\n          \"expression\": \".[0].schema as $s | if $s.server == null or $s.projectId == null then error(\\\"Configuration cannot be null\\\") else $s end\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"vendorDetails\",\n        \"taskReferenceName\": \"vendorDetails\",\n        \"inputParameters\": {\n          \"vendorId\": \"${pipelineData.output.request.request.data.vendorId}\"\n        },\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.vendordetails\",\n          \"version\": 1\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"cperequest_transition\",\n        \"taskReferenceName\": \"processSubmission\",\n        \"inputParameters\": {\n          \"namespace\": \"${pipelineData.output.request.request.namespace}\",\n          \"type\": \"${pipelineData.output.request.request.type}\",\n          \"requestId\": \"${pipelineData.output.request.request.id}\",\n          \"transitionName\": \"process\",\n          \"details\": {\n            \"skipPostProcess\": true\n          },\n          \"skipIfInState\": [\"IN_PROGRESS\"]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"updateTime\": 1604373979513,\n          \"updatedBy\": \"cperequest\",\n          \"name\": \"cperequest_transition\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\n            \"namespace\",\n            \"type\",\n            \"requestId\",\n            \"transitionName\",\n            \"currentState\",\n            \"currentVersion\",\n            \"assignee\",\n            \"clearAssignee\",\n            \"dueDate\",\n            \"clearDueDate\",\n            \"skipIfInState\",\n            \"transitionDetails\"\n          ],\n          \"outputKeys\": [\"request\"],\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"ownerEmail\": \"mce-workflow-infra@netflix.com\",\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.map\",\n        \"taskReferenceName\": \"details\",\n        \"inputParameters\": {\n          \"mappings\": {\n            \"contenthubBaseUrl\": \"@environment.getProperty('contenthub.url')\",\n            \"contenthubProjectUrl\": \"@environment.getProperty('contenthub.url').concat(\\\"/projects/${pipelineData.output.request.request.data.projectId}\\\")\"\n          }\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082386616,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.map\",\n          \"description\": \"General purpose task to apply expression language (SpEL) transforms\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"mappings\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"decide\",\n        \"taskReferenceName\": \"assetDiscoveryFlow\",\n        \"inputParameters\": {\n          \"status\": \"${pipelineData.output.request.request.data.skipAssetDiscovery}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"status\",\n        \"decisionCases\": {\n          \"true\": [\n            {\n              \"name\": \"stl.common.noop\",\n              \"taskReferenceName\": \"proceed\",\n              \"inputParameters\": {},\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1556082386204,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"stl.common.noop\",\n                \"description\": \"Do nothing\",\n                \"retryCount\": 3,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [],\n                \"outputKeys\": [],\n                \"timeoutPolicy\": \"RETRY\",\n                \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"inputTemplate\": {},\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1,\n                \"backoffScaleFactor\": 1\n              },\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ]\n        },\n        \"defaultCase\": [\n          {\n            \"name\": \"stl.common.jq\",\n            \"taskReferenceName\": \"getPipelineResourceIds\",\n            \"inputParameters\": {\n              \"inputData\": {\n                \"pipeline\": \"${pipelineData.output}\"\n              },\n              \"expression\": \"[.pipeline.pipelineId] + (.pipeline.pipelineResources // [] | map(.id))\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1556082387383,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"stl.common.jq\",\n              \"description\": \"Run JQ expression\",\n              \"retryCount\": 3,\n              \"timeoutSeconds\": 0,\n              \"inputKeys\": [\"expression\", \"inputData\"],\n              \"outputKeys\": [],\n              \"timeoutPolicy\": \"RETRY\",\n              \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n              \"retryDelaySeconds\": 120,\n              \"responseTimeoutSeconds\": 1800,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"backoffScaleFactor\": 1\n            },\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"stl.common.manageResourcesToPipelineTask\",\n            \"taskReferenceName\": \"detachPipelineResources\",\n            \"inputParameters\": {\n              \"pipelineResourceManagementInput\": {\n                \"detachById\": \"${getPipelineResourceIds.output.result}\"\n              },\n              \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n              \"contextUser\": \"pipelineapi\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1575590191136,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"stl.common.manageResourcesToPipelineTask\",\n              \"description\": \"Attach / detach resources on a pipeline\",\n              \"retryCount\": 3,\n              \"timeoutSeconds\": 0,\n              \"inputKeys\": [\n                \"pipelineId\",\n                \"contextUser\",\n                \"pipelineResourceManagementInput\"\n              ],\n              \"outputKeys\": [],\n              \"timeoutPolicy\": \"RETRY\",\n              \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n              \"retryDelaySeconds\": 120,\n              \"responseTimeoutSeconds\": 3000,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"backoffScaleFactor\": 1\n            },\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"assetdiscovery\",\n            \"taskReferenceName\": \"assetdiscovery\",\n            \"inputParameters\": {\n              \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n              \"folderId\": \"${pipelineData.output.request.request.data.submissionNodeId}\"\n            },\n            \"type\": \"SUB_WORKFLOW\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"subWorkflowParam\": {\n              \"name\": \"vfxmediareview.assetdiscovery\",\n              \"version\": 1\n            },\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"decide\",\n            \"taskReferenceName\": \"checkDiscoveryOutcome\",\n            \"inputParameters\": {\n              \"outcome\": \"${assetdiscovery.output.outcome}\"\n            },\n            \"type\": \"DECISION\",\n            \"caseValueParam\": \"outcome\",\n            \"decisionCases\": {\n              \"MANIFEST_NOT_FOUND\": [\n                {\n                  \"name\": \"stl.common.jq\",\n                  \"taskReferenceName\": \"prepareRecipientDomainsForManifestNotFound\",\n                  \"inputParameters\": {\n                    \"inputData\": {\n                      \"reviewServerConfig\": \"${reviewServerConfig.output.result}\",\n                      \"domains\": [\n                        {\n                          \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                        },\n                        {\n                          \"domains\": [\"netflix.amp.domain.production_vfx\"],\n                          \"partnerId\": \"${pipelineData.output.request.request.data.vendorId}\",\n                          \"ignoreIfPartnerNull\": true\n                        },\n                        {\n                          \"profiles\": [\"PRODUCTION_VFX_ADMIN\"],\n                          \"includeUsersWithProfile\": true\n                        }\n                      ],\n                      \"fallbackDomains\": [\n                        {\n                          \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                        }\n                      ]\n                    },\n                    \"expression\": \". as $in | $in.domains\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1556082387383,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"stl.common.jq\",\n                    \"description\": \"Run JQ expression\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\"expression\", \"inputData\"],\n                    \"outputKeys\": [],\n                    \"timeoutPolicy\": \"RETRY\",\n                    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                    \"retryDelaySeconds\": 120,\n                    \"responseTimeoutSeconds\": 1800,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"sendManifestErrorsEmail\",\n                  \"taskReferenceName\": \"sendManifestNotFoundEmail\",\n                  \"inputParameters\": {\n                    \"emailType\": \"SINGLE\",\n                    \"domains\": \"${prepareRecipientDomainsForManifestNotFound.output.result}\",\n                    \"additionalRecipients\": [\n                      \"vfx-media-review@netflix.com\",\n                      \"e1u9g2p6u1b8e5x7@netflix.slack.com\"\n                    ],\n                    \"additionalUsers\": [\n                      \"${pipelineData.output.request.request.data.ownerUser}\"\n                    ],\n                    \"eventName\": \"EVENT_MESSAGE_VFX_REVIEW_SUBMISSION\",\n                    \"eventType\": \"MESSAGE_VFX_REVIEW_SUBMISSION\",\n                    \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                    \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                    \"emailPayload\": {\n                      \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                      \"submissionId\": \"${pipelineData.output.request.request.data.submissionId}\",\n                      \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                      \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n                      \"subject\": \"Processing of submission ${pipelineData.output.request.request.data.submissionId} failed!\",\n                      \"manifestName\": \"${assetDiscovery.output.manifestName}\",\n                      \"manifestIncluded\": false,\n                      \"status\": \"FAILED\",\n                      \"oldCsv\": \"${oldCsv.output.result}\",\n                      \"chProjectId\": \"${pipelineData.output.request.request.data.projectId}\"\n                    }\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"subWorkflowParam\": {\n                    \"name\": \"vfxmediareview.notification\",\n                    \"version\": 1\n                  },\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"cperequest_transition\",\n                  \"taskReferenceName\": \"requestManifestDelivery\",\n                  \"inputParameters\": {\n                    \"namespace\": \"${pipelineData.output.request.request.namespace}\",\n                    \"type\": \"${pipelineData.output.request.request.type}\",\n                    \"requestId\": \"${pipelineData.output.request.request.id}\",\n                    \"transitionName\": \"redeliver\",\n                    \"skipIfInState\": [\"REDELIVERY\"]\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"updateTime\": 1604373979513,\n                    \"updatedBy\": \"cperequest\",\n                    \"name\": \"cperequest_transition\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\n                      \"namespace\",\n                      \"type\",\n                      \"requestId\",\n                      \"transitionName\",\n                      \"currentState\",\n                      \"currentVersion\",\n                      \"assignee\",\n                      \"clearAssignee\",\n                      \"dueDate\",\n                      \"clearDueDate\",\n                      \"skipIfInState\",\n                      \"transitionDetails\"\n                    ],\n                    \"outputKeys\": [\"request\"],\n                    \"timeoutPolicy\": \"TIME_OUT_WF\",\n                    \"retryLogic\": \"FIXED\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 1800,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"ownerEmail\": \"mce-workflow-infra@netflix.com\",\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"stl.pipeline.complete\",\n                  \"taskReferenceName\": \"completePipeline_SubmissionRejected1\",\n                  \"inputParameters\": {\n                    \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1556082385940,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"stl.pipeline.complete\",\n                    \"description\": \"Final task for all pipeline workflows\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\"pipelineId\"],\n                    \"outputKeys\": [],\n                    \"timeoutPolicy\": \"RETRY\",\n                    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 300,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"terminate\",\n                  \"taskReferenceName\": \"SubmissionRejected_1\",\n                  \"inputParameters\": {\n                    \"terminationStatus\": \"COMPLETED\",\n                    \"workflowOutput\": {\n                      \"outcome\": \"SUBMISSION_REJECTED\",\n                      \"code\": \"MANIFEST_NOT_FOUND\"\n                    }\n                  },\n                  \"type\": \"TERMINATE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ],\n              \"DISCOVERY_ERROR\": [\n                {\n                  \"name\": \"stl.common.jq\",\n                  \"taskReferenceName\": \"prepareRecipientDomainsForManifestErrors\",\n                  \"inputParameters\": {\n                    \"inputData\": {\n                      \"reviewServerConfig\": \"${reviewServerConfig.output.result}\",\n                      \"domains\": [\n                        {\n                          \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                        },\n                        {\n                          \"domains\": [\"netflix.amp.domain.production_vfx\"],\n                          \"partnerId\": \"${pipelineData.output.request.request.data.vendorId}\",\n                          \"ignoreIfPartnerNull\": true\n                        },\n                        {\n                          \"profiles\": [\"PRODUCTION_VFX_ADMIN\"],\n                          \"includeUsersWithProfile\": true\n                        }\n                      ],\n                      \"fallbackDomains\": [\n                        {\n                          \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                        }\n                      ]\n                    },\n                    \"expression\": \". as $in | $in.domains\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1556082387383,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"stl.common.jq\",\n                    \"description\": \"Run JQ expression\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\"expression\", \"inputData\"],\n                    \"outputKeys\": [],\n                    \"timeoutPolicy\": \"RETRY\",\n                    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                    \"retryDelaySeconds\": 120,\n                    \"responseTimeoutSeconds\": 1800,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"sendManifestErrorsEmail\",\n                  \"taskReferenceName\": \"sendManifestErrorsEmail\",\n                  \"inputParameters\": {\n                    \"emailType\": \"SINGLE\",\n                    \"domains\": \"${prepareRecipientDomainsForManifestErrors.output.result}\",\n                    \"additionalRecipients\": [\n                      \"vfx-media-review@netflix.com\",\n                      \"e1u9g2p6u1b8e5x7@netflix.slack.com\"\n                    ],\n                    \"additionalUsers\": [\n                      \"${pipelineData.output.request.request.data.ownerUser}\"\n                    ],\n                    \"eventName\": \"EVENT_MESSAGE_VFX_REVIEW_SUBMISSION\",\n                    \"eventType\": \"MESSAGE_VFX_REVIEW_SUBMISSION\",\n                    \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                    \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                    \"emailPayload\": {\n                      \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                      \"submissionId\": \"${pipelineData.output.request.request.data.submissionId}\",\n                      \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                      \"manifestErrors\": \"${assetDiscovery.output.displayErrors}\",\n                      \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n                      \"subject\": \"Processing of submission ${pipelineData.output.request.request.data.submissionId} failed!\",\n                      \"manifestName\": \"${assetDiscovery.output.manifestName}\",\n                      \"manifestIncluded\": true,\n                      \"manifestLink\": \"${details.output.submissionUri}&nodeIds=${assetDiscovery.output.manifestNodeId}\",\n                      \"filesMissingInManifest\": \"${assetDiscovery.output.filesMissingInManifest}\",\n                      \"filesMissingInSubmission\": \"${assetDiscovery.output.filesMissingInSubmission}\",\n                      \"manifestWarnings\": \"${assetDiscovery.output.displayWarnings}\",\n                      \"status\": \"FAILED\",\n                      \"oldCsv\": \"${oldCsv.output.result}\",\n                      \"chProjectId\": \"${pipelineData.output.request.request.data.projectId}\"\n                    }\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"subWorkflowParam\": {\n                    \"name\": \"vfxmediareview.notification\",\n                    \"version\": 1\n                  },\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"cperequest_transition\",\n                  \"taskReferenceName\": \"requestManifestRedelivery\",\n                  \"inputParameters\": {\n                    \"namespace\": \"${pipelineData.output.request.request.namespace}\",\n                    \"type\": \"${pipelineData.output.request.request.type}\",\n                    \"requestId\": \"${pipelineData.output.request.request.id}\",\n                    \"transitionName\": \"redeliver\",\n                    \"skipIfInState\": [\"REDELIVERY\"]\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"updateTime\": 1604373979513,\n                    \"updatedBy\": \"cperequest\",\n                    \"name\": \"cperequest_transition\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\n                      \"namespace\",\n                      \"type\",\n                      \"requestId\",\n                      \"transitionName\",\n                      \"currentState\",\n                      \"currentVersion\",\n                      \"assignee\",\n                      \"clearAssignee\",\n                      \"dueDate\",\n                      \"clearDueDate\",\n                      \"skipIfInState\",\n                      \"transitionDetails\"\n                    ],\n                    \"outputKeys\": [\"request\"],\n                    \"timeoutPolicy\": \"TIME_OUT_WF\",\n                    \"retryLogic\": \"FIXED\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 1800,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"ownerEmail\": \"mce-workflow-infra@netflix.com\",\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"stl.pipeline.complete\",\n                  \"taskReferenceName\": \"completePipeline_SubmissionRejected2\",\n                  \"inputParameters\": {\n                    \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1556082385940,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"stl.pipeline.complete\",\n                    \"description\": \"Final task for all pipeline workflows\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\"pipelineId\"],\n                    \"outputKeys\": [],\n                    \"timeoutPolicy\": \"RETRY\",\n                    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 300,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"terminate\",\n                  \"taskReferenceName\": \"SubmissionRejected_2\",\n                  \"inputParameters\": {\n                    \"terminationStatus\": \"COMPLETED\",\n                    \"workflowOutput\": {\n                      \"outcome\": \"SUBMISSION_REJECTED\",\n                      \"code\": \"MANIFEST_WITH_ERRORS\"\n                    }\n                  },\n                  \"type\": \"TERMINATE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ],\n              \"MULTIPLE_MANIFESTS_FOUND\": [\n                {\n                  \"name\": \"stl.common.jq\",\n                  \"taskReferenceName\": \"prepareRecipientDomainsForEncodingErrors1\",\n                  \"inputParameters\": {\n                    \"inputData\": {\n                      \"reviewServerConfig\": \"${reviewServerConfig.output.result}\",\n                      \"domains\": [\n                        {\n                          \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                        },\n                        {\n                          \"domains\": [\"netflix.amp.domain.production_vfx\"],\n                          \"partnerId\": \"${pipelineData.output.request.request.data.vendorId}\",\n                          \"ignoreIfPartnerNull\": true\n                        },\n                        {\n                          \"profiles\": [\"PRODUCTION_VFX_ADMIN\"],\n                          \"includeUsersWithProfile\": true\n                        }\n                      ],\n                      \"fallbackDomains\": [\n                        {\n                          \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                        }\n                      ]\n                    },\n                    \"expression\": \". as $in | $in.domains\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1556082387383,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"stl.common.jq\",\n                    \"description\": \"Run JQ expression\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\"expression\", \"inputData\"],\n                    \"outputKeys\": [],\n                    \"timeoutPolicy\": \"RETRY\",\n                    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                    \"retryDelaySeconds\": 120,\n                    \"responseTimeoutSeconds\": 1800,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"sendMultipleManifestsEmail\",\n                  \"taskReferenceName\": \"sendMultipleManifestsEmail\",\n                  \"inputParameters\": {\n                    \"emailType\": \"SINGLE\",\n                    \"domains\": \"${prepareRecipientDomainsForEncodingErrors1.output.result}\",\n                    \"additionalRecipients\": [\n                      \"vfx-media-review@netflix.com\",\n                      \"e1u9g2p6u1b8e5x7@netflix.slack.com\"\n                    ],\n                    \"additionalUsers\": [\n                      \"${pipelineData.output.request.request.data.ownerUser}\"\n                    ],\n                    \"eventName\": \"EVENT_MESSAGE_VFX_REVIEW_SUBMISSION\",\n                    \"eventType\": \"MESSAGE_VFX_REVIEW_SUBMISSION\",\n                    \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                    \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                    \"emailPayload\": {\n                      \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                      \"submissionId\": \"${pipelineData.output.request.request.data.submissionId}\",\n                      \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                      \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n                      \"subject\": \"Processing of submission ${pipelineData.output.request.request.data.submissionId} failed!\",\n                      \"chProjectId\": \"${pipelineData.output.request.request.data.projectId}\",\n                      \"multipleManifests\": true,\n                      \"status\": \"FAILED\",\n                      \"oldCsv\": \"${oldCsv.output.result}\"\n                    }\n                  },\n                  \"type\": \"SUB_WORKFLOW\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"subWorkflowParam\": {\n                    \"name\": \"vfxmediareview.notification\",\n                    \"version\": 1\n                  },\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"stl.pipeline.complete\",\n                  \"taskReferenceName\": \"completePipeline_SubmissionRejected3\",\n                  \"inputParameters\": {\n                    \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n                  },\n                  \"type\": \"SIMPLE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"taskDefinition\": {\n                    \"createTime\": 1556082385940,\n                    \"createdBy\": \"CPEWORKFLOW\",\n                    \"name\": \"stl.pipeline.complete\",\n                    \"description\": \"Final task for all pipeline workflows\",\n                    \"retryCount\": 3,\n                    \"timeoutSeconds\": 0,\n                    \"inputKeys\": [\"pipelineId\"],\n                    \"outputKeys\": [],\n                    \"timeoutPolicy\": \"RETRY\",\n                    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                    \"retryDelaySeconds\": 60,\n                    \"responseTimeoutSeconds\": 300,\n                    \"inputTemplate\": {},\n                    \"rateLimitPerFrequency\": 0,\n                    \"rateLimitFrequencyInSeconds\": 1,\n                    \"backoffScaleFactor\": 1\n                  },\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                },\n                {\n                  \"name\": \"terminate\",\n                  \"taskReferenceName\": \"SubmissionRejected_3\",\n                  \"inputParameters\": {\n                    \"terminationStatus\": \"COMPLETED\",\n                    \"workflowOutput\": {\n                      \"outcome\": \"SUBMISSION_REJECTED\",\n                      \"code\": \"MULTIPLE_MANIFESTS_FOUND\"\n                    }\n                  },\n                  \"type\": \"TERMINATE\",\n                  \"decisionCases\": {},\n                  \"defaultCase\": [],\n                  \"forkTasks\": [],\n                  \"startDelay\": 0,\n                  \"joinOn\": [],\n                  \"optional\": false,\n                  \"defaultExclusiveJoinTask\": [],\n                  \"asyncComplete\": false,\n                  \"loopOver\": []\n                }\n              ]\n            },\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"stl.common.jq\",\n            \"taskReferenceName\": \"addCdriveNodeIdAsExternalStatus\",\n            \"inputParameters\": {\n              \"inputData\": \"${assetdiscovery.output.assets}\",\n              \"expression\": \"[.[] | . as $in | { derivations:.derivations, movieId:.movieId, assetTree: {derivatives: .assetTree.derivatives, assets:.assetTree.assets  | (to_entries | map(  if(.value.payload.file != null) then .value.payload.externalStatuses |= .+{\\\"originalCDriveNodeId\\\": {value: $in.derivations.snapshot | to_entries | .[].value.nodeId}} else . end) | from_entries ), rootRefId:.assetTree.rootRefId, relations:.assetTree.relations}}]\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1556082387383,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"stl.common.jq\",\n              \"description\": \"Run JQ expression\",\n              \"retryCount\": 3,\n              \"timeoutSeconds\": 0,\n              \"inputKeys\": [\"expression\", \"inputData\"],\n              \"outputKeys\": [],\n              \"timeoutPolicy\": \"RETRY\",\n              \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n              \"retryDelaySeconds\": 120,\n              \"responseTimeoutSeconds\": 1800,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"backoffScaleFactor\": 1\n            },\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"stl.common.jq\",\n            \"taskReferenceName\": \"prepareImportAssetsFork\",\n            \"inputParameters\": {\n              \"inputData\": {\n                \"assetIngestRequests\": \"${addCdriveNodeIdAsExternalStatus.output.result}\",\n                \"user\": \"${workflow.input.pipelineInput.user}\",\n                \"limit\": 20,\n                \"partnerId\": \"${pipelineData.output.request.request.data.vendorId}\"\n              },\n              \"expression\": \". as $in | .assetIngestRequests | {taskDefs: map( {subWorkflowParam:{name:\\\"vfxmediareview.createandshareassets\\\"},name:\\\"createandshareassets\\\", taskReferenceName :\\\"createandshareassets_\\\\(.derivations.snapshot[]|.nodeId)\\\",\\\"type\\\": \\\"SUB_WORKFLOW\\\"}), \\\"taskInputs\\\":map({key:\\\"createandshareassets_\\\\(.derivations.snapshot[]|.nodeId)\\\", value:{assetIngestRequests:[.], user:$in.user, partnerId:$in.partnerId}})| from_entries}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1556082387383,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"stl.common.jq\",\n              \"description\": \"Run JQ expression\",\n              \"retryCount\": 3,\n              \"timeoutSeconds\": 0,\n              \"inputKeys\": [\"expression\", \"inputData\"],\n              \"outputKeys\": [],\n              \"timeoutPolicy\": \"RETRY\",\n              \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n              \"retryDelaySeconds\": 120,\n              \"responseTimeoutSeconds\": 1800,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"backoffScaleFactor\": 1\n            },\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"importAsset\",\n            \"taskReferenceName\": \"createandshareassets\",\n            \"inputParameters\": {\n              \"taskDefs\": \"${prepareImportAssetsFork.output.result.taskDefs}\",\n              \"taskInputs\": \"${prepareImportAssetsFork.output.result.taskInputs}\"\n            },\n            \"type\": \"FORK_JOIN_DYNAMIC\",\n            \"decisionCases\": {},\n            \"dynamicForkTasksParam\": \"taskDefs\",\n            \"dynamicForkTasksInputParamName\": \"taskInputs\",\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"createandshareassets_join\",\n            \"taskReferenceName\": \"createandshareassets_join\",\n            \"inputParameters\": {},\n            \"type\": \"JOIN\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"stl.common.manageResourcesToPipelineTask\",\n            \"taskReferenceName\": \"managePipelineResources\",\n            \"inputParameters\": {\n              \"pipelineResourceManagementInput\": \"${assetdiscovery.output.pipelineResourceManagementInput}\",\n              \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\",\n              \"contextUser\": \"pipelineapi\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 60,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1575590191136,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"stl.common.manageResourcesToPipelineTask\",\n              \"description\": \"Attach / detach resources on a pipeline\",\n              \"retryCount\": 3,\n              \"timeoutSeconds\": 0,\n              \"inputKeys\": [\n                \"pipelineId\",\n                \"contextUser\",\n                \"pipelineResourceManagementInput\"\n              ],\n              \"outputKeys\": [],\n              \"timeoutPolicy\": \"RETRY\",\n              \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n              \"retryDelaySeconds\": 120,\n              \"responseTimeoutSeconds\": 3000,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"backoffScaleFactor\": 1\n            },\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"stl.pipeline.get\",\n            \"taskReferenceName\": \"pipelineDataAfterParsing\",\n            \"inputParameters\": {\n              \"pipelineId\": \"${pipelineData.output.pipelineId}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1556082385634,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"stl.pipeline.get\",\n              \"description\": \"Read pipeline given pipeline id\",\n              \"retryCount\": 3,\n              \"timeoutSeconds\": 0,\n              \"inputKeys\": [\"pipelineId\"],\n              \"outputKeys\": [\"pipeline\"],\n              \"timeoutPolicy\": \"RETRY\",\n              \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n              \"retryDelaySeconds\": 60,\n              \"responseTimeoutSeconds\": 300,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"backoffScaleFactor\": 1\n            },\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.AwaitStatusMatchPipelineResource\",\n        \"taskReferenceName\": \"awaitPipelineResourceState\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n          \"selector\": {\n            \"resourceTypes\": [\"AMP_ASSET\"]\n          },\n          \"allowedStatuses\": [\"COMPLETED\", \"FAILED\"],\n          \"retryAfterSeconds\": \"60\",\n          \"user\": \"pipelineapi\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1599196237488,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.AwaitStatusMatchPipelineResource\",\n          \"description\": \"Wait for Resources to MatchStates\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\n            \"pipelineId\",\n            \"selector\",\n            \"allowedStatuses\",\n            \"retryAfterSeconds\",\n            \"user\"\n          ],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"ownerEmail\": \"cpe-che-backend@netflix.com\",\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.get\",\n        \"taskReferenceName\": \"pipelineDataAndResources\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${pipelineData.output.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082385634,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.get\",\n          \"description\": \"Read pipeline given pipeline id\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\"],\n          \"outputKeys\": [\"pipeline\"],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"manifestResource\",\n        \"inputParameters\": {\n          \"inputData\": \"${pipelineDataAndResources.output.pipelineResources}\",\n          \"expression\": \"map(select(.attachmentType == \\\"CSV\\\"))[0]\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"oldCsv\",\n        \"inputParameters\": {\n          \"inputData\": \"${manifestResource.output.result.resourceContext.entries}\",\n          \"expression\": \". // [] | any(has(\\\"Primary Shot Type\\\") or has(\\\"Shot Types\\\") or has(\\\"Shot Type\\\"))\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"manifestNodeDetailsTask\",\n        \"inputParameters\": {\n          \"inputData\": \"${pipelineDataAndResources.output.pipelineResources}\",\n          \"expression\": \"map(select(.attachmentType == \\\"CSV\\\") | .resourceIdentity.resourceId)[0] as $rid | $rid | if . == null then (\\\"stl.common.noop\\\") else (\\\"stl.cdrive.downloadManifest\\\") end | { task: ., nodeId: $rid }\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.cdrive.downloadManifest\",\n        \"taskReferenceName\": \"manifestNodeDetails\",\n        \"inputParameters\": {\n          \"task\": \"${manifestNodeDetailsTask.output.result.task}\",\n          \"nodeId\": \"${manifestNodeDetailsTask.output.result.nodeId}\",\n          \"userId\": \"${pipelineData.output.request.request.data.ownerUser}\",\n          \"useAppAuth\": true\n        },\n        \"type\": \"DYNAMIC\",\n        \"dynamicTaskNameParam\": \"task\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082388180,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.cdrive.downloadManifest\",\n          \"description\": \"get the cdrive manifest of a node.\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"nodeId\", \"userId\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"manifestDetails\",\n        \"inputParameters\": {\n          \"inputData\": \"${manifestNodeDetails.output.output}\",\n          \"expression\": \"if .downloadManifest != null then .assets[0] | { manifestName: .fileName[(.fileName|index(\\\"/\\\"))+1:], manifestBagginsUrl: .bagginsUrl, manifestNodeId: .nodeId, manifestIncluded: true  } else {} end\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"getCreatedAssets\",\n        \"inputParameters\": {\n          \"inputData\": \"${pipelineDataAndResources.output.pipelineResources}\",\n          \"expression\": \"map(select(.resourceIdentity.resourceType == \\\"AMP_ASSET\\\") | .resourceIdentity | { assetId: .resourceId, versionId: .versionId })\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"prepareGetAssetsTasks\",\n        \"inputParameters\": {\n          \"inputData\": \"${getCreatedAssets.output.result}\",\n          \"expressions\": [\"map({id: .assetId, version: .versionId})\"]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.amp.getAssetsTree\",\n        \"taskReferenceName\": \"assets_tree\",\n        \"inputParameters\": {\n          \"assetIds\": \"${prepareGetAssetsTasks.output.result}\",\n          \"derivatives\": [\"PROXY\"]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1600451428431,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.amp.getAssetsTree\",\n          \"description\": \"Get the full asset tree\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"assetIds\", \"derivatives\", \"relations\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"ownerEmail\": \"cpe-che-backend@netflix.com\",\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"assetsByType\",\n        \"inputParameters\": {\n          \"inputData\": \"${assets_tree.output.output}\",\n          \"expressions\": [\n            \"map( .assets | to_entries | map(.value)) | {proxies: map(select(any(.payload.type == \\\"PMR_REVIEW_PROXY\\\")) | map({assetId: .assetId, type: .payload.type})), images: map(select(any(.payload.type == \\\"IMAGE\\\")) | map({assetId: .assetId, type: .payload.type}))} \"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"prepareAssetProcessingTasks\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"assets\": \"${assetsByType.output.result}\",\n            \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n          },\n          \"expressions\": [\n            \". as $in | [$in.assets.proxies, $in.assets.images] | add | if . | length == 0 then [] else . | add end | map(select(.type == \\\"PMR_REVIEW_VERSION\\\") | {assetId: .assetId.id, versionId: .assetId.version}) | {assets: . , pipelineId: $in.pipelineId} as $in |\",\n            \" $in.assets | { taskDefs: map( {type: \\\"SUB_WORKFLOW\\\", name: \\\"process_asset\\\", taskReferenceName: \\\"process_asset_\\\\(.assetId)_\\\\(.versionId)\\\", subWorkflowParam: {name: \\\"vfxmediareview.assetprocessing\\\"}}),\",\n            \" taskInputs: map( { key: \\\"process_asset_\\\\(.assetId)_\\\\(.versionId)\\\", value: {assetId, versionId, pipelineId: $in.pipelineId}}) | from_entries  }\"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"asset_processing\",\n        \"taskReferenceName\": \"asset_processing\",\n        \"inputParameters\": {\n          \"taskDefs\": \"${prepareAssetProcessingTasks.output.result.taskDefs}\",\n          \"taskInputs\": \"${prepareAssetProcessingTasks.output.result.taskInputs}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"taskDefs\",\n        \"dynamicForkTasksInputParamName\": \"taskInputs\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"asset_processing_join\",\n        \"taskReferenceName\": \"asset_processing_join\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"evaluateAssetProcessingResults\",\n        \"inputParameters\": {\n          \"inputData\": \"${asset_processing_join.output}\",\n          \"expression\": \"to_entries | map(.value | { file, message: .description, outcome }) | { encodedFiles: map({ file, message }), outcome: (map(select(.outcome == \\\"FAILED\\\")) | if length > 0 then \\\"REPORT_ERRORS\\\" else \\\"PROCESS_SUBMISSION\\\" end) }\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"decide\",\n        \"taskReferenceName\": \"checkDeliveryStatus\",\n        \"inputParameters\": {\n          \"outcome\": \"${evaluateAssetProcessingResults.output.result.outcome}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"outcome\",\n        \"decisionCases\": {\n          \"REPORT_ERRORS\": [\n            {\n              \"name\": \"stl.common.jq\",\n              \"taskReferenceName\": \"assetProcessingResults\",\n              \"inputParameters\": {\n                \"inputData\": \"${asset_processing_join.output}\",\n                \"expression\": \"to_entries | map(.value | select(.outcome != \\\"SUCCESS\\\") | { file, message: .description, outcome }) | { encodedFiles: map({ file, message }), outcome: (map(select(.outcome == \\\"FAILED\\\")) | if length > 0 then \\\"REPORT_ERRORS\\\" else \\\"PROCESS_SUBMISSION\\\" end) }\"\n              },\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1556082387383,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"stl.common.jq\",\n                \"description\": \"Run JQ expression\",\n                \"retryCount\": 3,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [\"expression\", \"inputData\"],\n                \"outputKeys\": [],\n                \"timeoutPolicy\": \"RETRY\",\n                \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                \"retryDelaySeconds\": 120,\n                \"responseTimeoutSeconds\": 1800,\n                \"inputTemplate\": {},\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1,\n                \"backoffScaleFactor\": 1\n              },\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            },\n            {\n              \"name\": \"stl.common.jq\",\n              \"taskReferenceName\": \"prepareRecipientDomainsForEncodingErrors\",\n              \"inputParameters\": {\n                \"inputData\": {\n                  \"reviewServerConfig\": \"${reviewServerConfig.output.result}\",\n                  \"domains\": [\n                    {\n                      \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                    },\n                    {\n                      \"domains\": [\"netflix.amp.domain.production_vfx\"],\n                      \"partnerId\": \"${pipelineData.output.request.request.data.vendorId}\",\n                      \"ignoreIfPartnerNull\": true\n                    },\n                    {\n                      \"profiles\": [\"PRODUCTION_VFX_ADMIN\"],\n                      \"includeUsersWithProfile\": true\n                    }\n                  ],\n                  \"fallbackDomains\": [\n                    {\n                      \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n                    }\n                  ]\n                },\n                \"expression\": \". as $in | $in.domains\"\n              },\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1556082387383,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"stl.common.jq\",\n                \"description\": \"Run JQ expression\",\n                \"retryCount\": 3,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [\"expression\", \"inputData\"],\n                \"outputKeys\": [],\n                \"timeoutPolicy\": \"RETRY\",\n                \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                \"retryDelaySeconds\": 120,\n                \"responseTimeoutSeconds\": 1800,\n                \"inputTemplate\": {},\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1,\n                \"backoffScaleFactor\": 1\n              },\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            },\n            {\n              \"name\": \"notify_sendEncodingErrorsEmail\",\n              \"taskReferenceName\": \"sendEncodingErrorsEmail\",\n              \"inputParameters\": {\n                \"emailType\": \"SINGLE\",\n                \"domains\": \"${prepareRecipientDomainsForEncodingErrors.output.result}\",\n                \"additionalRecipients\": [\n                  \"vfx-media-review@netflix.com\",\n                  \"e1u9g2p6u1b8e5x7@netflix.slack.com\"\n                ],\n                \"additionalUsers\": [\n                  \"${pipelineData.output.request.request.data.ownerUser}\"\n                ],\n                \"eventName\": \"EVENT_MESSAGE_VFX_REVIEW_SUBMISSION\",\n                \"eventType\": \"MESSAGE_VFX_REVIEW_SUBMISSION\",\n                \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                \"emailPayload\": {\n                  \"status\": \"FAILED\",\n                  \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n                  \"submissionId\": \"${pipelineData.output.request.request.data.submissionId}\",\n                  \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n                  \"filesWithEncodingErrors\": \"${assetProcessingResults.output.result.encodedFiles}\",\n                  \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n                  \"subject\": \"Processing of submission ${pipelineData.output.request.request.data.submissionId} failed!\",\n                  \"chProjectId\": \"${pipelineData.output.request.request.data.projectId}\",\n                  \"oldCsv\": \"${oldCsv.output.result}\",\n                  \"manifestWarnings\": \"${assetdiscovery.output.displayWarnings}\"\n                }\n              },\n              \"type\": \"SUB_WORKFLOW\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"subWorkflowParam\": {\n                \"name\": \"vfxmediareview.notification\",\n                \"version\": 1\n              },\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            },\n            {\n              \"name\": \"cperequest_transition\",\n              \"taskReferenceName\": \"requestRedelivery\",\n              \"inputParameters\": {\n                \"namespace\": \"${pipelineData.output.request.request.namespace}\",\n                \"type\": \"${pipelineData.output.request.request.type}\",\n                \"requestId\": \"${pipelineData.output.request.request.id}\",\n                \"transitionName\": \"redeliver\",\n                \"skipIfInState\": [\"REDELIVERY\"]\n              },\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"updateTime\": 1604373979513,\n                \"updatedBy\": \"cperequest\",\n                \"name\": \"cperequest_transition\",\n                \"retryCount\": 3,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [\n                  \"namespace\",\n                  \"type\",\n                  \"requestId\",\n                  \"transitionName\",\n                  \"currentState\",\n                  \"currentVersion\",\n                  \"assignee\",\n                  \"clearAssignee\",\n                  \"dueDate\",\n                  \"clearDueDate\",\n                  \"skipIfInState\",\n                  \"transitionDetails\"\n                ],\n                \"outputKeys\": [\"request\"],\n                \"timeoutPolicy\": \"TIME_OUT_WF\",\n                \"retryLogic\": \"FIXED\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 1800,\n                \"inputTemplate\": {},\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1,\n                \"ownerEmail\": \"mce-workflow-infra@netflix.com\",\n                \"backoffScaleFactor\": 1\n              },\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            },\n            {\n              \"name\": \"stl.pipeline.complete\",\n              \"taskReferenceName\": \"completePipeline_SubmissionRejected\",\n              \"inputParameters\": {\n                \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1556082385940,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"stl.pipeline.complete\",\n                \"description\": \"Final task for all pipeline workflows\",\n                \"retryCount\": 3,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [\"pipelineId\"],\n                \"outputKeys\": [],\n                \"timeoutPolicy\": \"RETRY\",\n                \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"inputTemplate\": {},\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1,\n                \"backoffScaleFactor\": 1\n              },\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            },\n            {\n              \"name\": \"terminate\",\n              \"taskReferenceName\": \"SubmissionRejected\",\n              \"inputParameters\": {\n                \"terminationStatus\": \"COMPLETED\",\n                \"workflowOutput\": {\n                  \"outcome\": \"SUBMISSION_REJECTED\",\n                  \"code\": \"SOURCE_ERRORS\"\n                }\n              },\n              \"type\": \"TERMINATE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ],\n          \"PROCESS_SUBMISSION\": [\n            {\n              \"name\": \"stl.common.noop\",\n              \"taskReferenceName\": \"proceedWithShotgunProcessing\",\n              \"inputParameters\": {},\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"taskDefinition\": {\n                \"createTime\": 1556082386204,\n                \"createdBy\": \"CPEWORKFLOW\",\n                \"name\": \"stl.common.noop\",\n                \"description\": \"Do nothing\",\n                \"retryCount\": 3,\n                \"timeoutSeconds\": 0,\n                \"inputKeys\": [],\n                \"outputKeys\": [],\n                \"timeoutPolicy\": \"RETRY\",\n                \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n                \"retryDelaySeconds\": 60,\n                \"responseTimeoutSeconds\": 300,\n                \"inputTemplate\": {},\n                \"rateLimitPerFrequency\": 0,\n                \"rateLimitFrequencyInSeconds\": 1,\n                \"backoffScaleFactor\": 1\n              },\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ]\n        },\n        \"defaultCase\": [\n          {\n            \"name\": \"stl.pipeline.complete\",\n            \"taskReferenceName\": \"completePipeline_UnknownError1\",\n            \"inputParameters\": {\n              \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n            },\n            \"type\": \"SIMPLE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"taskDefinition\": {\n              \"createTime\": 1556082385940,\n              \"createdBy\": \"CPEWORKFLOW\",\n              \"name\": \"stl.pipeline.complete\",\n              \"description\": \"Final task for all pipeline workflows\",\n              \"retryCount\": 3,\n              \"timeoutSeconds\": 0,\n              \"inputKeys\": [\"pipelineId\"],\n              \"outputKeys\": [],\n              \"timeoutPolicy\": \"RETRY\",\n              \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n              \"retryDelaySeconds\": 60,\n              \"responseTimeoutSeconds\": 300,\n              \"inputTemplate\": {},\n              \"rateLimitPerFrequency\": 0,\n              \"rateLimitFrequencyInSeconds\": 1,\n              \"backoffScaleFactor\": 1\n            },\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          },\n          {\n            \"name\": \"terminate\",\n            \"taskReferenceName\": \"UnknownError1\",\n            \"inputParameters\": {\n              \"terminationStatus\": \"FAILED\",\n              \"workflowOutput\": {\n                \"outcome\": \"UNKNOWN_ERROR\",\n                \"code\": \"INTERNAL_ERRORS\"\n              }\n            },\n            \"type\": \"TERMINATE\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"submissionFolderProjection\",\n        \"taskReferenceName\": \"submissionFolderProjection\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${pipelineData.output.request.request.data.pipelineId}\",\n          \"manifest\": {\n            \"name\": \"${manifestDetails.output.result.manifestName}\",\n            \"bagginsUrl\": \"${manifestDetails.output.result.manifestBagginsUrl}\"\n          },\n          \"assets\": \"${getCreatedAssets.output.result}\",\n          \"vendorName\": \"${vendorDetails.output.vendorName}\"\n        },\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.projectsubmission\",\n          \"version\": 1\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.map\",\n        \"taskReferenceName\": \"pipelineDetails\",\n        \"inputParameters\": {\n          \"submissionUri\": \"workspace?workspaceRoot=SHARED&layout=TREE&categoryType=workspace&workspaceFolderId=${submissionFolderProjection.output.sharedSubmissionFolderId}\",\n          \"mappings\": {\n            \"submissionUri\": \"['submissionUri']\"\n          }\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082386616,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.map\",\n          \"description\": \"General purpose task to apply expression language (SpEL) transforms\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"mappings\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.get\",\n        \"taskReferenceName\": \"getPipelineData\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${pipelineData.output.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082385634,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.get\",\n          \"description\": \"Read pipeline given pipeline id\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\"],\n          \"outputKeys\": [\"pipeline\"],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"updateShotgunProcessingStart\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"resources\": \"${getPipelineData.output.pipelineResources}\"\n          },\n          \"expression\": \".resources | map((select(.attachmentType==\\\"EXPECTED_ASSET\\\") | . as $r | $r | .resourceContext.progress | map( if .name == \\\"SHOTGUN_UPLOAD\\\" then { name, status: \\\"IN_PROGRESS\\\" } else . end ) as $p | $r * { resourceContext: ($r.resourceContext * {progress: $p}) }) )\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.updateResource\",\n        \"taskReferenceName\": \"updateResourceShotgunStarted\",\n        \"inputParameters\": {\n          \"resources\": \"${updateShotgunProcessingStart.output.result}\",\n          \"pipelineId\": \"${pipelineData.output.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1576822186221,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.updateResource\",\n          \"description\": \"Update a pipeline resource\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\", \"resource\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 3000,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.titus\",\n        \"taskReferenceName\": \"getVendors\",\n        \"inputParameters\": {\n          \"applicationName\": \"che/vfx-review-cli\",\n          \"version\": \"${NETFLIX_ENVIRONMENT}.latest\",\n          \"entryPoint\": \"python cli.py vendors -s \\\"https://${reviewServerConfig.output.result.server}\\\" -p ${reviewServerConfig.output.result.projectId} --rc-conductor \\\"${CPEWF_TASK_ID}\\\" \"\n        },\n        \"type\": \"TITUS\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1580327637347,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.titus\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 3600,\n          \"inputKeys\": [],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 90,\n          \"responseTimeoutSeconds\": 1200,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": true,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"playlistVendorParameter\",\n        \"inputParameters\": {\n          \"inputData\": \"${getVendors.output.result}\",\n          \"expressions\": [\n            \".vendors | map(select(.sg_global_vendor.sg_vendor_id == \\\"${pipelineData.output.request.request.data.vendorId}\\\"))[0] // null\",\n            \"| if . then \\\"--vendor-id \\\\(.id)\\\" else \\\"\\\" end\"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"sgPlaylist\",\n        \"taskReferenceName\": \"playlistInfo\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${pipelineData.output.request.request.data.pipelineId}\",\n          \"playlistName\": \"${pipelineData.output.request.request.data.submissionId}\",\n          \"contenthubProjectUrl\": \"${details.output.contenthubProjectUrl}\",\n          \"reviewServerConfig\": {\n            \"server\": \"${reviewServerConfig.output.result.server}\",\n            \"projectId\": \"${reviewServerConfig.output.result.projectId}\"\n          },\n          \"playlistVendorParameter\": \"${playlistVendorParameter.output.result}\"\n        },\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.shotgunplaylist\",\n          \"version\": 1\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"assetAndComputedMetadata\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"assets\": \"${assets_tree.output.output}\",\n            \"entries\": \"${manifestResource.output.result.resourceContext.entries}\"\n          },\n          \"expressions\": [\n            \"def sf(e): .submitting_for; \",\n            \"def desc(e): .submission_note; \",\n            \"def sow(e): .scope_of_work; \",\n            \". as $in | \",\n            \"$in.entries | to_entries as $entries | \",\n            \"$in.assets | map(.assets | to_entries | map(.value)) | add | map(. as $a | $a | .payload.metadata as $meta | \",\n            \"$meta | { metadata: { reason_for_review: ([ \\\"• *SUBMITTING FOR* - \\\\(sf(.))\\\", \\\"• *SCOPE OF WORK* - \\\\(sow(.))\\\", \\\"• *NOTES* - \\\\(desc(.))\\\" ] | join(\\\"\\\\n\\\")), sort_order: $entries | map(select($meta.version_name == .value.version_name))[0].key }, assetId: $a.assetId.id, assetVersion: $a.assetId.version })\"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.amp.updateMetadataAndAddTypes\",\n        \"taskReferenceName\": \"updateAssetComputedMetadata\",\n        \"inputParameters\": {\n          \"assets\": \"${assetAndComputedMetadata.output.result}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1579029014610,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.amp.updateMetadataAndAddTypes\",\n          \"description\": \"This will update the metadata and add Type. The needed metadata and type should be the input\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"assets\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.amp.getAssetsTree\",\n        \"taskReferenceName\": \"assets_tree_2\",\n        \"inputParameters\": {\n          \"assetIds\": \"${prepareGetAssetsTasks.output.result}\",\n          \"derivatives\": [\"PROXY\"]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1600451428431,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.amp.getAssetsTree\",\n          \"description\": \"Get the full asset tree\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"assetIds\", \"derivatives\", \"relations\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"ownerEmail\": \"cpe-che-backend@netflix.com\",\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"assetAndManifest\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"assets\": \"${assets_tree_2.output.output}\",\n            \"entries\": \"${manifestResource.output.result.resourceContext.entries}\"\n          },\n          \"expressions\": [\n            \". as $in | .assets | \",\n            \"map(.assets | to_entries | \",\n            \"  { topLevelAssetId: map(select(.value.payload.type == \\\"PMR_REVIEW_VERSION\\\") | .value)[0].assetId, \",\n            \"    sgAmpRefId: map(select(.value.payload.type == \\\"PMR_REVIEW_VERSION\\\") | .value)[0] | \\\"\\\\(.assetId.id):\\\\(.assetId.version)\\\", \",\n            \"    reviewAssetId: map(select(.value.payload.type == \\\"IMAGE\\\" or .value.payload.type == \\\"PMR_REVIEW_PROXY\\\") | .value)[0].assetId, \",\n            \"    shotname: map(select(.value.payload.type == \\\"IMAGE\\\" or .value.payload.type == \\\"PMR_REVIEW_PROXY\\\") | .value)[0].payload.file.name, \",\n            \"    metadata: map(select(.value.payload.type == \\\"PMR_REVIEW_VERSION\\\") | .value.payload.metadata)[0] | { submission_note, version_name, scope_of_work, vendor, link, submitting_for, reason_for_review, sort_order}\",\n            \"  }\",\n            \")\"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"updateShotgunProcessingInProgress\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"resources\": \"${updateShotgunProcessingStart.output.result}\"\n          },\n          \"expression\": \".resources | map(. as $r | $r | .resourceContext.progress | map(if .name == \\\"SHOTGUN_UPLOAD\\\" then { name, status: \\\"IN_PROGRESS\\\" } else . end) as $p | $r * { resourceContext: ($r.resourceContext * {progress: $p}) })\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.updateResource\",\n        \"taskReferenceName\": \"updateResourceShotgunInProgress\",\n        \"inputParameters\": {\n          \"resources\": \"${updateShotgunProcessingInProgress.output.result}\",\n          \"pipelineId\": \"${pipelineData.output.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1576822186221,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.updateResource\",\n          \"description\": \"Update a pipeline resource\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\", \"resource\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 3000,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"prepareShotProcessingAssets\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"assets\": \"${assets_tree_2.output.output}\"\n          },\n          \"expressions\": [\n            \".assets | to_entries | \",\n            \"map(.value.assets | to_entries | \",\n            \"    { topLevelAssetId: map(select(.value.payload.type == \\\"PMR_REVIEW_VERSION\\\"))[0].value.assetId | {id, versionId: .version}, \",\n            \"      sgAmpRefId: map(select(.value.payload.type == \\\"PMR_REVIEW_VERSION\\\"))[0].value.assetId | \\\"\\\\(.id):\\\\(.version)\\\", \",\n            \"      reviewAssetId: map(select(.value.payload.type == \\\"PMR_REVIEW_PROXY\\\" or .value.payload.type == \\\"IMAGE\\\"))[0].value.assetId | {id, versionId: .version}, \",\n            \"      shotname: map(select(.value.payload.type == \\\"PMR_REVIEW_VERSION\\\"))[0].value.payload.metadata.version_name, \",\n            \"      metadata: map(select(.value.payload.type == \\\"PMR_REVIEW_VERSION\\\"))[0].value.payload.metadata | {submission_note, version_name, scope_of_work, reason_for_review, vendor, link, submitting_for, sort_order}\",\n            \"    }\",\n            \")\"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"prepareShotProcessingTasks\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"reviewServer\": \"${reviewServerConfig.output.result.server}\",\n            \"reviewProjectId\": \"${reviewServerConfig.output.result.projectId}\",\n            \"vendorId\": \"${workflow.input.sgVendorId}\",\n            \"playlistId\": \"${playlistInfo.output.result.id}\",\n            \"playlistName\": \"${playlistInfo.output.result.code}\",\n            \"assetAndManifest\": \"${prepareShotProcessingAssets.output.result}\"\n          },\n          \"expressions\": [\n            \". as $in | $in.assetAndManifest | map({topLevelAssetId, reviewAssetId, sgAmpRefId, shotname, reviewServer: $in.reviewServer, reviewProjectId: $in.reviewProjectId, vendorId: $in.vendorId, playlistId: $in.playlistId, playlistName: $in.playlistName, vendorName: \\\"${vendorDetails.output.vendorName}\\\", metadata: .metadata }) | \",\n            \"{ taskDefs: map({type: \\\"SUB_WORKFLOW\\\", name: \\\"processshot\\\", taskReferenceName: \\\"processshot_\\\\(.topLevelAssetId.id)_\\\\(.topLevelAssetId.versionId)\\\", subWorkflowParam: { name: \\\"vfxmediareview.shotprocessing_v2\\\" }}), \",\n            \"  taskInputs: map({ key: \\\"processshot_\\\\(.topLevelAssetId.id)_\\\\(.topLevelAssetId.versionId)\\\", value: . }) | from_entries}\"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"asset_processing\",\n        \"taskReferenceName\": \"shot_processing\",\n        \"inputParameters\": {\n          \"taskDefs\": \"${prepareShotProcessingTasks.output.result.taskDefs}\",\n          \"taskInputs\": \"${prepareShotProcessingTasks.output.result.taskInputs}\"\n        },\n        \"type\": \"FORK_JOIN_DYNAMIC\",\n        \"decisionCases\": {},\n        \"dynamicForkTasksParam\": \"taskDefs\",\n        \"dynamicForkTasksInputParamName\": \"taskInputs\",\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"shot_processing_join\",\n        \"taskReferenceName\": \"shot_processing_join\",\n        \"inputParameters\": {},\n        \"type\": \"JOIN\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"processedFiles\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"encodingResult\": \"${asset_processing_join.output}\",\n            \"uploadResult\": \"${shot_processing_join.output}\"\n          },\n          \"expressions\": [\n            \". as $in | .uploadResult | to_entries | map(.value.result.upload_shot | { file: .filename, size: .file_size, labels: (if .skipped == true then [\\\"UPLOAD_SKIPPED\\\"] else [] end) }) as $ur | $in |  .encodingResult | to_entries | map(.value | { file, message: .description, labels: (if .code == \\\"ENCODE_SUCCESS\\\" or .code == \\\"SUCCESS\\\" then (.file as $f | $ur | map(select($f == .file) | .labels)[0]) else [.code] + (.file as $f | $ur | map(select($f == .file) | .labels)[0]) end) })\"\n          ]\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.titus\",\n        \"taskReferenceName\": \"updatePlaylist\",\n        \"inputParameters\": {\n          \"applicationName\": \"che/vfx-review-cli\",\n          \"version\": \"${NETFLIX_ENVIRONMENT}.latest\",\n          \"entryPoint\": \"python cli.py playlist -s \\\"https://${reviewServerConfig.output.result.server}\\\" -p ${reviewServerConfig.output.result.projectId}  --action \\\"update\\\" --playlist-id ${playlistInfo.output.result.id} --playlist-status \\\"rev\\\" --contenthub-workspace-link \\\"${details.output.contenthubProjectUrl}/${pipelineDetails.output.submissionUri}\\\" --rc-conductor \\\"${CPEWF_TASK_ID}\\\"\"\n        },\n        \"type\": \"TITUS\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1580327637347,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.titus\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 3600,\n          \"inputKeys\": [],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 90,\n          \"responseTimeoutSeconds\": 1200,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": true,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"updateShotgunProcessingCompleted\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"resources\": \"${updateShotgunProcessingStart.output.result}\"\n          },\n          \"expression\": \".resources | map( . as $r | $r | .resourceContext.progress | map( if .name == \\\"SHOTGUN_UPLOAD\\\" then { name, status: \\\"COMPLETED\\\" } else . end ) as $p | $r * {\\\"status\\\":\\\"COMPLETED\\\", resourceContext: ($r.resourceContext * {progress: $p}) } )\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.updateResource\",\n        \"taskReferenceName\": \"updateResourceShotgunCompleted\",\n        \"inputParameters\": {\n          \"resources\": \"${updateShotgunProcessingCompleted.output.result}\",\n          \"pipelineId\": \"${pipelineData.output.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1576822186221,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.updateResource\",\n          \"description\": \"Update a pipeline resource\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\", \"resource\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 3000,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"cperequest_transition\",\n        \"taskReferenceName\": \"submissionProcessed\",\n        \"inputParameters\": {\n          \"namespace\": \"${pipelineData.output.request.request.namespace}\",\n          \"type\": \"${pipelineData.output.request.request.type}\",\n          \"requestId\": \"${pipelineData.output.request.request.id}\",\n          \"transitionName\": \"processed\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"updateTime\": 1604373979513,\n          \"updatedBy\": \"cperequest\",\n          \"name\": \"cperequest_transition\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\n            \"namespace\",\n            \"type\",\n            \"requestId\",\n            \"transitionName\",\n            \"currentState\",\n            \"currentVersion\",\n            \"assignee\",\n            \"clearAssignee\",\n            \"dueDate\",\n            \"clearDueDate\",\n            \"skipIfInState\",\n            \"transitionDetails\"\n          ],\n          \"outputKeys\": [\"request\"],\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"ownerEmail\": \"mce-workflow-infra@netflix.com\",\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.common.jq\",\n        \"taskReferenceName\": \"prepareRecipientDomainsForProcessingComplete\",\n        \"inputParameters\": {\n          \"inputData\": {\n            \"reviewServerConfig\": \"${reviewServerConfig.output.result}\",\n            \"domains\": [\n              {\n                \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n              },\n              {\n                \"domains\": [\"netflix.amp.domain.production_vfx\"],\n                \"partnerId\": \"${pipelineData.output.request.request.data.vendorId}\",\n                \"ignoreIfPartnerNull\": true\n              },\n              {\n                \"profiles\": [\"PRODUCTION_VFX_ADMIN\"],\n                \"includeUsersWithProfile\": true\n              }\n            ],\n            \"fallbackDomains\": [\n              {\n                \"domains\": [\"netflix.amp.domain.studio_vfx\"]\n              }\n            ]\n          },\n          \"expression\": \". as $in | $in.domains\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082387383,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.common.jq\",\n          \"description\": \"Run JQ expression\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"expression\", \"inputData\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 120,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"sendProcessingCompleteEmail\",\n        \"taskReferenceName\": \"sendProcessingCompleteEmail\",\n        \"inputParameters\": {\n          \"emailType\": \"INTERNAL_EXTERNAL\",\n          \"domains\": \"${prepareRecipientDomainsForProcessingComplete.output.result}\",\n          \"additionalRecipients\": [\n            \"vfx-media-review@netflix.com\",\n            \"e1u9g2p6u1b8e5x7@netflix.slack.com\"\n          ],\n          \"additionalUsers\": [\n            \"${pipelineData.output.request.request.data.ownerUser}\"\n          ],\n          \"eventName\": \"EVENT_MESSAGE_VFX_REVIEW_SUBMISSION\",\n          \"eventType\": \"MESSAGE_VFX_REVIEW_SUBMISSION\",\n          \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n          \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n          \"emailPayload\": {\n            \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n            \"submissionId\": \"${pipelineData.output.request.request.data.submissionId}\",\n            \"submissionNodeId\": \"${pipelineData.output.request.request.data.submissionNodeId}\",\n            \"shotgunBaseUrl\": \"https://${reviewServerConfig.output.result.server}\",\n            \"shotgunProjectId\": \"${reviewServerConfig.output.result.projectId}\",\n            \"pipelineId\": \"${pipelineData.output.pipelineId}\",\n            \"subject\": \"Playlist ${pipelineData.output.request.request.data.submissionId} is ready for review!\",\n            \"playlistId\": \"${playlistInfo.output.result.id}\",\n            \"status\": \"SUCCESS\",\n            \"playlistReadyForReview\": true,\n            \"filesInSubmission\": \"${processedFiles.output.result}\",\n            \"manifestName\": \"${manifestDetails.output.result.manifestName}\",\n            \"manifestLink\": \"${pipelineDetails.output.submissionUri}&nodeIds=${manifestDetails.output.result.manifestNodeId}\",\n            \"manifestIncluded\": \"${manifestDetails.output.result.manifestIncluded}\",\n            \"vendorName\": \"${vendorDetails.output.name}\",\n            \"chProjectId\": \"${pipelineData.output.request.request.data.projectId}\",\n            \"oldCsv\": \"${oldCsv.output.result}\",\n            \"manifestWarnings\": \"${manifestDetails.output.result.displayWarnings}\"\n          }\n        },\n        \"type\": \"SUB_WORKFLOW\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"subWorkflowParam\": {\n          \"name\": \"vfxmediareview.notification\",\n          \"version\": 1\n        },\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pegasus.processPipelineEvent\",\n        \"taskReferenceName\": \"processPipelineEvent\",\n        \"inputParameters\": {\n          \"assetType\": \"PMR_REVIEW_VERSION\",\n          \"downloadDescription\": \"Download of submission ${pipelineData.output.request.request.data.submissionId}\",\n          \"movieId\": \"${pipelineData.output.request.request.data.movieId}\",\n          \"pipelineType\": \"${pipelineData.output.request.request.type}\",\n          \"nodeIds\": [\"${submissionFolderProjection.output.downloadFolderId}\"],\n          \"relativePath\": \"vfxmediareview/${pipelineData.output.request.request.data.submissionId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1574880092269,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pegasus.processPipelineEvent\",\n          \"description\": \"Tell Pegasus Stargate that we have a node that is ready for download\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 300,\n          \"inputKeys\": [\n            \"assetType\",\n            \"downloadDescription\",\n            \"movieId\",\n            \"pipelineType\",\n            \"nodeId\",\n            \"relativePath\"\n          ],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"cperequest_transition\",\n        \"taskReferenceName\": \"completeRequest\",\n        \"inputParameters\": {\n          \"namespace\": \"${pipelineData.output.request.request.namespace}\",\n          \"type\": \"${pipelineData.output.request.request.type}\",\n          \"requestId\": \"${pipelineData.output.request.request.id}\",\n          \"transitionName\": \"complete\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"updateTime\": 1604373979513,\n          \"updatedBy\": \"cperequest\",\n          \"name\": \"cperequest_transition\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\n            \"namespace\",\n            \"type\",\n            \"requestId\",\n            \"transitionName\",\n            \"currentState\",\n            \"currentVersion\",\n            \"assignee\",\n            \"clearAssignee\",\n            \"dueDate\",\n            \"clearDueDate\",\n            \"skipIfInState\",\n            \"transitionDetails\"\n          ],\n          \"outputKeys\": [\"request\"],\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 1800,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"ownerEmail\": \"mce-workflow-infra@netflix.com\",\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"stl.pipeline.complete\",\n        \"taskReferenceName\": \"completePipeline_SubmissionAccepted\",\n        \"inputParameters\": {\n          \"pipelineId\": \"${workflow.input.pipelineInput.pipelineId}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"createTime\": 1556082385940,\n          \"createdBy\": \"CPEWORKFLOW\",\n          \"name\": \"stl.pipeline.complete\",\n          \"description\": \"Final task for all pipeline workflows\",\n          \"retryCount\": 3,\n          \"timeoutSeconds\": 0,\n          \"inputKeys\": [\"pipelineId\"],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"RETRY\",\n          \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n          \"retryDelaySeconds\": 60,\n          \"responseTimeoutSeconds\": 300,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"terminate\",\n        \"taskReferenceName\": \"SubmissionAccepted\",\n        \"inputParameters\": {\n          \"terminationStatus\": \"COMPLETED\",\n          \"workflowOutput\": {\n            \"outcome\": \"SUBMISSION_ACCEPTED\",\n            \"code\": \"SUBMISSION_ACCEPTED\"\n          }\n        },\n        \"type\": \"TERMINATE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [],\n    \"outputParameters\": {},\n    \"failureWorkflow\": \"pipelines.vfxmediareview.failure\",\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": false,\n    \"ownerEmail\": \"cpe-che-backend@netflix.com\",\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  \"priority\": 0,\n  \"variables\": {},\n  \"lastRetriedTime\": 0,\n  \"startTime\": 1608153919527,\n  \"workflowName\": \"pipelines.vfxmediareview\",\n  \"workflowVersion\": 1\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/metadataTasks.json",
    "content": "[\n  {\n    \"updateTime\": 1629995112563,\n    \"updatedBy\": \"user1@example.com\",\n    \"name\": \"example_task_1\",\n    \"retryCount\": 4,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"FIXED\",\n    \"retryDelaySeconds\": 120,\n    \"responseTimeoutSeconds\": 1800,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"user1@example.com\",\n    \"backoffScaleFactor\": 1\n  },\n  {\n    \"createTime\": 1562373417179,\n    \"createdBy\": \"user2@example.com\",\n    \"name\": \"example_task_2\",\n    \"retryCount\": 2,\n    \"timeoutSeconds\": 0,\n    \"inputKeys\": [],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"RETRY\",\n    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n    \"retryDelaySeconds\": 30,\n    \"responseTimeoutSeconds\": 120,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"backoffScaleFactor\": 1\n  },\n  {\n    \"createTime\": 1627367321969,\n    \"createdBy\": \"user3@example.com\",\n    \"name\": \"example_task_3\",\n    \"retryCount\": 3,\n    \"timeoutSeconds\": 1800,\n    \"inputKeys\": [\"projectId\"],\n    \"outputKeys\": [],\n    \"timeoutPolicy\": \"TIME_OUT_WF\",\n    \"retryLogic\": \"EXPONENTIAL_BACKOFF\",\n    \"retryDelaySeconds\": 3,\n    \"responseTimeoutSeconds\": 1800,\n    \"inputTemplate\": {},\n    \"rateLimitPerFrequency\": 0,\n    \"rateLimitFrequencyInSeconds\": 1,\n    \"ownerEmail\": \"user3@example.com\",\n    \"backoffScaleFactor\": 1\n  }\n]\n"
  },
  {
    "path": "ui/cypress/fixtures/metadataWorkflow.json",
    "content": "[\n  {\n    \"createTime\": 1638226947603,\n    \"name\": \"19test009\",\n    \"description\": \"test workflow\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"fetch_data\",\n        \"taskReferenceName\": \"fetch_data\",\n        \"inputParameters\": {\n          \"http_request\": {\n            \"connectionTimeOut\": \"3600\",\n            \"readTimeOut\": \"3600\",\n            \"uri\": \"${workflow.input.uri}\",\n            \"method\": \"GET\",\n            \"accept\": \"application/json\",\n            \"content-Type\": \"application/json\",\n            \"headers\": {}\n          }\n        },\n        \"type\": \"HTTP\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"taskDefinition\": {\n          \"name\": \"fetch_data\",\n          \"retryCount\": 0,\n          \"timeoutSeconds\": 3600,\n          \"inputKeys\": [],\n          \"outputKeys\": [],\n          \"timeoutPolicy\": \"TIME_OUT_WF\",\n          \"retryLogic\": \"FIXED\",\n          \"retryDelaySeconds\": 0,\n          \"responseTimeoutSeconds\": 3000,\n          \"inputTemplate\": {},\n          \"rateLimitPerFrequency\": 0,\n          \"rateLimitFrequencyInSeconds\": 1,\n          \"backoffScaleFactor\": 1\n        },\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": true,\n    \"ownerEmail\": \"test@163.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  {\n    \"createTime\": 1610653237179,\n    \"name\": \"ConditionalTerminateWorkflow\",\n    \"description\": \"ConditionalTerminateWorkflow\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"perf_task_1\",\n        \"taskReferenceName\": \"t1\",\n        \"inputParameters\": {\n          \"tp11\": \"${workflow.input.param1}\",\n          \"tp12\": \"${workflow.input.param2}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"decision\",\n        \"taskReferenceName\": \"decision\",\n        \"inputParameters\": {\n          \"case\": \"${t1.output.case}\"\n        },\n        \"type\": \"DECISION\",\n        \"caseValueParam\": \"case\",\n        \"decisionCases\": {\n          \"one\": [\n            {\n              \"name\": \"perf_task_2\",\n              \"taskReferenceName\": \"t2\",\n              \"inputParameters\": {\n                \"tp21\": \"${workflow.input.param1}\"\n              },\n              \"type\": \"SIMPLE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ],\n          \"two\": [\n            {\n              \"name\": \"terminate\",\n              \"taskReferenceName\": \"terminate0\",\n              \"inputParameters\": {\n                \"terminationStatus\": \"COMPLETED\",\n                \"workflowOutput\": \"${t1.output.op}\"\n              },\n              \"type\": \"TERMINATE\",\n              \"decisionCases\": {},\n              \"defaultCase\": [],\n              \"forkTasks\": [],\n              \"startDelay\": 0,\n              \"joinOn\": [],\n              \"optional\": false,\n              \"defaultExclusiveJoinTask\": [],\n              \"asyncComplete\": false,\n              \"loopOver\": []\n            }\n          ]\n        },\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      },\n      {\n        \"name\": \"perf_task_3\",\n        \"taskReferenceName\": \"t3\",\n        \"inputParameters\": {\n          \"tp31\": \"${workflow.input.param2}\"\n        },\n        \"type\": \"SIMPLE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopOver\": []\n      }\n    ],\n    \"inputParameters\": [\"param1\", \"param2\"],\n    \"outputParameters\": {\n      \"o2\": \"${t1.output.op}\"\n    },\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": false,\n    \"ownerEmail\": \"test@harness.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  },\n  {\n    \"createTime\": 1654202968736,\n    \"name\": \"Do_While_Workflow_Iteration_Fix\",\n    \"description\": \"Do_While_Workflow_Iteration_Fix\",\n    \"version\": 1,\n    \"tasks\": [\n      {\n        \"name\": \"loopTask\",\n        \"taskReferenceName\": \"loopTask\",\n        \"inputParameters\": {\n          \"value\": \"${workflow.input.loop}\"\n        },\n        \"type\": \"DO_WHILE\",\n        \"decisionCases\": {},\n        \"defaultCase\": [],\n        \"forkTasks\": [],\n        \"startDelay\": 0,\n        \"joinOn\": [],\n        \"optional\": false,\n        \"defaultExclusiveJoinTask\": [],\n        \"asyncComplete\": false,\n        \"loopCondition\": \"if ($.loopTask['iteration'] < $.value) { true; } else { false;} \",\n        \"loopOver\": [\n          {\n            \"name\": \"form_uri\",\n            \"taskReferenceName\": \"form_uri\",\n            \"inputParameters\": {\n              \"index\": \"${loopTask['iteration']}\",\n              \"scriptExpression\": \"return $.index - 1;\"\n            },\n            \"type\": \"LAMBDA\",\n            \"decisionCases\": {},\n            \"defaultCase\": [],\n            \"forkTasks\": [],\n            \"startDelay\": 0,\n            \"joinOn\": [],\n            \"optional\": false,\n            \"defaultExclusiveJoinTask\": [],\n            \"asyncComplete\": false,\n            \"loopOver\": []\n          }\n        ]\n      }\n    ],\n    \"inputParameters\": [],\n    \"outputParameters\": {},\n    \"schemaVersion\": 2,\n    \"restartable\": true,\n    \"workflowStatusListenerEnabled\": false,\n    \"ownerEmail\": \"peterl@netflix.com\",\n    \"timeoutPolicy\": \"ALERT_ONLY\",\n    \"timeoutSeconds\": 0,\n    \"variables\": {},\n    \"inputTemplate\": {}\n  }\n]\n"
  },
  {
    "path": "ui/cypress/fixtures/taskSearch.json",
    "content": "{\n  \"totalHits\": 1,\n  \"results\": [\n    {\n      \"workflowId\": \"e577cf0c-4cc0-4224-b729-79c5a2609b30\",\n      \"workflowType\": \"JXU_PROMO_MEDIA_PUBLISH_TO_PAL_WORKFLOW\",\n      \"scheduledTime\": \"2022-05-17T22:52:46.628Z\",\n      \"startTime\": \"2022-05-17T22:52:47.212Z\",\n      \"updateTime\": \"2022-05-17T22:52:47.212Z\",\n      \"endTime\": \"2022-05-17T22:52:47.602Z\",\n      \"status\": \"COMPLETED\",\n      \"executionTime\": 390,\n      \"queueWaitTime\": 584,\n      \"taskDefName\": \"JXU_PROMO_MEDIA_PUBLISH_BUNDLE_TO_PAL\",\n      \"taskType\": \"JXU_PROMO_MEDIA_PUBLISH_BUNDLE_TO_PAL\",\n      \"input\": \"{bundleId=workflow.input.bundleId}\",\n      \"output\": \"{singleAssetPublishTasks=[{taskReferenceName=fork_0, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_1, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_2, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_3, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_4, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_5, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_6, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_7, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_8, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_9, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_10, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_11, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_12, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_13, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_14, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_15, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_16, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_17, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_18, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_19, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_20, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_21, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_22, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_23, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_24, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_25, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_26, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_27, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_28, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_29, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_30, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_31, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_32, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_33, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_34, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_35, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_36, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_37, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_38, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_39, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_40, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_41, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_42, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_43, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_44, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_45, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_46, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_47, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_48, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_49, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_50, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_51, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_52, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_53, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_54, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_55, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_56, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_57, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_58, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_59, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_60, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_61, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_62, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_63, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_64, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_65, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_66, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_67, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_68, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_69, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_70, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_71, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_72, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_73, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_74, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_75, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_76, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_77, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_78, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_79, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_80, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_81, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_82, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_83, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_84, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_85, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_86, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_87, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_88, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_89, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_90, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_91, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_92, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_93, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_94, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_95, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_96, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_97, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_98, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}, {taskReferenceName=fork_99, name=JXU_PROMO_MEDIA_PUBLISH_SINGLE_ASSET_TO_PAL, type=SIMPLE}], singleAssetPublishTaskInput={fork_89={assetId=89}, fork_87={assetId=87}, fork_88={assetId=88}, fork_85={assetId=85}, fork_86={assetId=86}, fork_83={assetId=83}, fork_84={assetId=84}, fork_81={assetId=81}, fork_82={assetId=82}, fork_80={assetId=80}, fork_12={assetId=12}, fork_13={assetId=13}, fork_10={assetId=10}, fork_98={assetId=98}, fork_11={assetId=11}, fork_99={assetId=99}, fork_96={assetId=96}, fork_97={assetId=97}, fork_94={assetId=94}, fork_95={assetId=95}, fork_92={assetId=92}, fork_93={assetId=93}, fork_90={assetId=90}, fork_91={assetId=91}, fork_23={assetId=23}, fork_24={assetId=24}, fork_21={assetId=21}, fork_22={assetId=22}, fork_20={assetId=20}, fork_18={assetId=18}, fork_19={assetId=19}, fork_16={assetId=16}, fork_17={assetId=17}, fork_14={assetId=14}, fork_15={assetId=15}, fork_34={assetId=34}, fork_35={assetId=35}, fork_32={assetId=32}, fork_33={assetId=33}, fork_30={assetId=30}, fork_31={assetId=31}, fork_29={assetId=29}, fork_27={assetId=27}, fork_28={assetId=28}, fork_25={assetId=25}, fork_26={assetId=26}, fork_45={assetId=45}, fork_46={assetId=46}, fork_43={assetId=43}, fork_44={assetId=44}, fork_41={assetId=41}, fork_42={assetId=42}, fork_40={assetId=40}, fork_38={assetId=38}, fork_39={assetId=39}, fork_36={assetId=36}, fork_37={assetId=37}, fork_56={assetId=56}, fork_57={assetId=57}, fork_54={assetId=54}, fork_9={assetId=9}, fork_55={assetId=55}, fork_8={assetId=8}, fork_52={assetId=52}, fork_7={assetId=7}, fork_53={assetId=53}, fork_6={assetId=6}, fork_50={assetId=50}, fork_5={assetId=5}, fork_51={assetId=51}, fork_4={assetId=4}, fork_3={assetId=3}, fork_2={assetId=2}, fork_1={assetId=1}, fork_0={assetId=0}, fork_49={assetId=49}, fork_47={assetId=47}, fork_48={assetId=48}, fork_67={assetId=67}, fork_68={assetId=68}, fork_65={assetId=65}, fork_66={assetId=66}, fork_63={assetId=63}, fork_64={assetId=64}, fork_61={assetId=61}, fork_62={assetId=62}, fork_60={assetId=60}, fork_58={assetId=58}, fork_59={assetId=59}, fork_78={assetId=78}, fork_79={assetId=79}, fork_76={assetId=76}, fork_77={assetId=77}, fork_74={assetId=74}, fork_75={assetId=75}, fork_72={assetId=72}, fork_73={assetId=73}, fork_70={assetId=70}, fork_71={assetId=71}, fork_69={assetId=69}}}\",\n      \"taskId\": \"36d24c5c-9c26-46cf-9709-e1bc6963b8a5\",\n      \"workflowPriority\": 0\n    }\n  ]\n}\n"
  },
  {
    "path": "ui/cypress/fixtures/workflowSearch.json",
    "content": "{\n  \"totalHits\": 5,\n  \"results\": [\n    {\n      \"workflowType\": \"feature_value_compute_workflow\",\n      \"version\": 1,\n      \"workflowId\": \"d11255ed-4708-4ce5-992d-92803f0f19fc\",\n      \"startTime\": \"2022-06-09T16:32:56.851Z\",\n      \"status\": \"RUNNING\",\n      \"input\": \"{clientContext={}, featureDefId={namespace={name=gemstone-dev}, featureDefName=gcarmo-orchestration-test-3, featureDefVersion=2}, computeInfo={metaflowCompute={endpoint=https://httpbin.org/pos}}, triggerDagobahAttemptId=2c3c3444-dbb5-3bcc-aa7d-a3405c686c5c, gemIds=[8b132cd5-bde9-30ad-88b3-46f4ad720c73], featureDefTriggerId={namespace={name=some_trigger_id}, featureDefName=some_feature_def_name}}\",\n      \"output\": \"{}\",\n      \"executionTime\": 0,\n      \"failedReferenceTaskNames\": \"\",\n      \"priority\": 0,\n      \"inputSize\": 398,\n      \"outputSize\": 2\n    },\n    {\n      \"workflowType\": \"feature_value_compute_workflow\",\n      \"version\": 1,\n      \"workflowId\": \"7ff5c1d5-da27-4b27-9e60-0404eb4a1d23\",\n      \"startTime\": \"2022-06-09T16:31:54.904Z\",\n      \"endTime\": \"2022-06-09T16:32:31.901Z\",\n      \"status\": \"TERMINATED\",\n      \"input\": \"{clientContext={}, featureDefId={namespace={name=gemstone-dev}, featureDefName=gcarmo-orchestration-test-3, featureDefVersion=2}, computeInfo={metaflowCompute={endpoint=https://httpbin.org/pos}}, triggerDagobahAttemptId=2c3c3444-dbb5-3bcc-aa7d-a3405c686c5c, gemIds=[8b132cd5-bde9-30ad-88b3-46f4ad720c73], featureDefTriggerId={namespace={name=some_trigger_id}, featureDefName=some_feature_def_name}}\",\n      \"output\": \"{}\",\n      \"reasonForIncompletion\": \"Some reason!!!\",\n      \"executionTime\": 36997,\n      \"failedReferenceTaskNames\": \"feature_value_compute_task\",\n      \"priority\": 0,\n      \"inputSize\": 398,\n      \"outputSize\": 2\n    },\n    {\n      \"workflowType\": \"feature_value_compute_workflow\",\n      \"version\": 1,\n      \"workflowId\": \"ede49264-407d-4879-a708-e01526cee2ba\",\n      \"startTime\": \"2022-06-09T16:29:07.349Z\",\n      \"endTime\": \"2022-06-09T16:30:22.945Z\",\n      \"status\": \"FAILED\",\n      \"input\": \"{clientContext={}, featureDefId={namespace={name=gemstone-dev}, featureDefName=gcarmo-orchestration-test-3, featureDefVersion=2}, computeInfo={metaflowCompute={endpoint=https://httpbin.org/pos}}, triggerDagobahAttemptId=2c3c3444-dbb5-3bcc-aa7d-a3405c686c5c, gemIds=[8b132cd5-bde9-30ad-88b3-46f4ad720c73], featureDefTriggerId={namespace={name=some_trigger_id}, featureDefName=some_feature_def_name}}\",\n      \"output\": \"{}\",\n      \"reasonForIncompletion\": \"Request to https://httpbin.org/pos failed with status code 404\\n<!DOCTYPE HTML PUBLIC \\\"-//W3C//DTD HTML 3.2 Final//EN\\\">\\n<title>404 Not Found</title>\\n<h1>Not Found</h1>\\n<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>\\n\",\n      \"executionTime\": 75596,\n      \"failedReferenceTaskNames\": \"feature_value_compute_task\",\n      \"priority\": 0,\n      \"inputSize\": 398,\n      \"outputSize\": 2\n    },\n    {\n      \"workflowType\": \"feature_value_compute_workflow\",\n      \"version\": 1,\n      \"workflowId\": \"3950353b-9225-4729-a9e4-c8b4e244e041\",\n      \"startTime\": \"2022-06-09T16:27:48.666Z\",\n      \"endTime\": \"2022-06-09T16:27:50.560Z\",\n      \"status\": \"COMPLETED\",\n      \"input\": \"{clientContext={}, featureDefId={namespace={name=gemstone-dev}, featureDefName=gcarmo-orchestration-test-3, featureDefVersion=1}, computeInfo={metaflowCompute={endpoint=https://httpbin.org/post}}, triggerDagobahAttemptId=2c3c3444-dbb5-3bcc-aa7d-a3405c686c5c, gemIds=[8b132cd5-bde9-30ad-88b3-46f4ad720c73], featureDefTriggerId={namespace={name=some_trigger_id}, featureDefName=some_feature_def_name}}\",\n      \"output\": \"{}\",\n      \"executionTime\": 1894,\n      \"failedReferenceTaskNames\": \"\",\n      \"priority\": 0,\n      \"inputSize\": 399,\n      \"outputSize\": 2\n    },\n    {\n      \"workflowType\": \"feature_value_compute_workflow\",\n      \"version\": 1,\n      \"workflowId\": \"9a6438c5-60a4-4af6-b530-f2bf3a2dd859\",\n      \"startTime\": \"2022-06-09T16:20:28.188Z\",\n      \"endTime\": \"2022-06-09T16:20:29.935Z\",\n      \"status\": \"COMPLETED\",\n      \"input\": \"{clientContext={}, featureDefId={namespace={name=gemstone-dev}, featureDefName=gcarmo-orchestration-test-3, featureDefVersion=1}, computeInfo={metaflowCompute={endpoint=https://httpbin.org/post}}, triggerDagobahAttemptId=2c3c3444-dbb5-3bcc-aa7d-a3405c686c5c, gemIds=[8b132cd5-bde9-30ad-88b3-46f4ad720c73], featureDefTriggerId={namespace={name=some_trigger_id}, featureDefName=some_feature_def_name}}\",\n      \"output\": \"{}\",\n      \"executionTime\": 1747,\n      \"failedReferenceTaskNames\": \"\",\n      \"priority\": 0,\n      \"inputSize\": 399,\n      \"outputSize\": 2\n    }\n  ]\n}\n"
  },
  {
    "path": "ui/cypress/support/commands.ts",
    "content": "/// <reference types=\"cypress\" />\n// ***********************************************\n// This example commands.ts shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add('login', (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This will overwrite an existing command --\n// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })\n//\n// declare global {\n//   namespace Cypress {\n//     interface Chainable {\n//       login(email: string, password: string): Chainable<void>\n//       drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>\n//       dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>\n//       visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>\n//     }\n//   }\n// }\n"
  },
  {
    "path": "ui/cypress/support/component-index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n    <title>Components App</title>\n  </head>\n  <body>\n    <div data-cy-root></div>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/cypress/support/component.ts",
    "content": "// ***********************************************************\n// This example support/component.ts is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport \"./commands\";\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n\nimport { mount } from \"cypress/react\";\n\n// Augment the Cypress namespace to include type definitions for\n// your custom command.\n// Alternatively, can be defined in cypress/support/component.d.ts\n// with a <reference path=\"./component\" /> at the top of your spec.\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      mount: typeof mount;\n    }\n  }\n}\n\nCypress.Commands.add(\"mount\", mount);\n\n// Example use:\n// cy.mount(<MyComponent />)\n"
  },
  {
    "path": "ui/cypress/support/e2e.ts",
    "content": "// ***********************************************************\n// This example support/e2e.ts is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport \"./commands\";\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "ui/cypress.config.ts",
    "content": "import { defineConfig } from \"cypress\";\n\nexport default defineConfig({\n  e2e: {\n    baseUrl: \"http://localhost:5000\",\n  },\n\n  component: {\n    devServer: {\n      framework: \"create-react-app\",\n      bundler: \"webpack\",\n    },\n  },\n});\n"
  },
  {
    "path": "ui/package.json",
    "content": "{\n  \"name\": \"client\",\n  \"version\": \"3.8.0\",\n  \"dependencies\": {\n    \"@material-ui/core\": \"^4.12.3\",\n    \"@material-ui/icons\": \"^4.11.2\",\n    \"@material-ui/lab\": \"^4.0.0-alpha.60\",\n    \"@material-ui/styles\": \"^4.11.4\",\n    \"@monaco-editor/react\": \"^4.4.0\",\n    \"clsx\": \"^1.1.1\",\n    \"cronstrue\": \"^1.72.0\",\n    \"d3\": \"^6.2.0\",\n    \"dagre-d3\": \"^0.6.4\",\n    \"date-fns\": \"^2.16.1\",\n    \"formik\": \"^2.2.9\",\n    \"http-proxy-middleware\": \"^2.0.1\",\n    \"immutability-helper\": \"^3.1.1\",\n    \"json-bigint-string\": \"^1.0.0\",\n    \"lodash\": \"^4.17.20\",\n    \"moment\": \"^2.29.2\",\n    \"monaco-editor\": \"^0.44.0\",\n    \"node-forge\": \"^1.3.0\",\n    \"parse-svg-path\": \"^0.1.2\",\n    \"prop-types\": \"^15.7.2\",\n    \"react\": \"^16.8.0\",\n    \"react-cron-generator\": \"^1.3.5\",\n    \"react-data-table-component\": \"^6.11.8\",\n    \"react-dom\": \"^16.8.0\",\n    \"react-helmet\": \"^6.1.0\",\n    \"react-is\": \"^17.0.2\",\n    \"react-query\": \"^3.19.4\",\n    \"react-resize-detector\": \"^5.2.0\",\n    \"react-router\": \"^5.2.0\",\n    \"react-router-dom\": \"^5.2.0\",\n    \"react-router-use-location-state\": \"^2.5.0\",\n    \"react-scripts\": \"^5.0.1\",\n    \"react-vis-timeline-2\": \"^2.1.6\",\n    \"rison\": \"^0.1.1\",\n    \"styled-components\": \"^5.3.0\",\n    \"url-parse\": \"^1.5.1\",\n    \"use-local-storage-state\": \"^10.0.0\",\n    \"xss\": \"^1.0.8\",\n    \"yup\": \"^0.32.11\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\",\n    \"prettier\": \"prettier --write .\",\n    \"serve-build\": \"http-server ./build --port 5000 --proxy http://localhost:8080\",\n    \"cypress:open\": \"cypress open\",\n    \"cypress:test\": \"BROWSER=none start-server-and-test serve-build http-get://localhost:5000 'cypress run'\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"resolutions\": {\n    \"validator\": \"^13.7.0\",\n    \"nth-check\": \"^2.0.1\",\n    \"async\": \"^3.2.2\",\n    \"ejs\": \"^3.1.7\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.18.2\",\n    \"@babel/preset-env\": \"^7.18.2\",\n    \"@babel/register\": \"^7.17.7\",\n    \"@cypress/react\": \"^5.12.5\",\n    \"@cypress/webpack-dev-server\": \"^1.8.4\",\n    \"cypress\": \"^10.0.3\",\n    \"eslint-plugin-cypress\": \"^2.12.1\",\n    \"http-server\": \"^14.1.1\",\n    \"js-yaml\": \"4.1.0\",\n    \"prettier\": \"^2.2.1\",\n    \"sass\": \"^1.49.9\",\n    \"start-server-and-test\": \"^1.14.0\",\n    \"typescript\": \"^4.6.3\"\n  },\n  \"engines\": {\n    \"node\": \">=14.17.0\"\n  },\n  \"license\": \"Apache-2.0\"\n}\n"
  },
  {
    "path": "ui/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <title>Conductor UI</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "ui/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "ui/src/App.jsx",
    "content": "import React from \"react\";\n\nimport { Route, Switch } from \"react-router-dom\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { loader } from '@monaco-editor/react';\nimport { Button, AppBar, Toolbar } from \"@material-ui/core\";\nimport AppLogo from \"./plugins/AppLogo\";\nimport NavLink from \"./components/NavLink\";\n\nimport WorkflowSearch from \"./pages/executions/WorkflowSearch\";\nimport TaskSearch from \"./pages/executions/TaskSearch\";\n\nimport Execution from \"./pages/execution/Execution\";\nimport WorkflowDefinitions from \"./pages/definitions/Workflow\";\nimport WorkflowDefinition from \"./pages/definition/WorkflowDefinition\";\nimport TaskDefinitions from \"./pages/definitions/Task\";\nimport TaskDefinition from \"./pages/definition/TaskDefinition\";\nimport EventHandlerDefinitions from \"./pages/definitions/EventHandler\";\nimport EventHandlerDefinition from \"./pages/definition/EventHandler\";\nimport TaskQueue from \"./pages/misc/TaskQueue\";\nimport KitchenSink from \"./pages/kitchensink/KitchenSink\";\nimport DiagramTest from \"./pages/kitchensink/DiagramTest\";\nimport Examples from \"./pages/kitchensink/Examples\";\nimport Gantt from \"./pages/kitchensink/Gantt\";\n\nimport CustomRoutes from \"./plugins/CustomRoutes\";\nimport AppBarModules from \"./plugins/AppBarModules\";\nimport CustomAppBarButtons from \"./plugins/CustomAppBarButtons\";\nimport Workbench from \"./pages/workbench/Workbench\";\n\nconst useStyles = makeStyles((theme) => ({\n  root: {\n    backgroundColor: \"#efefef\", // TODO: Use theme var\n    display: \"flex\",\n  },\n  body: {\n    width: \"100vw\",\n    height: \"100vh\",\n    paddingTop: theme.overrides.MuiAppBar.root.height,\n  },\n  toolbarRight: {\n    marginLeft: \"auto\",\n    display: \"flex\",\n    flexDirection: \"row\",\n  },\n  toolbarRegular: {\n    minHeight: 80,\n  },\n}));\n\nexport default function App() {\n  const classes = useStyles();\n\n  return (\n    // Provide context for backward compatibility with class components\n    <div className={classes.root}>\n      <AppBar position=\"fixed\">\n        <Toolbar\n          classes={{\n            regular: classes.toolbarRegular,\n          }}\n        >\n          <AppLogo />\n          <Button component={NavLink} path=\"/\">\n            Executions\n          </Button>\n          <Button component={NavLink} path=\"/workflowDefs\">\n            Definitions\n          </Button>\n          <Button component={NavLink} path=\"/taskQueue\">\n            Task Queues\n          </Button>\n          <Button component={NavLink} path=\"/workbench\">\n            Workbench\n          </Button>\n          <CustomAppBarButtons />\n\n          <div className={classes.toolbarRight}>\n            <AppBarModules />\n          </div>\n        </Toolbar>\n      </AppBar>\n      <div className={classes.body}>\n        <Switch>\n          <Route exact path=\"/\">\n            <WorkflowSearch />\n          </Route>\n          <Route exact path=\"/search/tasks\">\n            <TaskSearch />\n          </Route>\n          <Route path=\"/execution/:id/:taskId?\">\n            <Execution />\n          </Route>\n          <Route exact path=\"/workflowDefs\">\n            <WorkflowDefinitions />\n          </Route>\n          <Route exact path=\"/workflowDef/:name?/:version?\">\n            <WorkflowDefinition />\n          </Route>\n          <Route exact path=\"/taskDefs\">\n            <TaskDefinitions />\n          </Route>\n          <Route exact path=\"/taskDef/:name?\">\n            <TaskDefinition />\n          </Route>\n          <Route exact path=\"/eventHandlerDef\">\n            <EventHandlerDefinitions />\n          </Route>\n          <Route exact path=\"/eventHandlerDef/:name\">\n            <EventHandlerDefinition />\n          </Route>\n          <Route exact path=\"/taskQueue/:name?\">\n            <TaskQueue />\n          </Route>\n          <Route exact path=\"/workbench\">\n            <Workbench />\n          </Route>\n          <Route exact path=\"/kitchen\">\n            <KitchenSink />\n          </Route>\n          <Route exact path=\"/kitchen/diagram\">\n            <DiagramTest />\n          </Route>\n          <Route exact path=\"/kitchen/examples\">\n            <Examples />\n          </Route>\n          <Route exact path=\"/kitchen/gantt\">\n            <Gantt />\n          </Route>\n          <CustomRoutes />\n        </Switch>\n      </div>\n    </div>\n  );\n}\n\nif (process.env.REACT_APP_MONACO_EDITOR_USING_CDN === \"false\") {\n  // Change the source of the monaco files, see https://github.com/suren-atoyan/monaco-react/issues/168#issuecomment-762336713\n  loader.config({ paths: { vs: '/monaco-editor/min/vs' } });\n}\n"
  },
  {
    "path": "ui/src/components/Banner.jsx",
    "content": "import React from \"react\";\nimport { Paper } from \"@material-ui/core\";\nimport { makeStyles } from \"@material-ui/styles\";\n\nconst useStyles = makeStyles({\n  root: {\n    padding: 15,\n    backgroundColor: \"rgba(73, 105, 228, 0.1)\",\n    color: \"rgba(0, 0, 0, 0.9)\",\n    borderLeft: \"solid rgba(73, 105, 228, 0.1) 4px\",\n  },\n});\n\nexport default function Banner({ children, ...rest }) {\n  const classes = useStyles();\n\n  return (\n    <Paper\n      elevation={0}\n      classes={{\n        root: classes.root,\n      }}\n      {...rest}\n    >\n      {children}\n    </Paper>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/Button.jsx",
    "content": "import { Button as MuiButton } from \"@material-ui/core\";\n\nexport default function Button({ variant = \"primary\", ...props }) {\n  if (variant === \"secondary\") {\n    return <MuiButton color=\"secondary\" variant=\"outlined\" {...props} />;\n  } else {\n    // primary or invalid\n    return <MuiButton color=\"primary\" variant=\"contained\" {...props} />;\n  }\n}\n"
  },
  {
    "path": "ui/src/components/ButtonGroup.jsx",
    "content": "import React from \"react\";\nimport {\n  FormControl,\n  InputLabel,\n  ButtonGroup,\n  Button,\n} from \"@material-ui/core\";\n\nexport default function ({ options, label, style, classes, ...props }) {\n  return (\n    <FormControl style={style} classes={classes}>\n      {label && <InputLabel>{label}</InputLabel>}\n      <ButtonGroup color=\"secondary\" variant=\"outlined\" {...props}>\n        {options.map((option, idx) => (\n          <Button key={idx} onClick={option.onClick}>\n            {option.label}\n          </Button>\n        ))}\n      </ButtonGroup>\n    </FormControl>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/ConfirmChoiceDialog.jsx",
    "content": "import React from \"react\";\nimport {\n  Dialog,\n  DialogActions,\n  DialogContent,\n  DialogTitle,\n} from \"@material-ui/core\";\nimport Text from \"./Text\";\nimport Button from \"./Button\";\n\nexport default function ({\n  header = \"Confirmation\",\n  message = \"Please confirm\",\n  handleConfirmationValue,\n  open,\n}) {\n  return (\n    <Dialog\n      fullWidth\n      maxWidth=\"sm\"\n      open={open}\n      onClose={() => handleConfirmationValue(false)}\n    >\n      <DialogTitle>{header}</DialogTitle>\n      <DialogContent>\n        <Text>{message}</Text>\n      </DialogContent>\n      <DialogActions>\n        <Button onClick={() => handleConfirmationValue(true)}>Confirm</Button>\n        <Button\n          variant=\"secondary\"\n          onClick={() => handleConfirmationValue(false)}\n        >\n          Cancel\n        </Button>\n      </DialogActions>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/CustomButtons.jsx",
    "content": "import Button from \"@material-ui/core/Button\";\nimport { styled } from \"@material-ui/core\";\n\nexport const fontFamilyList = [\n  \"-apple-system\",\n  \"BlinkMacSystemFont\",\n  '\"Segoe UI\"',\n  \"Roboto\",\n  '\"Helvetica Neue\"',\n  \"Arial\",\n  \"sans-serif\",\n  '\"Apple Color Emoji\"',\n  '\"Segoe UI Emoji\"',\n  '\"Segoe UI Symbol\"',\n].join(\",\");\n\nconst hoverCss = {\n  backgroundColor: \"#857aff\",\n  borderColor: \"#857aff\",\n  boxShadow: \"none\",\n  \"&> .MuiButton-label\": {\n    color: \"white\",\n  },\n};\n\nconst buttonBaseStyle = {\n  boxShadow: \"none\",\n  textTransform: \"none\",\n  fontSize: 16,\n  padding: \"6px 12px\",\n  border: \"1px solid\",\n  lineHeight: 1.3,\n  color: \"#ffffff\",\n  backgroundColor: \"#6558F5\",\n  borderColor: \"#6558F5\",\n  fontFamily: fontFamilyList,\n  \"&:hover\": hoverCss,\n  \"&:active\": hoverCss,\n  \"&:focus\": {\n    boxShadow: \"0 0 0 0.2rem rgba(0,123,255,.5)\",\n  },\n  \"&> .MuiButton-label\": {\n    color: \"#ffffff\",\n  },\n};\n\nexport const BootstrapButton = styled(Button)(buttonBaseStyle);\n\nconst outlineHoverCss = {\n  ...hoverCss,\n  \"&> .MuiButton-label\": {\n    color: \"ghostwhite\",\n  },\n};\n\nconst actionHoverCss = {\n  ...hoverCss,\n  backgroundColor: \"#30499f\",\n  borderColor: \"#30499f\",\n};\n\nexport const BootstrapOutlineButton = styled(Button)({\n  ...buttonBaseStyle,\n  color: \"#ffffff\",\n  backgroundColor: \"ghostwhite\",\n  borderColor: \"#6558F5\",\n  \"&> .MuiButton-label\": {\n    color: \"#6558F5\",\n  },\n  \"&:hover\": outlineHoverCss,\n  \"&:active\": outlineHoverCss,\n});\n\nexport const BootstrapOutlineActionButton = styled(Button)({\n  ...buttonBaseStyle,\n  color: \"#ffffff\",\n  backgroundColor: \"ghostwhite\",\n  borderColor: \"#30499f\",\n  \"&> .MuiButton-label\": {\n    color: \"#30499f\",\n  },\n  \"&:hover\": actionHoverCss,\n  \"&:active\": actionHoverCss,\n});\n\nexport const BootstrapTextButton = styled(Button)({\n  ...buttonBaseStyle,\n  color: \"#ffffff\",\n  backgroundColor: \"ghostwhite\",\n  borderColor: \"transparent\",\n  \"&> .MuiButton-label\": {\n    color: \"#6558F5\",\n  },\n  \"&:hover\": outlineHoverCss,\n  \"&:active\": outlineHoverCss,\n});\n\nexport const BootstrapActionButton = styled(Button)({\n  ...buttonBaseStyle,\n  fontSize: 14,\n  lineHeight: 1.5,\n  backgroundColor: \"#4969e4\",\n  borderColor: \"#4969e4\",\n  \"&> .MuiButton-label\": {\n    color: \"#ffffff\",\n  },\n  \"&:hover\": actionHoverCss,\n  \"&:active\": actionHoverCss,\n});\n"
  },
  {
    "path": "ui/src/components/DataTable.jsx",
    "content": "import React, { useMemo, useState } from \"react\";\nimport RawDataTable from \"react-data-table-component\";\nimport {\n  Checkbox,\n  MenuItem,\n  ListItemText,\n  IconButton,\n  Menu,\n  Tooltip,\n  Popover,\n} from \"@material-ui/core\";\nimport ViewColumnIcon from \"@material-ui/icons/ViewColumn\";\nimport SearchIcon from \"@material-ui/icons/Search\";\nimport { Heading, Select, Input } from \"./\";\nimport { timestampRenderer, timestampMsRenderer } from \"../utils/helpers\";\nimport { useLocalStorage } from \"../utils/localstorage\";\n\nimport _ from \"lodash\";\nexport const DEFAULT_ROWS_PER_PAGE = 15;\n\nexport default function DataTable(props) {\n  const {\n    localStorageKey,\n    columns,\n    data,\n    options,\n    defaultShowColumns,\n    paginationPerPage = 15,\n    showFilter = true,\n    showColumnSelector = true,\n    paginationServer = false,\n    title,\n    onFilterChange,\n    initialFilterObj,\n    ...rest\n  } = props;\n\n  const DEFAULT_FILTER_OBJ = {\n    columnName: columns.find((col) => col.searchable !== false).name,\n    substring: \"\",\n  };\n\n  // If no defaultColumns passed - use all columns\n  const defaultColumns = useMemo(\n    () =>\n      props.defaultShowColumns || props.columns.map((col) => getColumnId(col)),\n    [props.defaultShowColumns, props.columns]\n  );\n\n  const [tableState, setTableState] = useLocalStorage(\n    localStorageKey,\n    defaultColumns\n  );\n\n  const [filterObj, setFilterObj] = useState(\n    initialFilterObj || DEFAULT_FILTER_OBJ\n  );\n\n  const handleFilterChange = (val) => {\n    setFilterObj(val);\n    if (onFilterChange) {\n      if (!_.isEmpty(val.substring)) {\n        onFilterChange(val);\n      } else {\n        onFilterChange(undefined);\n      }\n    }\n  };\n\n  // Append bodyRenderer for date fields;\n  const dataTableColumns = useMemo(() => {\n    let viewColumns = [];\n    if (tableState) {\n      for (let col of columns) {\n        if (tableState.includes(getColumnId(col))) {\n          viewColumns.push(col);\n        }\n      }\n    } else {\n      viewColumns = columns;\n    }\n\n    return viewColumns.map((column) => {\n      let {\n        id,\n        name,\n        label,\n        type,\n        renderer,\n        wrap = true,\n        sortable = true,\n        ...rest\n      } = column;\n\n      const internalOptions = {};\n      if (type === \"date\") {\n        internalOptions.format = (row) => timestampRenderer(_.get(row, name));\n      } else if (type === \"date-ms\") {\n        internalOptions.format = (row) => timestampMsRenderer(_.get(row, name));\n      } else if (type === \"json\") {\n        internalOptions.format = (row) => JSON.stringify(_.get(row, name));\n      }\n\n      if (renderer) {\n        internalOptions.format = (row) => renderer(_.get(row, name), row);\n      }\n\n      return {\n        id: getColumnId(column),\n        selector: name,\n        name: getColumnLabel(column),\n        sortable: sortable,\n        wrap: wrap,\n        type,\n        ...internalOptions,\n        ...rest,\n      };\n    });\n  }, [tableState, columns]);\n\n  const filteredItems = useMemo(() => {\n    const column = dataTableColumns.find(\n      (col) => col.id === filterObj.columnName\n    );\n\n    if (!filterObj.substring || !filterObj.columnName) {\n      return data;\n    } else {\n      try {\n        const regexp = new RegExp(filterObj.substring, \"i\");\n\n        return data.filter((row) => {\n          let target;\n          if (\n            column.type === \"json\" ||\n            column.type === \"date\" ||\n            column.type === \"date-ms\" ||\n            column.searchable === \"calculated\"\n          ) {\n            target = column.format(row);\n\n            if (!_.isString(target)) {\n              target = JSON.stringify(target);\n            }\n          } else {\n            target = _.get(row, column.selector);\n          }\n\n          return _.isString(target) && regexp.test(target);\n        });\n      } catch (e) {\n        // Bad or incomplete Regexp\n        console.log(e);\n        return [];\n      }\n    }\n  }, [data, dataTableColumns, filterObj]);\n\n  return (\n    <RawDataTable\n      title={<Heading level={0}>{title}</Heading>}\n      columns={dataTableColumns}\n      data={filteredItems}\n      pagination\n      paginationServer={paginationServer}\n      paginationPerPage={paginationPerPage}\n      paginationRowsPerPageOptions={[15, 30, 100, 1000]}\n      actions={\n        <>\n          {!paginationServer && showFilter && (\n            <Filter\n              columns={columns}\n              filterObj={filterObj}\n              setFilterObj={handleFilterChange}\n            />\n          )}\n          {showColumnSelector && (\n            <ColumnsSelector\n              columns={columns}\n              selected={tableState}\n              setSelected={setTableState}\n              defaultColumns={defaultColumns}\n            />\n          )}\n        </>\n      }\n      {...rest}\n    />\n  );\n}\n\nfunction Filter({ columns, filterObj, setFilterObj }) {\n  const [anchorEl, setAnchorEl] = React.useState(null);\n\n  const handleClick = (event) => {\n    setAnchorEl(event.currentTarget);\n  };\n\n  const handleClose = () => {\n    setAnchorEl(null);\n  };\n\n  const handleValueChange = (v) => {\n    setFilterObj({\n      columnName: filterObj.columnName,\n      substring: v,\n    });\n  };\n\n  const handleColumnChange = (c) => {\n    setFilterObj({\n      columnName: c,\n      substring: \"\",\n    });\n  };\n\n  return (\n    <>\n      <Tooltip title=\"Show Columns\">\n        <IconButton\n          onClick={handleClick}\n          label=\"Columns\"\n          color={_.get(filterObj, \"substring\") !== \"\" ? \"primary\" : \"default\"}\n        >\n          <SearchIcon />\n        </IconButton>\n      </Tooltip>\n      <Popover\n        onClose={handleClose}\n        anchorEl={anchorEl}\n        open={Boolean(anchorEl)}\n        anchorOrigin={{\n          vertical: \"bottom\",\n          horizontal: \"right\",\n        }}\n        transformOrigin={{\n          vertical: \"top\",\n          horizontal: \"right\",\n        }}\n        PaperProps={{\n          style: { padding: 10, display: \"flex\", flexDirection: \"row\" },\n        }}\n      >\n        <Select\n          label=\"Field\"\n          style={{ marginRight: 15, width: 200 }}\n          onChange={(e) => handleColumnChange(e.target.value)}\n          value={filterObj.columnName}\n          renderValue={(v) => getColumnLabelById(v, columns)}\n          displayEmpty={true}\n        >\n          {columns\n            .filter((col) => col.searchable !== false)\n            .map((col) => (\n              <MenuItem value={getColumnId(col)} key={getColumnId(col)}>\n                {getColumnLabel(col)}\n              </MenuItem>\n            ))}\n        </Select>\n        <Input\n          clearable\n          label=\"Substring\"\n          style={{ marginRight: 15, width: 200 }}\n          value={filterObj.substring}\n          onChange={handleValueChange}\n        />\n      </Popover>\n    </>\n  );\n}\n\nfunction getColumnLabelById(columnId, columns) {\n  const col = columns.find((c) => c.id === columnId || c.name === columnId);\n  return col.label || col.name;\n}\n\nfunction getColumnLabel(col) {\n  return col.label || col.name;\n}\n\nfunction getColumnId(col) {\n  return col.id || col.name;\n}\n\nfunction ColumnsSelector({ columns, selected, setSelected, defaultColumns }) {\n  const [anchorEl, setAnchorEl] = React.useState(null);\n\n  const handleClick = (event) => {\n    setAnchorEl(event.currentTarget);\n  };\n\n  const handleClose = () => {\n    setAnchorEl(null);\n  };\n\n  const handleChange = (columnId, checked) => {\n    if (!checked && selected.includes(columnId)) {\n      setSelected(selected.filter((v) => v !== columnId));\n    } else {\n      setSelected([...selected, columnId]);\n    }\n  };\n\n  const reset = () => {\n    setSelected(defaultColumns);\n  };\n  return (\n    <>\n      <Tooltip title=\"Show Columns\">\n        <IconButton onClick={handleClick} label=\"Columns\">\n          <ViewColumnIcon />\n        </IconButton>\n      </Tooltip>\n      <Menu\n        anchorEl={anchorEl}\n        open={Boolean(anchorEl)}\n        onClose={handleClose}\n        getContentAnchorEl={null}\n        anchorOrigin={{\n          vertical: \"bottom\",\n          horizontal: \"right\",\n        }}\n        transformOrigin={{\n          vertical: \"top\",\n          horizontal: \"right\",\n        }}\n      >\n        {[\n          ...columns.map((column) => (\n            <MenuItem\n              key={getColumnId(column)}\n              value={getColumnId(column)}\n              dense\n            >\n              <Checkbox\n                checked={selected.includes(getColumnId(column))}\n                onChange={(e) =>\n                  handleChange(getColumnId(column), e.target.checked)\n                }\n              />\n              <ListItemText primary={getColumnLabel(column)} />\n            </MenuItem>\n          )),\n          <MenuItem key=\"_reset\" value=\"_reset\" onClick={reset}>\n            <ListItemText>Reset to default</ListItemText>\n          </MenuItem>,\n        ]}\n      </Menu>\n    </>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/DateRangePicker.jsx",
    "content": "import React from \"react\";\nimport { Input } from \"./\";\nimport { makeStyles } from \"@material-ui/styles\";\n\nconst useStyles = makeStyles({\n  wrapper: {\n    display: \"flex\",\n  },\n  input: {\n    marginRight: 5,\n    flex: \"0 1 50%\",\n  },\n  quick: {\n    flex: \"0 0 auto\",\n  },\n});\n\nexport default function DateRangePicker({\n  onFromChange,\n  from,\n  onToChange,\n  to,\n  label,\n  disabled,\n}) {\n  const classes = useStyles();\n\n  return (\n    <div className={classes.wrapper}>\n      <Input\n        className={classes.input}\n        label={label && `${label} - From`}\n        value={from}\n        onChange={onFromChange}\n        type=\"datetime-local\"\n        fullWidth\n        clearable\n        disabled={disabled}\n      />\n      <Input\n        className={classes.input}\n        label={label && `${label} - To`}\n        value={to}\n        onChange={onToChange}\n        type=\"datetime-local\"\n        fullWidth\n        clearable\n        disabled={disabled}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/Dropdown.jsx",
    "content": "import React from \"react\";\nimport { Input } from \"./\";\nimport Autocomplete from \"@material-ui/lab/Autocomplete\";\nimport FormControl from \"@material-ui/core/FormControl\";\nimport InputLabel from \"@material-ui/core/InputLabel\";\nimport CloseIcon from \"@material-ui/icons/Close\";\nimport { InputAdornment, CircularProgress } from \"@material-ui/core\";\n\nexport default function ({\n  label,\n  className,\n  style,\n  error,\n  helperText,\n  name,\n  value,\n  placeholder,\n  loading,\n  disabled,\n  ...props\n}) {\n  return (\n    <FormControl style={style} className={className}>\n      {label && <InputLabel error={!!error}>{label}</InputLabel>}\n      <Autocomplete\n        {...props}\n        disabled={loading || disabled}\n        closeIcon={<CloseIcon />}\n        renderInput={({ InputProps, ...params }) => (\n          <Input\n            {...params}\n            InputProps={{\n              ...InputProps,\n              ...(loading && {\n                startAdornment: (\n                  <InputAdornment position=\"end\">\n                    <CircularProgress size={20} color=\"inherit\" thickness={6} />\n                  </InputAdornment>\n                ),\n              }),\n            }}\n            placeholder={loading ? \"Loading Options\" : placeholder}\n            name={name}\n            error={!!error}\n            helperText={helperText}\n          />\n        )}\n        value={value === undefined ? null : value} // convert undefined to null\n      />\n    </FormControl>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/DropdownButton.jsx",
    "content": "import React from \"react\";\nimport Button from \"@material-ui/core/Button\";\nimport ArrowDropDownIcon from \"@material-ui/icons/ArrowDropDown\";\nimport {\n  ClickAwayListener,\n  Popper,\n  MenuItem,\n  MenuList,\n} from \"@material-ui/core\";\nimport { Paper } from \"./\";\n\nexport default function DropdownButton({ children, options }) {\n  const [open, setOpen] = React.useState(false);\n  const anchorRef = React.useRef(null);\n\n  const handleToggle = () => {\n    setOpen((prevOpen) => !prevOpen);\n  };\n\n  const handleClose = (event) => {\n    if (anchorRef.current && anchorRef.current.contains(event.target)) {\n      return;\n    }\n\n    setOpen(false);\n  };\n\n  return (\n    <React.Fragment>\n      <Button\n        color=\"primary\"\n        variant=\"contained\"\n        ref={anchorRef}\n        onClick={handleToggle}\n      >\n        {children} <ArrowDropDownIcon />\n      </Button>\n\n      <Popper\n        open={open}\n        anchorEl={anchorRef.current}\n        disablePortal\n        placement=\"bottom-end\"\n      >\n        <Paper elevation={1}>\n          <ClickAwayListener onClickAway={handleClose}>\n            <MenuList>\n              {options.map(({ label, handler }, index) => (\n                <MenuItem\n                  key={index}\n                  onClick={(event) => {\n                    handler(event, index);\n                    setOpen(false);\n                  }}\n                >\n                  {label}\n                </MenuItem>\n              ))}\n            </MenuList>\n          </ClickAwayListener>\n        </Paper>\n      </Popper>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/Heading.jsx",
    "content": "import React from \"react\";\nimport Typography from \"@material-ui/core/Typography\";\n\nconst levelMap = [\"h6\", \"h5\", \"h4\", \"h3\", \"h2\", \"h1\"];\n\nexport default function ({ level = 3, ...props }) {\n  return <Typography variant={levelMap[level]} {...props} />;\n}\n"
  },
  {
    "path": "ui/src/components/Input.jsx",
    "content": "import React, { useRef } from \"react\";\nimport { TextField, InputAdornment, IconButton } from \"@material-ui/core\";\nimport ClearIcon from \"@material-ui/icons/Clear\";\n\nexport default function (props) {\n  const { label, clearable, onBlur, onChange, InputProps, ...rest } = props;\n  const inputRef = useRef();\n\n  function handleClear() {\n    inputRef.current.value = \"\";\n    if (onBlur) return onBlur(\"\");\n    if (onChange) return onChange(\"\");\n  }\n\n  function handleBlur(e) {\n    if (onBlur) onBlur(e.target.value);\n  }\n\n  function handleChange(e) {\n    if (onChange) onChange(e.target.value);\n  }\n\n  return (\n    <TextField\n      label={label}\n      inputRef={inputRef}\n      InputProps={\n        InputProps || {\n          endAdornment: clearable && (\n            <InputAdornment position=\"end\" style={{ marginRight: -8 }}>\n              <IconButton\n                size=\"small\"\n                onClick={handleClear}\n                disabled={props.disabled}\n              >\n                <ClearIcon />\n              </IconButton>\n            </InputAdornment>\n          ),\n        }\n      }\n      onBlur={handleBlur}\n      onChange={handleChange}\n      {...rest}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/KeyValueTable.jsx",
    "content": "import React from \"react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { List, ListItem, ListItemText, Tooltip } from \"@material-ui/core\";\nimport _ from \"lodash\";\n\nimport { useEnv } from \"../plugins/env\";\nimport {\n  timestampRenderer,\n  timestampMsRenderer,\n  durationRenderer,\n} from \"../utils/helpers\";\nimport { customTypeRenderers } from \"../plugins/customTypeRenderers\";\n\nconst useStyles = makeStyles((theme) => ({\n  value: {\n    flex: 0.7,\n  },\n  label: {\n    flex: 0.3,\n    minWidth: \"100px\",\n  },\n  labelText: {\n    fontWeight: \"bold !important\",\n  },\n}));\n\nexport default function KeyValueTable({ data }) {\n  const classes = useStyles();\n  const env = useEnv();\n  return (\n    <List>\n      {data.map((item, index) => {\n        let tooltipText = \"\";\n        let displayValue;\n        const renderer = item.type ? customTypeRenderers[item.type] : null;\n        if (renderer) {\n          displayValue = renderer(item.value, data, env);\n        } else {\n          switch (item.type) {\n            case \"date\":\n              displayValue =\n                !isNaN(item.value) && item.value > 0\n                  ? timestampRenderer(item.value)\n                  : \"N/A\";\n              tooltipText = new Date(item.value).toISOString();\n              break;\n            case \"date-ms\":\n              displayValue =\n                !isNaN(item.value) && item.value > 0\n                  ? timestampMsRenderer(item.value)\n                  : \"N/A\";\n              tooltipText = new Date(item.value).toISOString();\n              break;\n            case \"duration\":\n              displayValue =\n                !isNaN(item.value) && item.value > 0\n                  ? durationRenderer(item.value)\n                  : \"N/A\";\n              break;\n            default:\n              displayValue = !_.isNil(item.value) ? item.value : \"N/A\";\n          }\n        }\n\n        return (\n          <ListItem key={index} divider alignItems=\"flex-start\">\n            <ListItemText\n              className={classes.label}\n              classes={{ primary: classes.labelText }}\n              primary={item.label}\n            />\n\n            <ListItemText\n              className={classes.value}\n              primary={\n                <Tooltip\n                  placement=\"right\"\n                  title={tooltipText}\n                  open={tooltipText ? undefined : false}\n                >\n                  <span>{displayValue}</span>\n                </Tooltip>\n              }\n            />\n          </ListItem>\n        );\n      })}\n    </List>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/LinearProgress.jsx",
    "content": "import React from \"react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport clsx from \"clsx\";\nimport LinearProgress from \"@material-ui/core/LinearProgress\";\n\nconst useStyles = makeStyles({\n  progress: {\n    marginBottom: -4,\n    zIndex: 999,\n  },\n});\n\nexport default function ({ className, ...props }) {\n  const classes = useStyles();\n\n  return (\n    <LinearProgress\n      className={clsx([classes.progress, className])}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/NavLink.jsx",
    "content": "import React from \"react\";\nimport { Link as RouterLink, useHistory } from \"react-router-dom\";\nimport { Link } from \"@material-ui/core\";\nimport LaunchIcon from \"@material-ui/icons/Launch\";\nimport Url from \"url-parse\";\nimport { useEnv } from \"../plugins/env\";\nimport { getBasename } from \"../utils/helpers\";\nimport { cleanDuplicateSlash } from \"../plugins/fetch\";\n\n// 1. Strip `navigate` from props to prevent error\n// 2. Preserve stack param\n\nexport default React.forwardRef((props, ref) => {\n  const { navigate, path, newTab, absolutePath = false, ...rest } = props;\n  const { stack, defaultStack } = useEnv();\n\n  const url = new Url(path, {}, true);\n  if (stack !== defaultStack) {\n    url.query.stack = stack;\n  }\n\n  if (!newTab) {\n    return (\n      <Link ref={ref} component={RouterLink} to={url.toString()} {...rest}>\n        {rest.children}\n      </Link>\n    );\n  } else {\n    const href = absolutePath ? url.toString() : cleanDuplicateSlash(getBasename() + url.toString());\n    return (\n      <Link ref={ref} target=\"_blank\" href={href}>\n        {rest.children}\n        &nbsp;\n        <LaunchIcon fontSize=\"small\" style={{ verticalAlign: \"middle\" }} />\n      </Link>\n    );\n  }\n});\n\nexport function usePushHistory() {\n  const history = useHistory();\n  const { stack, defaultStack } = useEnv();\n\n  return (path) => {\n    const url = new Url(path, {}, true);\n    if (stack !== defaultStack) {\n      url.query.stack = stack;\n    }\n\n    history.push(url.toString());\n  };\n}\n"
  },
  {
    "path": "ui/src/components/Paper.jsx",
    "content": "import React from \"react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport clsx from \"clsx\";\nimport Paper from \"@material-ui/core/Paper\";\n\nconst useStyles = makeStyles({\n  padded: {\n    padding: 15,\n  },\n});\n\nexport default React.forwardRef(function (\n  { elevation, className, padded, ...props },\n  ref\n) {\n  const classes = useStyles();\n  const internalClassName = [];\n  if (padded) internalClassName.push(classes.padded);\n  return (\n    <Paper\n      ref={ref}\n      elevation={elevation || 0}\n      className={clsx([internalClassName, className])}\n      {...props}\n    />\n  );\n});\n"
  },
  {
    "path": "ui/src/components/Pill.jsx",
    "content": "import { makeStyles } from \"@material-ui/styles\";\nimport Chip from \"@material-ui/core/Chip\";\n\nconst COLORS = {\n  red: \"rgb(229, 9, 20)\",\n  yellow: \"rgb(251, 164, 4)\",\n  green: \"rgb(65, 185, 87)\",\n};\n\nconst useStyles = makeStyles({\n  pill: {\n    borderColor: (props) => COLORS[props.color],\n    color: (props) => COLORS[props.color],\n  },\n});\n\nexport default function Pill({ color, ...props }) {\n  const classes = useStyles({ color });\n\n  return (\n    <Chip\n      color={color && \"primary\"}\n      variant=\"outlined\"\n      {...props}\n      classes={{ colorPrimary: classes.pill }}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/PrimaryButton.jsx",
    "content": "import React from \"react\";\nimport Button from \"@material-ui/core/Button\";\n\nexport default function (props) {\n  return <Button variant=\"contained\" color=\"primary\" {...props} />;\n}\n"
  },
  {
    "path": "ui/src/components/ReactJson.jsx",
    "content": "import React, { useRef } from \"react\";\nimport Editor from \"@monaco-editor/react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { InputLabel, IconButton, Tooltip } from \"@material-ui/core\";\nimport clsx from \"clsx\";\nimport ExpandMoreIcon from \"@material-ui/icons/ExpandMore\";\nimport ExpandLessIcon from \"@material-ui/icons/ExpandLess\";\nimport FileCopyIcon from \"@material-ui/icons/FileCopy\";\n\nconst useStyles = makeStyles({\n  monaco: {},\n  outerWrapper: {\n    height: \"100%\",\n    display: \"flex\",\n    flexDirection: \"column\",\n    paddingTop: 15,\n  },\n  editorWrapper: {\n    flex: 1,\n    marginLeft: 10,\n    position: \"relative\",\n    minHeight: 0,\n  },\n  label: {\n    marginTop: 13,\n    marginBottom: 10,\n    flex: 1,\n  },\n  toolbar: {\n    paddingRight: 15,\n    paddingLeft: 15,\n    display: \"flex\",\n    alignItems: \"flex-start\",\n    flexDirection: \"row\",\n  },\n});\n\nexport default function ReactJson({\n  className,\n  label,\n  src,\n  lineNumbers = true,\n}) {\n  const classes = useStyles();\n  const editorRef = useRef(null);\n\n  function handleEditorMount(editor) {\n    editorRef.current = editor;\n  }\n\n  function handleCopyAll() {\n    const editor = editorRef.current;\n    const range = editor.getModel().getFullModelRange();\n    editor.setSelection(range);\n    editor\n      .getAction(\"editor.action.clipboardCopyWithSyntaxHighlightingAction\")\n      .run();\n  }\n\n  function handleExpandAll() {\n    editorRef.current.getAction(\"editor.unfoldAll\").run();\n  }\n\n  function handleCollapse() {\n    editorRef.current.getAction(\"editor.foldLevel2\").run();\n  }\n\n  return (\n    <div className={clsx([classes.outerWrapper, className])}>\n      <div className={classes.toolbar}>\n        <InputLabel variant=\"outlined\" className={classes.label}>\n          {label}\n        </InputLabel>\n\n        <Tooltip title=\"Collapse All\">\n          <IconButton onClick={handleCollapse}>\n            <ExpandLessIcon />\n          </IconButton>\n        </Tooltip>\n        <Tooltip title=\"Expand All\">\n          <IconButton onClick={handleExpandAll}>\n            <ExpandMoreIcon />\n          </IconButton>\n        </Tooltip>\n        <Tooltip title=\"Copy All\">\n          <IconButton onClick={handleCopyAll}>\n            <FileCopyIcon />\n          </IconButton>\n        </Tooltip>\n      </div>\n      <div className={classes.editorWrapper}>\n        <Editor\n          className={classes.monaco}\n          height=\"100%\"\n          defaultLanguage=\"json\"\n          onMount={handleEditorMount}\n          defaultValue={JSON.stringify(src, null, 2)}\n          options={{\n            readOnly: true,\n            tabSize: 2,\n            minimap: { enabled: false },\n            lightbulb: { enabled: false },\n            scrollbar: { useShadows: false },\n            quickSuggestions: false,\n            showFoldingControls: \"always\",\n            lineNumbers: lineNumbers ? \"on\" : \"off\",\n\n            // Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882\n            lineDecorationsWidth: 0,\n            lineNumbersMinChars: 0,\n            renderLineHighlight: \"none\",\n\n            overviewRulerLanes: 0,\n            hideCursorInOverviewRuler: true,\n            overviewRulerBorder: false,\n          }}\n        />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/SecondaryButton.jsx",
    "content": "import React from \"react\";\nimport Button from \"@material-ui/core/Button\";\n\nexport default function (props) {\n  return <Button variant=\"outlined\" color=\"secondary\" {...props} />;\n}\n"
  },
  {
    "path": "ui/src/components/Select.jsx",
    "content": "import React from \"react\";\nimport Select from \"@material-ui/core/Select\";\nimport FormControl from \"@material-ui/core/FormControl\";\nimport InputLabel from \"@material-ui/core/InputLabel\";\nimport _ from \"lodash\";\n\nexport default function ({ label, fullWidth, nullable = true, ...props }) {\n  return (\n    <FormControl fullWidth={fullWidth}>\n      {label && <InputLabel>{label}</InputLabel>}\n      <Select\n        variant=\"outlined\"\n        fullWidth={fullWidth}\n        displayEmpty={nullable}\n        renderValue={(v) => (_.isNil(v) ? \"\" : v)}\n        {...props}\n      />\n    </FormControl>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/SplitButton.jsx",
    "content": "import React from \"react\";\nimport Grid from \"@material-ui/core/Grid\";\nimport Button from \"@material-ui/core/Button\";\nimport ButtonGroup from \"@material-ui/core/ButtonGroup\";\nimport ArrowDropDownIcon from \"@material-ui/icons/ArrowDropDown\";\nimport ClickAwayListener from \"@material-ui/core/ClickAwayListener\";\nimport Grow from \"@material-ui/core/Grow\";\nimport Paper from \"@material-ui/core/Paper\";\nimport Popper from \"@material-ui/core/Popper\";\nimport MenuItem from \"@material-ui/core/MenuItem\";\nimport MenuList from \"@material-ui/core/MenuList\";\n\nexport default function SplitButton({ children, options, onPrimaryClick }) {\n  const [open, setOpen] = React.useState(false);\n  const anchorRef = React.useRef(null);\n\n  const handleToggle = () => {\n    setOpen((prevOpen) => !prevOpen);\n  };\n\n  const handleClose = (event) => {\n    if (anchorRef.current && anchorRef.current.contains(event.target)) {\n      return;\n    }\n\n    setOpen(false);\n  };\n\n  return (\n    <Grid container direction=\"column\" alignItems=\"center\">\n      <Grid item xs={12}>\n        <ButtonGroup ref={anchorRef}>\n          <Button onClick={onPrimaryClick} color=\"primary\" variant=\"contained\">\n            {children}\n          </Button>\n          <Button color=\"primary\" variant=\"contained\" onClick={handleToggle}>\n            <ArrowDropDownIcon />\n          </Button>\n        </ButtonGroup>\n        <Popper\n          open={open}\n          anchorEl={anchorRef.current}\n          role={undefined}\n          transition\n          disablePortal\n        >\n          {({ TransitionProps, placement }) => (\n            <Grow\n              {...TransitionProps}\n              style={{\n                transformOrigin:\n                  placement === \"bottom\" ? \"center top\" : \"center bottom\",\n              }}\n            >\n              <Paper>\n                <ClickAwayListener onClickAway={handleClose}>\n                  <MenuList id=\"split-button-menu\">\n                    {options.map(({ label, handler }, index) => (\n                      <MenuItem\n                        key={index}\n                        onClick={(event) => {\n                          handler(event, index);\n                          setOpen(false);\n                        }}\n                      >\n                        {label}\n                      </MenuItem>\n                    ))}\n                  </MenuList>\n                </ClickAwayListener>\n              </Paper>\n            </Grow>\n          )}\n        </Popper>\n      </Grid>\n    </Grid>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/StatusBadge.jsx",
    "content": "import React from \"react\";\nimport { Chip } from \"@material-ui/core\";\n\nconst colorMap = {\n  success: \"#41b957\",\n  progress: \"#1f83db\",\n  error: \"#e50914\",\n  warning: \"#fba404\",\n};\n\nexport default function StatusBadge({ status, ...props }) {\n  let color;\n  switch (status) {\n    case \"RUNNING\":\n      color = colorMap.progress;\n      break;\n    case \"COMPLETED\":\n      color = colorMap.success;\n      break;\n    case \"PAUSED\":\n      color = colorMap.warning;\n      break;\n    default:\n      color = colorMap.error;\n  }\n\n  return (\n    <Chip\n      {...props}\n      style={color && { backgroundColor: color, color: \"white\" }}\n      label={status}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/Tabs.jsx",
    "content": "import React from \"react\";\nimport { Tabs as RawTabs, Tab as RawTab } from \"@material-ui/core\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { colors } from \"../theme/variables\";\nimport { theme } from \"../theme\";\n\n// Override styles for 'Contextual' tabs\nconst useContextualTabStyles = makeStyles({\n  root: {\n    color: colors.gray02,\n    textTransform: \"none\",\n    height: 38,\n    minHeight: 38,\n    padding: \"12px 16px\",\n    backgroundColor: colors.gray13,\n    [theme.breakpoints.up(\"md\")]: {\n      minWidth: 0,\n    },\n    width: \"auto\",\n    \"&:hover\": {\n      backgroundColor: colors.grayXLight,\n      color: colors.gray02,\n    },\n  },\n  selected: {\n    backgroundColor: \"white\",\n    color: colors.black,\n    \"&:hover\": {\n      backgroundColor: \"white\",\n      color: colors.black,\n    },\n  },\n  wrapper: {\n    width: \"auto\",\n  },\n});\n\nconst useContextualTabsStyles = makeStyles({\n  indicator: {\n    height: 0,\n  },\n  flexContainer: {\n    backgroundColor: colors.gray13,\n  },\n});\n\nexport default function Tabs({ contextual, children, ...props }) {\n  const classes = useContextualTabsStyles();\n  return (\n    <RawTabs\n      classes={contextual ? classes : null}\n      indicatorColor=\"primary\"\n      {...props}\n    >\n      {contextual\n        ? children.map((child, idx) =>\n            React.cloneElement(child, { contextual: true, key: idx })\n          )\n        : children}\n    </RawTabs>\n  );\n}\n\nexport function Tab({ contextual, ...props }) {\n  const classes = useContextualTabStyles();\n  return <RawTab classes={contextual ? classes : null} {...props} />;\n}\n"
  },
  {
    "path": "ui/src/components/TaskLink.jsx",
    "content": "import NavLink from \"./NavLink\";\nimport rison from \"rison\";\n\nexport default function ({ taskId, workflowId }) {\n  const taskObj = rison.encode({\n    id: taskId,\n  });\n  return (\n    <NavLink path={`/execution/${workflowId}?task=${taskObj}`}>\n      {taskId}\n    </NavLink>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/TaskNameInput.jsx",
    "content": "import { Dropdown } from \".\";\nimport { isEmpty } from \"lodash\";\nimport { useTaskNames } from \"../data/task\";\n\nexport default function TaskNameInput(props) {\n  const taskNames = useTaskNames();\n\n  return (\n    <Dropdown\n      label={props.label || \"Task Name\"}\n      options={taskNames}\n      multiple\n      freeSolo\n      loading={isEmpty(taskNames)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/TertiaryButton.jsx",
    "content": "import React from \"react\";\nimport Button from \"@material-ui/core/Button\";\n\nexport default function (props) {\n  return <Button {...props} />;\n}\n"
  },
  {
    "path": "ui/src/components/Text.jsx",
    "content": "import React from \"react\";\nimport Typography from \"@material-ui/core/Typography\";\n\nconst levelMap = [\"caption\", \"body2\", \"body1\"];\n\nexport default function ({ level = 1, ...props }) {\n  return <Typography variant={levelMap[level]} {...props} />;\n}\n"
  },
  {
    "path": "ui/src/components/WorkflowNameInput.jsx",
    "content": "import { useWorkflowNames } from \"../data/workflow\";\nimport { Dropdown } from \".\";\nimport { isEmpty } from \"lodash\";\n\nexport default function WorkflowNameInput(props) {\n  const workflowNames = useWorkflowNames();\n\n  return (\n    <Dropdown\n      label={props.label || \"Workflow Name\"}\n      options={workflowNames}\n      multiple\n      freeSolo\n      loading={isEmpty(workflowNames)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/definitionList/DefinitionList.jsx",
    "content": "import withStyles from \"@material-ui/core/styles/withStyles\";\nimport Table from \"@material-ui/core/Table\";\nimport TableBody from \"@material-ui/core/TableBody\";\nimport * as React from \"react\";\n\nconst DefinitionList = ({ children, classes }) => (\n  <Table>\n    <TableBody className={classes.root}>{children}</TableBody>\n  </Table>\n);\n\nexport default withStyles((theme) => ({\n  root: {\n    \"& tr:first-child\": {\n      borderTopColor: theme.palette.divider,\n      borderTopStyle: \"solid\",\n      borderTopWidth: \"1px\",\n    },\n  },\n}))(DefinitionList);\n"
  },
  {
    "path": "ui/src/components/diagram/TaskPointer.d.ts",
    "content": "export type TaskPointer = {\n  id: ?string;\n  ref: ?string;\n};\n"
  },
  {
    "path": "ui/src/components/diagram/TaskResult.d.ts",
    "content": "export type TaskWrapper = {};\n"
  },
  {
    "path": "ui/src/components/diagram/WorkflowDAG.js",
    "content": "import _ from \"lodash\";\nimport { graphlib } from \"dagre-d3\";\n\nexport default class WorkflowDAG {\n  constructor(execution, workflowDef) {\n    this.execution = execution;\n    this.workflowDef = workflowDef;\n\n    this.graph = new graphlib.Graph({ directed: true, compound: false });\n    this.taskResultsByRef = new Map();\n    this.taskResultsById = new Map();\n\n    this.constructGraph();\n  }\n\n  addTaskResult(ref, task) {\n    if (!this.taskResultsByRef.has(ref)) {\n      this.taskResultsByRef.set(ref, []);\n    }\n    this.taskResultsByRef.get(ref).push(task);\n    this.taskResultsById.set(task.taskId, task);\n  }\n\n  getLastTaskResult(ref) {\n    if (this.taskResultsByRef.has(ref)) {\n      return _.last(this.taskResultsByRef.get(ref));\n    } else {\n      return null;\n    }\n  }\n\n  constructGraph() {\n    const { workflowDef, execution } = this;\n\n    // Definition Only\n    if (workflowDef) {\n      this.defToGraph(workflowDef);\n    }\n    // Definition part of Execution object\n    else if (execution) {\n      let isTerminated = false;\n      for (let task of execution.tasks) {\n        if (task[\"taskType\"] === \"TERMINATE\") isTerminated = true;\n\n        this.addTaskResult(task[\"referenceTaskName\"], task);\n      }\n\n      if (execution.status) {\n        this.addTaskResult(\"__start\", { status: \"COMPLETED\" });\n        if (execution.status === \"COMPLETED\" && !isTerminated) {\n          this.addTaskResult(\"__final\", { status: \"COMPLETED\" });\n        }\n      }\n\n      this.defToGraph(execution.workflowDefinition);\n    } else {\n      throw new Error(\n        \"Must pass either workflowDef or execution in constructor\"\n      );\n    }\n  }\n\n  defToGraph(workflowDef) {\n    const definedTasks = [...workflowDef.tasks];\n\n    definedTasks.unshift({\n      type: \"TERMINAL\",\n      name: \"start\",\n      taskReferenceName: \"__start\",\n    });\n\n    definedTasks.push({\n      type: \"TERMINAL\",\n      name: \"final\",\n      taskReferenceName: \"__final\",\n    });\n\n    // Recursively process tasks\n    this.processTaskList(definedTasks, []);\n\n    // All branches are terminated by a user-defined 'TERMINATE' task.\n    if (_.isEmpty(this.graph.inEdges(\"__final\"))) {\n      this.graph.removeNode(\"__final\");\n    }\n  }\n\n  getExecutionStatus(ref) {\n    const taskResult = this.getLastTaskResult(ref);\n    if (taskResult) {\n      return taskResult.status;\n    } else {\n      return null;\n    }\n  }\n\n  switchBranchTaken(caseValue, decisionTaskRef, type) {\n    if (!this.taskResultsByRef.has(decisionTaskRef)) return false;\n\n    const switchTaskResult = this.getLastTaskResult(decisionTaskRef);\n    const cases = Object.keys(switchTaskResult.workflowTask.decisionCases);\n\n    // Required until DECISION is fully removed\n    let resultField = type === \"SWITCH\" ? \"evaluationResult\" : \"caseOutput\";\n    const actualValue = _.get(switchTaskResult, `outputData.${resultField}[0]`);\n    if (actualValue === undefined) return false;\n\n    if (caseValue) {\n      // This is a case branch. Check whether it is the right one.\n      return caseValue === actualValue;\n    } else {\n      // This is the default branch (caseValue === null). Check whether actual value is in one of the other cases\n      return !cases.includes(actualValue);\n    }\n  }\n\n  addVertex(taskConfig, antecedents) {\n    const taskResults = taskConfig.aliasForRef\n      ? this.taskResultsByRef.get(taskConfig.aliasForRef)\n      : this.taskResultsByRef.get(taskConfig.taskReferenceName);\n    const lastTaskResult = _.last(taskResults);\n    const vertex = {\n      taskResults: taskResults || [\n        {\n          workflowTask: taskConfig,\n        },\n      ],\n      name: taskConfig.name,\n      ref: taskConfig.taskReferenceName,\n      type: taskConfig.type,\n      aliasForRef: taskConfig.aliasForRef,\n    };\n\n    if (lastTaskResult) {\n      vertex.status = lastTaskResult.status;\n    }\n\n    this.graph.setNode(taskConfig.taskReferenceName, vertex);\n    for (let antecedent of antecedents) {\n      const antecedentExecuted = !!this.getExecutionStatus(\n        antecedent.aliasForRef || antecedent.taskReferenceName\n      );\n      const edgeParams = {};\n\n      // Special case - When the antecedent of an executed node is a SWITCH, the edge may not necessarily be highlighted.\n      // E.g. the default edge not taken.\n      //\n      // SWITCH is the newer version of DECISION and DECISION is deprecated\n      //\n      // Skip this if current type is DO_WHILE_END - which means the SWITCH is one of the bundled\n      // loop tasks and the current task is not the result of a decision\n      if (\n        taskConfig.type !== \"DO_WHILE_END\" &&\n        (antecedent.type === \"SWITCH\" || antecedent.type === \"DECISION\")\n      ) {\n        edgeParams.caseValue = getCaseValue(\n          taskConfig.taskReferenceName,\n          antecedent\n        );\n\n        // Highlight edge as executed only after thorough test\n        const branchTaken = this.switchBranchTaken(\n          edgeParams.caseValue,\n          antecedent.taskReferenceName,\n          antecedent.type\n        );\n        if (branchTaken) {\n          edgeParams.executed = true;\n        }\n      } else if (\n        lastTaskResult &&\n        lastTaskResult.status &&\n        antecedentExecuted\n      ) {\n        edgeParams.executed = true;\n      }\n\n      this.graph.setEdge(\n        antecedent.taskReferenceName,\n        taskConfig.taskReferenceName,\n        edgeParams\n      );\n    }\n  }\n\n  processTaskList(tasks, antecedents) {\n    console.assert(Array.isArray(antecedents));\n\n    let currAntecedents = antecedents;\n    for (const task of tasks.values()) {\n      currAntecedents = this.processTask(task, currAntecedents);\n    }\n\n    return currAntecedents;\n  }\n\n  // Nodes are connected to previous\n  processSwitchTask(decisionTask, antecedents) {\n    console.assert(Array.isArray(antecedents));\n    const retval = [];\n\n    this.addVertex(decisionTask, antecedents);\n\n    if (_.isEmpty(decisionTask.defaultCase)) {\n      retval.push(decisionTask); // Empty default path\n    } else {\n      retval.push(\n        ...this.processTaskList(decisionTask.defaultCase, [decisionTask])\n      );\n    }\n\n    retval.push(\n      ..._.flatten(\n        Object.entries(decisionTask.decisionCases).map(([caseValue, tasks]) => {\n          return this.processTaskList(tasks, [decisionTask]);\n        })\n      )\n    );\n\n    return retval;\n  }\n\n  processForkJoinDynamic(dfTask, antecedents) {\n    console.assert(Array.isArray(antecedents));\n\n    // This is the DF task (dotted bar) itself.\n    this.addVertex(dfTask, antecedents);\n\n    // Only add placeholder if there are 0 spawned tasks for this DF\n    const dfTaskResult = this.getLastTaskResult(dfTask.taskReferenceName);\n    const forkedTasks = _.get(dfTaskResult, \"inputData.forkedTaskDefs\");\n    const forkedTasksCount = _.get(forkedTasks, \"length\");\n\n    if (!forkedTasksCount) {\n      const placeholderRef = dfTask.taskReferenceName + \"_DF_EMPTY_PLACEHOLDER\";\n\n      const placeholderTask = {\n        name: placeholderRef, // will be overwritten if results available\n        taskReferenceName: placeholderRef, // will be overwritten if results available\n        type: \"DF_EMPTY_PLACEHOLDER\",\n      };\n\n      if (_.get(dfTaskResult, \"status\")) {\n        // Edge case: Backfill placeholder status for 'no tasks spawned'.\n        this.addTaskResult(placeholderRef, { status: dfTaskResult.status });\n      }\n\n      this.addVertex(placeholderTask, [dfTask]);\n      return [placeholderTask];\n    } else {\n      return dfTaskResult.inputData.forkedTaskDefs.map((task) => {\n        this.addVertex(task, [dfTask]);\n        return task;\n      });\n    }\n  }\n\n  getRefTaskChilds(task) {\n    switch (task.type) {\n      case \"FORK_JOIN\": {\n        const outerForkTasks = task.forkTasks || [];\n        return _.flatten(\n          outerForkTasks.map((innerForkTasks) =>\n            innerForkTasks.map((tasks) => tasks)\n          )\n        );\n      }\n\n      case \"FORK_JOIN_DYNAMIC\": {\n        const dfTaskResult = this.getLastTaskResult(task.taskReferenceName);\n        const forkedTasks = _.get(dfTaskResult, \"inputData.forkedTaskDefs\");\n        const forkedTasksCount = _.get(forkedTasks, \"length\");\n\n        if (!forkedTasksCount) {\n          const placeholderRef =\n            task.taskReferenceName + \"_DF_EMPTY_PLACEHOLDER\";\n          const placeholderTask = {\n            name: placeholderRef, // will be overwritten if results available\n            taskReferenceName: placeholderRef, // will be overwritten if results available\n            type: \"DF_EMPTY_PLACEHOLDER\",\n          };\n          return [placeholderTask];\n        } else {\n          return dfTaskResult.inputData.forkedTaskDefs;\n        }\n      }\n\n      case \"DECISION\": // DECISION is deprecated and will be removed in a future release\n      case \"SWITCH\": {\n        const retval = [];\n        if (!_.isEmpty(task.defaultCase)) {\n          retval.push(...this.getRefTask(task.defaultCase));\n        }\n        retval.push(\n          ..._.flatten(\n            Object.entries(task.decisionCases).map(([caseValue, tasks]) => {\n              return tasks;\n            })\n          )\n        );\n        return retval;\n      }\n\n      case \"DO_WHILE\": {\n        return task.loopOver;\n      }\n\n      /*\n      case \"TERMINATE\": \n      case \"JOIN\": \n      case \"TERMINAL\":\n      case \"EVENT\":\n      case \"SUB_WORKFLOW\":\n      case \"EXCLUSIVE_JOIN\":\n      */\n      default: {\n        return [];\n      }\n    }\n  }\n\n  getRefTask(task) {\n    const taskRefs = this.getRefTaskChilds(task)\n      .map((t) => {\n        return this.getRefTask(t);\n      })\n      .reduce((r, tasks) => {\n        return r.concat(tasks);\n      }, []);\n    return [task].concat(taskRefs);\n  }\n\n  processDoWhileTask(doWhileTask, antecedents) {\n    console.assert(Array.isArray(antecedents));\n\n    const hasDoWhileExecuted = !!this.getExecutionStatus(\n      doWhileTask.taskReferenceName\n    );\n\n    this.addVertex(doWhileTask, antecedents);\n\n    // Bottom bar\n    // aliasForRef indicates when the bottom bar is clicked one we should highlight the ref\n    let endDoWhileTask = {\n      type: \"DO_WHILE_END\",\n      name: doWhileTask.name,\n      taskReferenceName: doWhileTask.taskReferenceName + \"-END\",\n      aliasForRef: doWhileTask.taskReferenceName,\n    };\n\n    const loopOverRefPrefixes = this.getRefTask(doWhileTask).map(\n      (t) => t.taskReferenceName\n    );\n    if (hasDoWhileExecuted) {\n      // Create cosmetic LOOP edges between top and bottom bars\n      this.graph.setEdge(\n        doWhileTask.taskReferenceName,\n        doWhileTask.taskReferenceName + \"-END\",\n        {\n          type: \"loop\",\n          executed: hasDoWhileExecuted,\n        }\n      );\n\n      const loopOverRefs = Array.from(this.taskResultsByRef.keys()).filter(\n        (key) => {\n          for (let prefix of loopOverRefPrefixes) {\n            if (key.startsWith(prefix + \"__\")) return true;\n          }\n          return false;\n        }\n      );\n\n      const loopTaskResults = [];\n      for (let ref of loopOverRefs) {\n        const refList = this.taskResultsByRef.get(ref);\n        loopTaskResults.push(...refList);\n      }\n\n      const loopTasks = loopTaskResults.map((task) => ({\n        name: task.taskDefName,\n        taskReferenceName: task.referenceTaskName,\n        type: task.taskType,\n      }));\n\n      for (let task of loopTasks) {\n        this.addVertex(task, [doWhileTask]);\n      }\n\n      this.addVertex(endDoWhileTask, [...loopTasks]);\n    } else {\n      // Definition view (or not executed)\n\n      this.processTaskList(doWhileTask.loopOver, [doWhileTask]);\n\n      const lastLoopTask = _.last(doWhileTask.loopOver);\n\n      // Connect the end of each case to the loop end\n      if (\n        lastLoopTask?.type === \"SWITCH\" ||\n        lastLoopTask?.type === \"DECISION\"\n      ) {\n        Object.entries(lastLoopTask.decisionCases).forEach(\n          ([caseValue, tasks]) => {\n            const lastTaskInCase = _.last(tasks);\n            this.addVertex(endDoWhileTask, [lastTaskInCase]);\n          }\n        );\n      }\n\n      // Default case\n      this.addVertex(endDoWhileTask, [lastLoopTask]);\n    }\n\n    // Create reverse loop edge\n    this.graph.setEdge(\n      doWhileTask.taskReferenceName,\n      doWhileTask.taskReferenceName + \"-END\",\n      {\n        type: \"loop\",\n        executed: hasDoWhileExecuted,\n      }\n    );\n\n    return [endDoWhileTask];\n  }\n\n  processForkJoin(forkJoinTask, antecedents) {\n    let outerForkTasks = forkJoinTask.forkTasks || [];\n\n    // Add FORK_JOIN task itself (solid bar)\n    this.addVertex(forkJoinTask, antecedents);\n\n    // Each sublist is executed in parallel. Tasks within sublist executed sequentially\n    return _.flatten(\n      outerForkTasks.map((innerForkTasks) =>\n        this.processTaskList(innerForkTasks, [forkJoinTask])\n      )\n    );\n  }\n\n  processJoin(joinTask, antecedents) {\n    // Process as a normal node UNLESS in special case of an externalized dynamic-fork. In which case - backfill spawned children.\n\n    const taskResult = _.last(\n      this.taskResultsByRef.get(joinTask.taskReferenceName)\n    );\n    const backfilled = [];\n    const antecedent = _.first(antecedents);\n\n    if (_.has(taskResult, \"inputData.joinOn\")) {\n      const backfillRefs = taskResult.inputData.joinOn;\n      if (_.get(antecedent, \"type\") === \"DF_EMPTY_PLACEHOLDER\") {\n        const twoBeforeRef = _.first(\n          this.graph.predecessors(antecedent.taskReferenceName)\n        );\n        const twoBefore = this.graph.node(twoBeforeRef);\n        if (_.get(twoBefore, \"type\") === \"FORK_JOIN_DYNAMIC\") {\n          console.log(\"Special case - backfill for externalized DYNAMIC_FORK\");\n\n          const twoBeforeDef = _.first(twoBefore.taskResults).workflowTask;\n          for (let ref of backfillRefs) {\n            const tasks = this.taskResultsByRef.get(ref);\n            for (let task of tasks) {\n              this.addVertex(task.workflowTask, [twoBeforeDef]);\n              backfilled.push(task.workflowTask);\n            }\n          }\n        }\n      }\n    }\n\n    if (backfilled.length > 0) {\n      // Remove placeholder if needed\n      this.graph.removeNode(antecedent.taskReferenceName);\n\n      // backfilled nodes converge onto join\n      this.addVertex(joinTask, backfilled);\n    } else {\n      this.addVertex(joinTask, antecedents);\n    }\n\n    return [joinTask];\n  }\n\n  // returns tails = [...]\n  processTask(task, antecedents) {\n    switch (task.type) {\n      case \"FORK_JOIN\": {\n        return this.processForkJoin(task, antecedents);\n      }\n\n      case \"FORK_JOIN_DYNAMIC\": {\n        return this.processForkJoinDynamic(task, antecedents);\n      }\n\n      case \"DECISION\": // DECISION is deprecated and will be removed in a future release\n      case \"SWITCH\": {\n        return this.processSwitchTask(task, antecedents);\n      }\n\n      case \"TERMINATE\": {\n        this.addVertex(task, antecedents);\n        return [];\n      }\n\n      case \"DO_WHILE\": {\n        return this.processDoWhileTask(task, antecedents);\n      }\n\n      case \"JOIN\": {\n        return this.processJoin(task, antecedents);\n      }\n      /*\n      case \"TERMINAL\":\n      case \"EVENT\":\n      case \"SUB_WORKFLOW\":\n      case \"EXCLUSIVE_JOIN\":\n      */\n      default: {\n        this.addVertex(task, antecedents);\n        return [task];\n      }\n    }\n  }\n\n  getSiblings(taskPointer) {\n    let ref;\n    if (taskPointer.id) {\n      const taskResult = this.taskResultsById.get(taskPointer.id);\n      if (taskResult) {\n        ref = taskResult.referenceTaskName;\n      }\n    } else {\n      ref = taskPointer.ref;\n    }\n\n    if (!ref) return;\n\n    const predecessors = this.graph.predecessors(ref);\n    // Nodes might have multiple predecessors e.g. following Decision node.\n    // But when parent is FORK_JOIN_DYNAMIC there should only be one.\n    if (_.size(predecessors) === 1) {\n      const parent = this.graph.node(_.first(predecessors));\n      if (parent && parent.status) {\n        if (parent.type === \"FORK_JOIN_DYNAMIC\") {\n          return this.graph\n            .successors(parent.ref)\n            .map((ref) => this.graph.node(ref));\n        } else if (parent.type === \"DO_WHILE\") {\n          return this.graph\n            .successors(parent.ref)\n            .map((ref) => this.graph.node(ref))\n            .filter((node) => node.type !== \"DO_WHILE_END\");\n        }\n      }\n    }\n    // Returns undefined\n  }\n\n  findTaskResultById(id) {\n    return this.taskResultsById.get(id);\n  }\n\n  getRetries(taskPointer) {\n    if (taskPointer.id) {\n      const taskResult = this.taskResultsById.get(taskPointer.id);\n      if (taskResult) {\n        const ref = taskResult.referenceTaskName;\n        return this.taskResultsByRef.get(ref);\n      }\n    } else {\n      return this.taskResultsByRef.get(taskPointer.ref);\n    }\n  }\n\n  resolveTaskResult(taskPointer) {\n    if (!taskPointer) {\n      return null;\n    } else if (taskPointer.id) {\n      return this.taskResultsById.get(taskPointer.id);\n    } else {\n      const node = this.graph.node(taskPointer.ref);\n      return _.last(node.taskResults);\n    }\n  }\n}\n\nfunction getCaseValue(ref, decisionTask) {\n  for (const [caseValue, taskList] of Object.entries(\n    decisionTask.decisionCases\n  )) {\n    if (!_.isEmpty(taskList) && ref === taskList[0].taskReferenceName) {\n      return caseValue;\n    }\n  }\n\n  return null;\n}\n\n/*\n\nNode {\n  taskResults: [... TaskResult]\n}\n\nTaskResult {\n  ...[Task Result fields only present if executed],\n  workflowTask: {\n    ... Always populated\n  }\n}\n\n*/\n"
  },
  {
    "path": "ui/src/components/diagram/WorkflowGraph.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { graphlib, render as dagreD3Render, intersect } from \"dagre-d3\";\nimport * as d3 from \"d3\";\nimport _ from \"lodash\";\nimport { withResizeDetector } from \"react-resize-detector\";\nimport parseSvgPath from \"parse-svg-path\";\nimport { IconButton, Toolbar } from \"@material-ui/core\";\nimport ZoomInIcon from \"@material-ui/icons/ZoomIn\";\nimport ZoomOutIcon from \"@material-ui/icons/ZoomOut\";\nimport ZoomOutMapIcon from \"@material-ui/icons/ZoomOutMap\";\nimport HomeIcon from \"@material-ui/icons/Home\";\nimport \"./diagram.scss\";\n\nconst BAR_MARGIN = 50;\nconst BOTTOM_MARGIN = 30;\nconst GRAPH_MIN_HEIGHT = 600;\n\nclass WorkflowGraph extends React.Component {\n  constructor(props) {\n    super(props);\n    this.renderer = new dagreD3Render();\n    this.renderer.shapes().bar = barRenderer;\n    this.renderer.shapes().stack = stackRenderer;\n\n    this.svgRef = React.createRef();\n  }\n\n  componentDidUpdate(prevProps) {\n    // useEffect on dag\n    if (prevProps.dag !== this.props.dag) {\n      this.drawGraph();\n      this.zoomHome();\n    }\n\n    // useEffect on selectedRef\n    if (prevProps.selectedTask !== this.props.selectedTask) {\n      this.highlightSelectedNode();\n    }\n  }\n\n  componentDidMount() {\n    this.svg = d3.select(this.svgRef.current);\n\n    // Set up zoom support\n    this.zoom = d3\n      .zoom()\n      .filter((event) => {\n        if (event.type === \"wheel\") {\n          return event.ctrlKey;\n        } else if (event.type === \"dblclick\") {\n          return false; // ignore dblclick\n        } else {\n          return !event.ctrlKey && !event.button;\n        }\n      })\n      .on(\"zoom\", (event) => {\n        this.inner.attr(\"transform\", event.transform);\n      });\n\n    this.zoom(this.svg);\n\n    this.drawGraph();\n    this.highlightSelectedNode();\n    this.zoomHome();\n  }\n\n  highlightSelectedNode = () => {\n    const dagGraph = this.props.dag.graph;\n    const taskResult = this.props.dag.resolveTaskResult(\n      this.props.selectedTask\n    );\n\n    const selectedRef =\n      taskResult &&\n      (taskResult.referenceTaskName ||\n        taskResult.workflowTask.taskReferenceName);\n\n    let resolvedRef;\n    if (!selectedRef) {\n      resolvedRef = null;\n    } else if (this.graph.hasNode(selectedRef)) {\n      resolvedRef = selectedRef;\n    } else if (dagGraph.hasNode(selectedRef)) {\n      // if ref cannot be found in this.graph, it may be rendered as a stacked placeholder.\n\n      const parentRef = _.first(dagGraph.predecessors(selectedRef));\n      const parentType = dagGraph.node(parentRef).type;\n      console.assert(\n        parentType === \"FORK_JOIN_DYNAMIC\" || parentType === \"DO_WHILE\"\n      );\n\n      resolvedRef = this.graph\n        .successors(parentRef)\n        .find((ref) => ref.includes(\"DF_TASK_PLACEHOLDER\"));\n    } else {\n      throw new Error(\"Assertion failed. ref not found\");\n    }\n\n    const { inner } = this;\n    inner.selectAll(\"g.node\").classed(\"selected\", false);\n\n    if (resolvedRef) {\n      inner.select(`g[id='${resolvedRef}']`).classed(\"selected\", true);\n    }\n  };\n\n  zoomInOut = (dir) => {\n    const { svg, inner } = this;\n    const currTransform = d3.zoomTransform(inner.node());\n    const newZoom =\n      dir === \"in\" ? currTransform.k * 1.25 : currTransform.k / 1.25;\n    this.zoom.transform(svg, d3.zoomIdentity.scale(newZoom));\n    const postZoomedHeight = inner.node().getBoundingClientRect().height;\n    svg.attr(\n      \"height\",\n      Math.max(postZoomedHeight + BOTTOM_MARGIN, GRAPH_MIN_HEIGHT)\n    );\n  };\n\n  zoomHome = () => {\n    const { svg, inner } = this;\n    const containerWidth = svg.node().getBoundingClientRect().width;\n    const graphWidth = this.graph.graph().width;\n\n    this.zoom.transform(\n      svg,\n      d3.zoomIdentity.translate(containerWidth / 2 - graphWidth / 2, 0)\n    );\n\n    const postZoomedHeight = inner.node().getBoundingClientRect().height;\n    svg.attr(\n      \"height\",\n      Math.max(postZoomedHeight + BOTTOM_MARGIN, GRAPH_MIN_HEIGHT)\n    );\n  };\n\n  zoomToFit = () => {\n    const { svg, inner } = this;\n    const containerWidth = svg.node().getBoundingClientRect().width;\n    const scale = Math.min(containerWidth / this.graph.graph().width, 1);\n    this.zoom.transform(svg, d3.zoomIdentity.scale(scale));\n\n    // Adjust svg height to fit post-zoomed\n    const postZoomedHeight = inner.nodes()[0].getBoundingClientRect().height;\n    svg.attr(\n      \"height\",\n      Math.max(postZoomedHeight + BOTTOM_MARGIN, GRAPH_MIN_HEIGHT)\n    );\n  };\n\n  collapseDfChildren = (parentRef, childrenRef) => {\n    const graph = this.graph;\n    const dagGraph = this.props.dag.graph;\n\n    const tally = childrenRef\n      .map((childRef) => dagGraph.node(childRef).status)\n      .reduce(\n        (prev, curr) => {\n          const retval = { total: prev.total + 1 };\n          if (curr === \"COMPLETED\") {\n            retval.success = prev.success + 1;\n          } else if (curr === \"IN_PROGRESS\" || curr === \"SCHEDULED\") {\n            retval.inProgress = prev.inProgress + 1;\n          } else if (curr === \"CANCELED\") {\n            retval.canceled = prev.canceled + 1;\n          }\n          return {\n            ...prev,\n            ...retval,\n          };\n        },\n        {\n          success: 0,\n          inProgress: 0,\n          canceled: 0,\n          total: 0,\n        }\n      );\n\n    const placeholderRef = parentRef + \"_DF_TASK_PLACEHOLDER\";\n\n    let status;\n    if (tally.success === tally.total) {\n      status = \"COMPLETED\";\n    } else if (tally.inProgress) {\n      status = \"IN_PROGRESS\";\n    } else {\n      status = \"FAILED\";\n    }\n\n    const placeholderNode = {\n      name: placeholderRef,\n      ref: placeholderRef,\n      type: \"DF_TASK_PLACEHOLDER\",\n      status: status, // Only used for coloring\n      firstDfRef: _.first(childrenRef),\n      tally: tally,\n    };\n    graph.setNode(placeholderRef, placeholderNode);\n\n    const tailSet = new Set();\n    for (const childRef of childrenRef) {\n      graph\n        .successors(childRef)\n        .forEach((successorRef) => tailSet.add(successorRef));\n      graph.removeNode(childRef); // This automatically removes any incident edges\n    }\n\n    // Add edges for placeholder\n    graph.setEdge(parentRef, placeholderRef, { executed: true });\n\n    // Should have only 1 unique successor (being a JOIN)\n    console.assert(tailSet.size === 1);\n\n    const successorRef = tailSet.values().next().value;\n    const successor = dagGraph.node(successorRef);\n    graph.setEdge(\n      placeholderRef,\n      successorRef,\n      successor.status ? { executed: true } : undefined\n    );\n  };\n\n  drawGraph = () => {\n    if (this.inner) this.inner.remove();\n    this.inner = this.svg.append(\"g\");\n    const { svg, inner } = this;\n\n    const graph = new graphlib.Graph({ compound: true }).setGraph({\n      nodesep: 15,\n      ranksep: 30,\n    });\n    this.graph = graph;\n    this.barNodes = [];\n\n    const dagGraph = this.props.dag.graph;\n\n    // Clone graph\n    for (const nodeId of dagGraph.nodes()) {\n      graph.setNode(nodeId);\n    }\n    for (const { v, w } of dagGraph.edges()) {\n      graph.setEdge(v, w);\n    }\n\n    // Collapse Dynamic Fork children\n    const dfNodes = dagGraph\n      .nodes()\n      .filter((nodeId) => dagGraph.node(nodeId).type === \"FORK_JOIN_DYNAMIC\");\n\n    for (const parentRef of dfNodes) {\n      const childRefs = dagGraph.successors(parentRef);\n\n      if (childRefs.length > 2) {\n        this.collapseDfChildren(parentRef, childRefs);\n      }\n    }\n\n    // Collapse Do_while children\n    const doWhileNodes = dagGraph\n      .nodes()\n      .filter((nodeId) => dagGraph.node(nodeId).type === \"DO_WHILE\");\n\n    for (const parentRef of doWhileNodes) {\n      const parentNode = dagGraph.node(parentRef);\n\n      // Only collapse executed DO_WHILE loops\n      if (_.get(parentNode, \"status\")) {\n        const childRefs = dagGraph\n          .successors(parentRef)\n          .map((ref) => dagGraph.node(ref))\n          .filter((node) => node.type !== \"DO_WHILE_END\")\n          .map((node) => node.ref);\n\n        if (childRefs.length > 0) {\n          this.collapseDfChildren(parentRef, childRefs);\n        }\n      }\n    }\n\n    // Render Nodes\n    for (const nodeId of graph.nodes()) {\n      graph.setNode(nodeId, this.renderVertex(nodeId)); // Update nodes with render info\n    }\n\n    // Render Edges\n    for (const edgeId of graph.edges()) {\n      const dagEdge = dagGraph.edge(edgeId) || graph.edge(edgeId);\n\n      const caseValue = _.get(dagEdge, \"caseValue\");\n      const type = _.get(dagEdge, \"type\");\n\n      let classes = [],\n        label,\n        labelStyle;\n\n      if (type === \"loop\") {\n        label = \"LOOP\";\n        classes.push(\"reverse\");\n      } else {\n        label = caseValue || (caseValue === null ? \"default\" : \"\");\n      }\n\n      if (this.props.executionMode) {\n        const executed = _.get(dagEdge, \"executed\");\n        if (executed) {\n          classes.push(\"executed\");\n          labelStyle = \"\";\n        } else {\n          classes.push(\"dimmed\");\n          labelStyle = \"fill: #ccc\";\n        }\n      }\n\n      graph.setEdge(edgeId.v, edgeId.w, {\n        label: label,\n        labelStyle: labelStyle,\n        class: classes.join(\" \"),\n      });\n    }\n\n    this.renderer(inner, graph);\n\n    // Expand barNodes and rerender\n    for (const barRef of this.barNodes) {\n      this.expandBar(barRef);\n    }\n\n    // svg.width=100% via CSS\n    svg.attr(\"height\", graph.graph().height + BOTTOM_MARGIN);\n\n    // Fix dagre-d3 bug with marker-end. Use css to set marker-end\n    // See: https://github.com/dagrejs/dagre-d3/pull/413\n    d3.selectAll(\"path.path\").attr(\"marker-end\", \"\");\n\n    // Attach click handler\n    inner.selectAll(\"g.node\").on(\"click\", this.handleClick);\n  };\n\n  /**\n   * Get the taskRef id base on browsers\n   * @param e\n   * @returns {string | undefined} The id of the task ref\n   */\n  getTaskRef = (e) => {\n    const flag = navigator.userAgent.toLowerCase().indexOf(\"firefox\") > -1 || navigator.userAgent.toLowerCase().indexOf(\"chrome\") > -1;\n    if (flag) {\n      return e.target?.parentNode?.id;\n    }\n    return e?.path[1]?.id || e?.path[2]?.id; // could be 2 layers down\n  };\n\n  handleClick = (e) => {\n    const taskRef = e.composedPath()[1].id || e.composedPath()[2].id; // could be 2 layers down\n    const node = this.graph.node(taskRef);\n    if (node.type === \"DF_TASK_PLACEHOLDER\") {\n      if (this.props.onClick) this.props.onClick({ ref: node.firstDfRef });\n    } else if (\n      node.type === \"DF_EMPTY_PLACEHOLDER\" ||\n      node.type === \"TERMINAL\"\n    ) {\n      return null; // No-op for click on unexecuted DF card-pile or terminal nodes\n    } else {\n      // Non-DF, or unexecuted DF vertex\n      if (this.props.onClick) this.props.onClick({ ref: taskRef });\n    }\n  };\n\n  render() {\n    const { style, className } = this.props;\n    return (\n      <div style={style} className={`graphWrapper ${className || \"\"}`}>\n        <Toolbar>\n          <IconButton onClick={() => this.zoomInOut(\"in\")}>\n            <ZoomInIcon />\n          </IconButton>\n          <IconButton onClick={() => this.zoomInOut(\"out\")}>\n            <ZoomOutIcon />\n          </IconButton>\n          <IconButton onClick={this.zoomHome}>\n            <HomeIcon />\n          </IconButton>\n          <IconButton onClick={this.zoomToFit}>\n            <ZoomOutMapIcon />\n          </IconButton>\n          <span>Shortcut: Ctrl + scroll to zoom</span>\n        </Toolbar>\n        <svg ref={this.svgRef} className=\"graphSvg\">\n          <defs>\n            <filter id=\"brightness\">\n              <feComponentTransfer>\n                <feFuncR type=\"linear\" slope=\"0.9\"></feFuncR>\n                <feFuncG type=\"linear\" slope=\"0.9\"></feFuncG>\n                <feFuncB type=\"linear\" slope=\"0.9\"></feFuncB>\n              </feComponentTransfer>\n            </filter>\n\n            <filter\n              id=\"dropShadow\"\n              height=\"300%\"\n              width=\"300%\"\n              x=\"-75%\"\n              y=\"-75%\"\n            >\n              <feMorphology\n                operator=\"dilate\"\n                radius=\"4\"\n                in=\"SourceAlpha\"\n                result=\"thicken\"\n              />\n              <feGaussianBlur in=\"thicken\" stdDeviation=\"7\" result=\"blurred\" />\n              <feFlood floodColor=\"rgb(0,122,255)\" result=\"glowColor\" />\n              <feComposite\n                in=\"glowColor\"\n                in2=\"blurred\"\n                operator=\"in\"\n                result=\"softGlow_colored\"\n              />\n\n              <feMerge>\n                <feMergeNode in=\"softGlow_colored\" />\n                <feMergeNode in=\"SourceGraphic\" />\n              </feMerge>\n            </filter>\n\n            <marker\n              id=\"endarrow\"\n              markerWidth=\"8\"\n              markerHeight=\"6\"\n              refX=\"8\"\n              refY=\"3\"\n              orient=\"auto\"\n              markerUnits=\"strokeWidth\"\n            >\n              <polygon points=\"0 0, 8 3, 0 6\" />\n            </marker>\n\n            <marker\n              id=\"startarrow\"\n              markerWidth=\"8\"\n              markerHeight=\"6\"\n              refX=\"0\"\n              refY=\"3\"\n              orient=\"auto\"\n              markerUnits=\"strokeWidth\"\n            >\n              <polygon points=\"8 0, 8 6, 0 3\" />\n            </marker>\n\n            <marker\n              id=\"endarrow-dimmed\"\n              markerWidth=\"8\"\n              markerHeight=\"6\"\n              refX=\"8\"\n              refY=\"3\"\n              orient=\"auto\"\n              markerUnits=\"strokeWidth\"\n              stroke=\"#c8c8c8\"\n              fill=\"#c8c8c8\"\n            >\n              <polygon points=\"0 0, 8 3, 0 6\" />\n            </marker>\n\n            <marker\n              id=\"startarrow-dimmed\"\n              markerWidth=\"8\"\n              markerHeight=\"6\"\n              refX=\"0\"\n              refY=\"3\"\n              orient=\"auto\"\n              markerUnits=\"strokeWidth\"\n              stroke=\"#c8c8c8\"\n              fill=\"#c8c8c8\"\n            >\n              <polygon points=\"8 0, 8 6, 0 3\" />\n            </marker>\n          </defs>\n        </svg>\n      </div>\n    );\n  }\n\n  renderVertex = (nodeId) => {\n    const dagGraph = this.props.dag.graph;\n    const graph = this.graph;\n\n    const v = dagGraph.node(nodeId) || graph.node(nodeId); // synthetic nodes (e.g. DF placeholder) not found in 'dag' but preloaded into local graph.\n\n    let retval = {\n      id: v.ref,\n      class: `type-${v.type}`,\n      type: v.type,\n    };\n\n    switch (v.type) {\n      case \"SUB_WORKFLOW\":\n        retval.label = `${v.ref}\\n(${v.name})`;\n        break;\n      case \"TERMINAL\":\n        retval.label = v.name;\n        retval.shape = \"circle\";\n        break;\n      case \"TERMINATE\":\n        retval.label = `${v.ref}\\n(terminate)`;\n        retval.shape = \"circle\";\n        break;\n      case \"FORK_JOIN\":\n      case \"FORK_JOIN_DYNAMIC\":\n        retval = composeBarNode(v, \"down\");\n        this.barNodes.push(v.ref);\n        break;\n      case \"JOIN\":\n      case \"EXCLUSIVE_JOIN\":\n        retval = composeBarNode(v, \"up\");\n        this.barNodes.push(v.ref);\n        break;\n      case \"DECISION\":\n      case \"SWITCH\":\n        retval.label = v.ref;\n        retval.shape = \"diamond\";\n        retval.height = 40;\n        break;\n      case \"DF_EMPTY_PLACEHOLDER\":\n        retval.label = v.status\n          ? \"No tasks spawned\"\n          : \"Dynamically spawned tasks\";\n        retval.shape = \"stack\";\n        break;\n      case \"DF_TASK_PLACEHOLDER\":\n        retval.label = `${v.tally.success} of ${v.tally.total} tasks succeeded`;\n        if (v.tally.inProgress) {\n          retval.label += `\\n${v.tally.inProgress} pending`;\n        }\n        if (v.tally.canceled) {\n          retval.label += `\\n${v.tally.canceled} canceled`;\n        }\n        retval.firstDfRef = v.firstDfRef;\n        retval.shape = \"stack\";\n        break;\n      case \"DO_WHILE\":\n      case \"DO_WHILE_END\":\n        retval = composeBarNode(v, \"down\");\n        retval.label = `${retval.label} [DO_WHILE]`;\n        this.barNodes.push(v.ref);\n        break;\n      default:\n        retval.label = `${v.ref}\\n(${v.name})`;\n        retval.shape = \"rect\";\n    }\n\n    if (_.size(v.taskResults) > 1) {\n      retval.label += `\\n${v.taskResults.length} Attempts`;\n    }\n\n    if (this.props.executionMode) {\n      if (v.status) {\n        if (v.type !== \"TERMINAL\") {\n          retval.class += ` status_${v.status}`;\n        }\n      } else {\n        retval.class += \" dimmed\";\n      }\n    }\n\n    return retval;\n  };\n\n  expandBar(barRef) {\n    const barNode = this.graph.node(barRef);\n    let fanOut;\n    if (barNode.fanDir === \"down\") {\n      fanOut = this.graph.outEdges(barRef).map((e) => {\n        const points = parseSvgPath(\n          this.graph.edge(e).elem.querySelector(\"path\").getAttribute(\"d\")\n        );\n        return _.first(points);\n      });\n    } else if (barNode.fanDir === \"bidir\") {\n      fanOut = this.graph.inEdges(barRef).map((e) => {\n        const points = parseSvgPath(\n          this.graph.edge(e).elem.querySelector(\"path\").getAttribute(\"d\")\n        );\n        return _.last(points);\n      });\n    } else {\n      fanOut = this.graph.inEdges(barRef).map((e) => {\n        const points = parseSvgPath(\n          this.graph.edge(e).elem.querySelector(\"path\").getAttribute(\"d\")\n        );\n        return _.last(points);\n      });\n    }\n\n    const barWidth = barNode.elem.getBBox().width;\n    let translateX = getTranslateX(barNode.elem),\n      translateY = getTranslateY(barNode.elem);\n    let minX = barNode.x - barWidth / 2;\n    let maxX = barNode.x + barWidth / 2;\n\n    for (const point of fanOut) {\n      const left = point[1] - BAR_MARGIN;\n      const right = point[1] + BAR_MARGIN;\n      if (right > maxX) maxX = right;\n      if (left < minX) minX = left;\n    }\n\n    if (minX < 0) {\n      maxX = maxX - minX + BAR_MARGIN;\n      minX = -BAR_MARGIN;\n    }\n\n    translateX = minX;\n    barNode.elem.setAttribute(\n      \"transform\",\n      `translate(${translateX}, ${translateY})`\n    );\n\n    const rect = barNode.elem.querySelector(\"rect\");\n    const currTransformY = rect.transform.baseVal[0].matrix.f;\n    const newWidth = maxX - minX;\n    const newTransformX = 0;\n    rect.removeAttribute(\"transform\");\n    rect.setAttribute(\"y\", currTransformY);\n    rect.setAttribute(\"width\", newWidth);\n\n    const text = barNode.elem.querySelector(\"g.label > g\");\n    const textWidth = text.getBBox().width;\n    const newTextTransformX = newTransformX + (newWidth - textWidth) / 2;\n    const currTextTransformY = text.transform.baseVal[0].matrix.f;\n    text.setAttribute(\n      \"transform\",\n      `translate(${newTextTransformX}, ${currTextTransformY})`\n    );\n  }\n}\n\nexport default withResizeDetector(WorkflowGraph);\nWorkflowGraph.propTypes = {\n  dag: PropTypes.object,\n  onClick: PropTypes.func,\n  selectedTask: PropTypes.object,\n  width: PropTypes.number,\n  height: PropTypes.number,\n};\n\nfunction composeBarNode(v, fanDir) {\n  const retval = {\n    id: v.ref,\n    type: v.type,\n    fanDir: fanDir,\n    class: `bar type-${v.type}`,\n    shape: \"bar\",\n    labelStyle: \"font-size:11px\",\n    padding: 4,\n    label: `${v.name} (${v.aliasForRef || v.ref})`,\n  };\n  return retval;\n}\n\nfunction barRenderer(parent, bbox, node) {\n  const group = parent.insert(\"g\", \":first-child\");\n  group\n    .insert(\"rect\")\n    .attr(\"width\", bbox.width)\n    .attr(\"height\", bbox.height)\n    .attr(\"transform\", `translate(${-bbox.width / 2}, ${-bbox.height / 2})`);\n\n  /*\n  if(node.type === 'EXCLUSIVE_JOIN') {\n    group.insert(\"rect\")\n    .attr(\"class\", \"underline\")\n    .attr(\"width\", bbox.width)\n    .attr(\"height\", 3)\n    .attr(\"transform\", `translate(${-bbox.width/2}, ${bbox.height - 7})`);\n  }*/\n\n  node.intersect = function (point) {\n    // Only spread out arrows in fan direction\n    return {\n      x:\n        (node.fanDir === \"down\" && point.y > node.y) ||\n          (node.fanDir === \"up\" && point.y < node.y)\n          ? point.x\n          : intersect.rect(node, point).x,\n      y: point.y < node.y ? node.y - bbox.height / 2 : node.y + bbox.height / 2,\n    };\n  };\n\n  return group;\n}\n\nconst STACK_OFFSET = 5;\nfunction stackRenderer(parent, bbox, node) {\n  const group = parent.insert(\"g\", \":first-child\");\n\n  group\n    .insert(\"rect\")\n    .attr(\"width\", bbox.width)\n    .attr(\"height\", bbox.height)\n    .attr(\n      \"transform\",\n      `translate(${-bbox.width / 2 - STACK_OFFSET * 2}, ${-bbox.height / 2 - STACK_OFFSET * 2\n      })`\n    );\n  group\n    .insert(\"rect\")\n    .attr(\"width\", bbox.width)\n    .attr(\"height\", bbox.height)\n    .attr(\n      \"transform\",\n      `translate(${-bbox.width / 2 - STACK_OFFSET}, ${-bbox.height / 2 - STACK_OFFSET\n      })`\n    );\n  group\n    .insert(\"rect\")\n    .attr(\"width\", bbox.width)\n    .attr(\"height\", bbox.height)\n    .attr(\"transform\", `translate(${-bbox.width / 2}, ${-bbox.height / 2})`);\n\n  node.intersect = function (point) {\n    const retval = intersect.rect(node, point);\n    if (retval.y < node.y) retval.y -= STACK_OFFSET;\n    if (retval.y >= node.y) retval.y -= STACK_OFFSET * 2;\n\n    return retval;\n  };\n  return group;\n}\n\nfunction getTranslateX(elem) {\n  return elem.transform.baseVal[0].matrix.e;\n}\nfunction getTranslateY(elem) {\n  return elem.transform.baseVal[0].matrix.f;\n}"
  },
  {
    "path": "ui/src/components/diagram/WorkflowGraph.test.cy.js",
    "content": "import { mount } from \"cypress/react\";\nimport WorkflowDAG from \"./WorkflowDAG\";\nimport WorkflowGraph from \"./WorkflowGraph\";\n\ndescribe(\"<WorkflowGraph>\", () => {\n  it(\"Dynamic Fork - success\", () => {\n    const onClickSpy = cy.spy().as(\"onClickSpy\");\n    cy.fixture(\"dynamicFork/success\").then((data) => {\n      const dag = new WorkflowDAG(data);\n      mount(\n        <WorkflowGraph dag={dag} executionMode={true} onClick={onClickSpy} />\n      );\n      cy.get(\"#dynamic_tasks_DF_TASK_PLACEHOLDER\")\n        .should(\"contain\", \"3 of 3 tasks succeeded\")\n        .click();\n\n      cy.get(\"@onClickSpy\").should(\"be.calledWith\", { ref: \"first_task\" });\n    });\n  });\n\n  it(\"Dynamic Fork - one task failed\", () => {\n    const onClickSpy = cy.spy().as(\"onClickSpy\");\n\n    cy.fixture(\"dynamicFork/oneFailed\").then((data) => {\n      const dag = new WorkflowDAG(data);\n      mount(\n        <WorkflowGraph dag={dag} executionMode={true} onClick={onClickSpy} />\n      );\n      cy.get(\"#dynamic_tasks_DF_TASK_PLACEHOLDER\")\n        .should(\"contain\", \"2 of 3 tasks succeeded\")\n        .should(\"have.class\", \"status_FAILED\")\n        .click();\n\n      cy.get(\"@onClickSpy\").should(\"be.calledWith\", { ref: \"first_task\" });\n    });\n  });\n\n  it(\"Dynamic Fork - externalized input\", () => {\n    const onClickSpy = cy.spy().as(\"onClickSpy\");\n\n    cy.fixture(\"dynamicFork/externalizedInput\").then((data) => {\n      const dag = new WorkflowDAG(data);\n      mount(\n        <WorkflowGraph dag={dag} executionMode={true} onClick={onClickSpy} />\n      );\n      cy.get(\"#dynamic_tasks_DF_TASK_PLACEHOLDER\")\n        .should(\"contain\", \"3 of 3 tasks succeeded\")\n        .click();\n\n      cy.get(\"@onClickSpy\").should(\"be.calledWith\", { ref: \"first_task\" });\n    });\n  });\n\n  it(\"Dynamic Fork - not executed\", () => {\n    cy.fixture(\"dynamicFork/notExecuted\").then((data) => {\n      const dag = new WorkflowDAG(data);\n      mount(<WorkflowGraph dag={dag} executionMode={true} />);\n      cy.get(\"#dynamic_tasks_DF_EMPTY_PLACEHOLDER\")\n        .should(\"have.class\", \"dimmed\")\n        .should(\"contain\", \"Dynamically spawned tasks\");\n    });\n  });\n\n  it(\"Dynamic Fork - none spawned\", () => {\n    const onClickSpy = cy.spy().as(\"onClickSpy\");\n\n    cy.fixture(\"dynamicFork/noneSpawned\").then((data) => {\n      const dag = new WorkflowDAG(data);\n      mount(\n        <WorkflowGraph dag={dag} executionMode={true} onClick={onClickSpy} />\n      );\n      cy.get(\"#dynamic_tasks_DF_EMPTY_PLACEHOLDER\")\n        .should(\"contain\", \"No tasks spawned\")\n        .click();\n\n      cy.get(\"@onClickSpy\").should(\"not.be.called\");\n    });\n  });\n\n  it(\"Do_while containing switch (definition)\", () => {\n    const onClickSpy = cy.spy().as(\"onClickSpy\");\n\n    cy.fixture(\"doWhile/doWhileSwitch\").then((data) => {\n      const dag = new WorkflowDAG(null, data.workflowDefinition);\n      mount(\n        <WorkflowGraph dag={dag} executionMode={false} onClick={onClickSpy} />\n      );\n\n      cy.get(\".edgePaths .edgePath.reverse\").should(\"exist\");\n      cy.get(\".edgePaths\").find(\".edgePath\").should(\"have.length\", 11);\n      cy.get(\".edgeLabels\").should(\"contain\", \"LOOP\");\n    });\n  });\n\n  // Note: The addition of task 'inline_task_outside' tests prefix-based loop content detection.\n  // Will succeed only when filtering via 'prefix + \"__\"';\n  it(\"Do_while containing switch (execution)\", () => {\n    const onClickSpy = cy.spy().as(\"onClickSpy\");\n\n    cy.fixture(\"doWhile/doWhileSwitch\").then((data) => {\n      const dag = new WorkflowDAG(data);\n      mount(\n        <WorkflowGraph dag={dag} executionMode={true} onClick={onClickSpy} />\n      );\n\n      cy.get(\"#LoopTask_DF_TASK_PLACEHOLDER\")\n        .should(\"contain\", \"2 of 2 tasks succeeded\")\n        .click();\n\n      cy.get(\"@onClickSpy\").should(\"be.calledWith\", { ref: \"inline_task__1\" });\n      cy.get(\".edgePaths\").find(\".edgePath\").should(\"have.length\", 6);\n      cy.get(\".edgePaths .edgePath.reverse\").should(\"exist\");\n      cy.get(\".edgeLabels\").should(\"contain\", \"LOOP\");\n    });\n  });\n});\n"
  },
  {
    "path": "ui/src/components/diagram/diagram.scss",
    "content": "$dark-color: #333;\n$light-color: #c8c8c8; /* gray11*/\n$white: #fff;\n$edge-label-color: blue;\n$outline-width: 0.6px;\n$node-text-size: 12px;\n\n.graphWrapper {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  user-select: none;\n}\n\n.graphToolbar {\n  flex: 0;\n}\n\n.graphSvg {\n  width: 100%;\n  min-height: 600px;\n  flex-grow: 1;\n}\n\n@mixin nodeColor($colorfg, $colorbg: #fff) {\n  &.bar {\n    rect {\n      stroke: $colorfg !important;\n      fill: $colorfg;\n    }\n  }\n  text {\n    fill: $colorfg;\n  }\n  rect,\n  polygon,\n  circle {\n    fill: $colorbg;\n    stroke: $colorfg;\n  }\n}\n\n.node {\n  &:hover {\n    rect,\n    polygon {\n      filter: url(\"#brightness\");\n    }\n  }\n\n  text {\n    fill: $dark-color;\n    font-size: 13px;\n    pointer-events: none;\n  }\n\n  rect,\n  circle,\n  polygon {\n    stroke: $dark-color;\n    fill: $white;\n    stroke-width: $outline-width;\n  }\n\n  rect {\n    rx: 5px;\n    ry: 5px;\n  }\n\n  &.type-SUB_WORKFLOW {\n    rect {\n      stroke-width: 5px;\n    }\n  }\n  &.type-TERMINAL {\n    circle {\n      stroke: $dark-color;\n      fill: #eee;\n      stroke-width: 0.6px;\n    }\n    text {\n      color: $dark-color;\n      font-weight: bold;\n    }\n    &.dimmed circle {\n      stroke: $light-color;\n    }\n  }\n\n  &.dimmed {\n    @include nodeColor($light-color);\n  }\n  &.status_COMPLETED {\n    @include nodeColor(#163e1d, #aee1b8);\n  }\n  &.status_COMPLETED_WITH_ERRORS {\n    @include nodeColor(#8b5b02, #feeac5);\n  }\n  &.status_IN_PROGRESS {\n    @include nodeColor(#c2920d, #fff5da);\n  }\n  &.status_SCHEDULED {\n    @include nodeColor(#11497a, #cbe2f7);\n  }\n  &.status_CANCELED {\n    @include nodeColor(#26194b, #ded5f8);\n  }\n  &.status_FAILED,\n  &.status_FAILED_WITH_TERMINAL_ERROR,\n  &.status_TIMED_OUT,\n  &.status_DF_PARTIAL {\n    @include nodeColor(#7f050b, #f9c6c9);\n  }\n  &.status_SKIPPED {\n    @include nodeColor(gray);\n  }\n  &.selected {\n    filter: url(\"#dropShadow\");\n  }\n}\n\n.node.bar {\n  &.type-FORK_JOIN_DYNAMIC {\n    rect {\n      stroke: $dark-color;\n      stroke-width: 5;\n      stroke-dasharray: 10;\n    }\n    &.dimmed {\n      rect {\n        stroke: $light-color;\n      }\n    }\n  }\n  /*\n  &.type-EXCLUSIVE_JOIN {\n    rect {\n      stroke: $dark-color;\n      fill: #fff;\n      stroke-width: $outline-width;\n    }\n    rect.underline {\n      stroke-width: 0;\n      fill: $dark-color;\n    }\n    text {\n      fill: $dark-color;\n    }\n    &.dimmed {\n      rect {\n        stroke: $light-color;\n        fill: #fff;        \n      }\n      text {\n        fill: $light-color;\n      }\n    }\n  }\n*/\n  rect {\n    rx: 0px;\n    ry: 0px;\n    stroke-width: 0;\n    fill: $dark-color;\n  }\n  text {\n    fill: $white;\n  }\n\n  &.dimmed {\n    rect {\n      fill: $light-color;\n    }\n  }\n}\n\n.edgePath {\n  path {\n    marker-end: url(#endarrow);\n    stroke: $dark-color;\n    stroke-width: 1px;\n  }\n\n  &.reverse {\n    path {\n      marker-end: none;\n      marker-start: url(#startarrow);\n    }\n  }\n\n  &.dimmed {\n    path {\n      stroke: $light-color;\n      stroke-dasharray: 5;\n      marker-end: url(#endarrow-dimmed);\n    }\n    marker {\n      fill: $light-color;\n    }\n  }\n\n  &.dimmed.reverse {\n    path {\n      marker-end: none;\n      marker-start: url(#startarrow-dimmed);\n    }\n  }\n\n  &.executed {\n    path {\n      stroke-width: 2px;\n    }\n  }\n}\n.edgeLabel {\n  fill: $edge-label-color;\n  font-size: 12px;\n  &.dimmed text {\n    fill: $light-color;\n  }\n}\n"
  },
  {
    "path": "ui/src/components/formik/FormikCronEditor.jsx",
    "content": "import React from \"react\";\nimport { InputLabel } from \"@material-ui/core\";\nimport { useField } from \"formik\";\nimport Cron from \"react-cron-generator\";\n\nimport \"./cron.css\";\n\nexport default function (props) {\n  const [field /*meta*/, , helper] = useField(props);\n\n  return (\n    <div className={props.className}>\n      <InputLabel variant=\"outlined\">Cron Expression</InputLabel>\n      <Cron\n        value={field.value}\n        onChange={(value) => helper.setValue(value)}\n        showResultText={true}\n        showResultCron={true}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/formik/FormikDropdown.jsx",
    "content": "import { useField } from \"formik\";\nimport { Dropdown } from \"..\";\n\nexport default function (props) {\n  const [field, meta, helper] = useField(props);\n  const touchedError = meta.touched && meta.error;\n\n  return (\n    <Dropdown\n      {...props}\n      {...field}\n      onChange={(e, value) => helper.setValue(value)}\n      error={touchedError}\n      helperText={touchedError}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/formik/FormikInput.jsx",
    "content": "import React from \"react\";\nimport { TextField } from \"@material-ui/core\";\nimport { useField } from \"formik\";\n\nexport default function (props) {\n  const [field, meta] = useField(props);\n\n  return (\n    <TextField\n      error={!!(meta.touched && meta.error)}\n      helperText={meta.touched && meta.error}\n      {...field}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/formik/FormikJsonInput.jsx",
    "content": "import { useRef, useEffect } from \"react\";\nimport { useField } from \"formik\";\nimport Editor from \"@monaco-editor/react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { FormHelperText, InputLabel } from \"@material-ui/core\";\nimport clsx from \"clsx\";\n\nconst useStyles = makeStyles({\n  wrapper: {\n    width: \"100%\",\n  },\n  monaco: {\n    padding: 10,\n    width: \"100%\",\n    borderColor: \"rgba(128, 128, 128, 0.2)\",\n    borderStyle: \"solid\",\n    borderWidth: 1,\n    borderRadius: 4,\n    backgroundColor: \"rgb(255, 255, 255)\",\n    \"&:focus-within\": {\n      margin: -2,\n      borderColor: \"rgb(73, 105, 228)\",\n      borderStyle: \"solid\",\n      borderWidth: 2,\n    },\n  },\n  label: {\n    display: \"block\",\n    marginBottom: 8,\n  },\n});\n\nexport default function ({\n  className,\n  label,\n  height,\n  reinitialize = false,\n  ...props\n}) {\n  const classes = useStyles();\n  const [field, meta, helper] = useField(props);\n  const editorRef = useRef(null);\n\n  function handleEditorMount(editor) {\n    editorRef.current = editor;\n    editor.onDidBlurEditorText(() => {\n      helper.setValue(editorRef.current.getValue());\n    });\n  }\n\n  useEffect(() => {\n    if (reinitialize && editorRef.current) {\n      editorRef.current.getModel().setValue(field.value);\n    }\n  }, [reinitialize, field.value]);\n\n  return (\n    <div className={clsx([classes.wrapper, className])}>\n      <InputLabel variant=\"outlined\" error={meta.touched && !!meta.error}>\n        {label}\n      </InputLabel>\n\n      <Editor\n        className={classes.monaco}\n        height={height || 90}\n        defaultLanguage=\"json\"\n        onMount={handleEditorMount}\n        defaultValue={field.value}\n        options={{\n          tabSize: 2,\n          minimap: { enabled: false },\n          lightbulb: { enabled: false },\n          quickSuggestions: false,\n\n          lineNumbers: \"off\",\n          glyphMargin: false,\n          folding: false,\n          // Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882\n          lineDecorationsWidth: 0,\n          lineNumbersMinChars: 0,\n          renderLineHighlight: \"none\",\n\n          overviewRulerLanes: 0,\n          hideCursorInOverviewRuler: true,\n          scrollbar: {\n            vertical: \"hidden\",\n          },\n          overviewRulerBorder: false,\n        }}\n      />\n\n      {meta.touched && meta.error ? (\n        <FormHelperText variant=\"outlined\" error>\n          {meta.error}\n        </FormHelperText>\n      ) : null}\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/formik/FormikSwitch.jsx",
    "content": "import React from \"react\";\nimport { Switch, InputLabel } from \"@material-ui/core\";\nimport { useField } from \"formik\";\n\nexport default function ({ label, ...props }) {\n  const [field /*meta*/, , helper] = useField(props);\n\n  return (\n    <div>\n      <InputLabel variant=\"outlined\">{label}</InputLabel>\n      <Switch\n        color=\"primary\"\n        checked={field.value}\n        onChange={(e) => helper.setValue(e.target.checked)}\n        {...props}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/components/formik/FormikVersionDropdown.jsx",
    "content": "import { useFormikContext } from \"formik\";\nimport { useWorkflowNamesAndVersions } from \"../../data/workflow\";\nimport FormikDropdown from \"./FormikDropdown\";\nimport { useEffect } from \"react\";\n\nexport default function FormikVersionDropdown(props) {\n  const { name } = props;\n  const { data: namesAndVersions } = useWorkflowNamesAndVersions();\n  const {\n    setFieldValue,\n    values: { workflowName, workflowVersion },\n  } = useFormikContext();\n\n  useEffect(() => {\n    if (workflowVersion && namesAndVersions.has(workflowName)) {\n      const found = namesAndVersions\n        .get(workflowName)\n        .find((row) => row.version.toString() === workflowVersion);\n      if (!found) {\n        console.log(\n          `Version ${workflowVersion} not found for new workflowName. Clearing dropdown.`\n        );\n        setFieldValue(name, null, false);\n      }\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [namesAndVersions, workflowName, workflowVersion]);\n\n  const versions =\n    workflowName && namesAndVersions.has(workflowName)\n      ? namesAndVersions.get(workflowName).map((row) => \"\" + row.version)\n      : [];\n\n  return <FormikDropdown options={versions} {...props} />;\n}\n"
  },
  {
    "path": "ui/src/components/formik/FormikWorkflowNameInput.jsx",
    "content": "import { useField } from \"formik\";\nimport { useWorkflowNames } from \"../..//data/workflow\";\nimport { Dropdown } from \"../\";\nimport { isEmpty } from \"lodash\";\n\nexport default function (props) {\n  const [field, meta, helper] = useField(props);\n  const touchedError = meta.touched && meta.error;\n\n  const workflowNames = useWorkflowNames();\n\n  return (\n    <Dropdown\n      disableClearable\n      label={props.label || \"Workflow Name\"}\n      options={workflowNames}\n      placeholder={\"Select Workflow Name\"}\n      loading={isEmpty(workflowNames)}\n      error={touchedError}\n      helperText={touchedError}\n      {...field}\n      {...props}\n      onChange={(e, value) => helper.setValue(value)}\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/components/formik/cron.css",
    "content": ".nav-tabs {\n  border-bottom: 1px solid #dee2e6;\n}\n.nav {\n  display: flex;\n  -ms-flex-wrap: wrap;\n  flex-wrap: wrap;\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n\n.nav-tabs .nav-item.show .nav-link,\n.nav-tabs .nav-link.active {\n  color: #495057;\n  background-color: #fff;\n  border-color: #dee2e6 #dee2e6 #fff;\n}\n.nav-tabs .nav-link {\n  border: 1px solid transparent;\n  border-top-left-radius: 0.25rem;\n  border-top-right-radius: 0.25rem;\n}\n.nav-link {\n  display: block;\n  padding: 0.5rem 1rem;\n}\n\n.cron_builder {\n  background: none !important;\n  border: none;\n  padding: 0px;\n  position: relative;\n}\n.cron_builder input {\n  line-height: 36px;\n}\n.cron_builder select {\n  height: 39px;\n}\n\n.cron-builder-bg {\n  position: absolute;\n  top: 40px;\n  left: 640px;\n  width: 200px;\n}\n\n.cron-builder-bg:last-child {\n  top: 100px;\n}\n\n.cron_builder .row {\n  display: flex;\n  flex-direction: row;\n}\n"
  },
  {
    "path": "ui/src/components/index.js",
    "content": "// Buttons\nexport { default as PrimaryButton } from \"./PrimaryButton\";\nexport { default as SecondaryButton } from \"./SecondaryButton\";\nexport { default as TertiaryButton } from \"./TertiaryButton\";\nexport { default as ButtonGroup } from \"./ButtonGroup\";\nexport { default as DropdownButton } from \"./DropdownButton\";\nexport { default as SplitButton } from \"./SplitButton\";\nexport { default as Button } from \"./Button\";\nexport { default as Banner } from \"./Banner\";\n\n// Layout\nexport { default as Paper } from \"./Paper\";\nexport { default as Tabs, Tab } from \"./Tabs\";\n\n// Text\nexport { default as NavLink } from \"./NavLink\";\nexport { default as Heading } from \"./Heading\";\nexport { default as Text } from \"./Text\";\nexport { default as Input } from \"./Input\";\nexport { default as Select } from \"./Select\";\nexport { default as Dropdown } from \"./Dropdown\";\n\n// Tables\nexport { default as DataTable } from \"./DataTable\";\nexport { default as KeyValueTable } from \"./KeyValueTable\";\nexport { default as ReactJson } from \"./ReactJson\";\n\n// Misc\nexport { default as LinearProgress } from \"./LinearProgress\";\nexport { default as Pill } from \"./Pill\";\nexport { default as StatusBadge } from \"./StatusBadge\";\n\n// Conductor Specific\nexport { default as WorkflowNameInput } from \"./WorkflowNameInput\";\nexport { default as TaskNameInput } from \"./TaskNameInput\";\nexport { default as TaskLink } from \"./TaskLink\";\n"
  },
  {
    "path": "ui/src/data/actions.js",
    "content": "import { useAction } from \"./common\";\nimport Path from \"../utils/path\";\nimport { useFetchContext, fetchWithContext } from \"../plugins/fetch\";\nimport { useMutation } from \"react-query\";\nimport _ from \"lodash\";\n\nexport const useRestartAction = ({ workflowId, onSuccess }) => {\n  return useAction(`/workflow/${workflowId}/restart`, \"post\", { onSuccess });\n};\n\nexport const useRestartLatestAction = ({ workflowId, onSuccess }) => {\n  return useAction(\n    `/workflow/${workflowId}/restart?useLatestDefinitions=true`,\n    \"post\",\n    { onSuccess }\n  );\n};\n\nexport const useRetryAction = ({ workflowId, onSuccess }) => {\n  return useAction(\n    `/workflow/${workflowId}/retry?resumeSubworkflowTasks=false`,\n    \"post\",\n    { onSuccess }\n  );\n};\n\nexport const useRetryResumeSubworkflowTasksAction = ({\n  workflowId,\n  onSuccess,\n}) => {\n  return useAction(\n    `/workflow/${workflowId}/retry?resumeSubworkflowTasks=true`,\n    \"post\",\n    { onSuccess }\n  );\n};\n\nexport const useTerminateAction = ({ workflowId, onSuccess }) => {\n  const fetchContext = useFetchContext();\n  return useMutation(\n    (mutateParams) => {\n      const reason = _.get(mutateParams, \"reason\");\n      const path = new Path(`/workflow/${workflowId}`);\n      if (reason) {\n        path.search.append(\"reason\", reason);\n      }\n\n      return fetchWithContext(path.toString(), fetchContext, {\n        method: \"delete\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n      });\n    },\n    { onSuccess }\n  );\n};\n\nexport const useResumeAction = ({ workflowId, onSuccess }) => {\n  return useAction(`/workflow/${workflowId}/resume`, \"put\", { onSuccess });\n};\n\nexport const usePauseAction = ({ workflowId, onSuccess }) => {\n  return useAction(`/workflow/${workflowId}/pause`, \"put\", { onSuccess });\n};\n"
  },
  {
    "path": "ui/src/data/bulkactions.js",
    "content": "import { useAction } from \"./common\";\nimport Path from \"../utils/path\";\nimport { fetchWithContext, useFetchContext } from \"../plugins/fetch\";\nimport { useMutation } from \"react-query\";\nimport _ from \"lodash\";\n\nexport const useBulkPauseAction = ({ onSuccess }) => {\n  return useAction(\"/workflow/bulk/pause\", \"put\", { onSuccess });\n};\n\nexport const useBulkResumeAction = ({ onSuccess }) => {\n  return useAction(\"/workflow/bulk/resume\", \"put\", { onSuccess });\n};\n\nexport const useBulkRestartAction = ({ onSuccess }) => {\n  return useAction(\"/workflow/bulk/restart\", \"post\", { onSuccess });\n};\n\nexport const useBulkRestartLatestAction = ({ onSuccess }) => {\n  return useAction(\"/workflow/bulk/restart?useLatestDefinitions=true\", \"post\", {\n    onSuccess,\n  });\n};\n\nexport const useBulkRetryAction = ({ onSuccess }) => {\n  return useAction(\"/workflow/bulk/retry\", \"post\", { onSuccess });\n};\n\nexport const useBulkTerminateAction = ({ onSuccess }) => {\n  return useAction(\"/workflow/bulk/terminate\", \"post\", { onSuccess });\n};\n\nexport const useBulkTerminateWithReasonAction = (callbacks) => {\n  const fetchContext = useFetchContext();\n\n  return useMutation((mutateParams) => {\n    const path = new Path(\"/workflow/bulk/terminate\");\n    if (mutateParams.reason) {\n      path.search.append(\"reason\", mutateParams.reason);\n    }\n\n    return fetchWithContext(path, fetchContext, {\n      method: \"post\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: _.get(mutateParams, \"body\"),\n    });\n  }, callbacks);\n};\n"
  },
  {
    "path": "ui/src/data/common.js",
    "content": "import _ from \"lodash\";\nimport { useQuery, useQueries, useMutation } from \"react-query\";\nimport { useFetchContext, fetchWithContext } from \"../plugins/fetch\";\n\nexport function useFetchParallel(paths, reactQueryOptions) {\n  const fetchContext = useFetchContext();\n\n  return useQueries(\n    paths.map((path) => {\n      return {\n        queryKey: [fetchContext.stack, ...path],\n        queryFn: () => fetchWithContext(`/${path.join(\"/\")}`, fetchContext),\n        enabled:\n          fetchContext.ready && _.get(reactQueryOptions, \"enabled\", true),\n        keepPreviousData: true,\n        ...reactQueryOptions,\n      };\n    })\n  );\n}\n\nexport function useFetch(key, path, reactQueryOptions, defaultResponse) {\n  const fetchContext = useFetchContext();\n\n  return useQuery(\n    [fetchContext.stack, ...key],\n    () => {\n      if (path) {\n        return fetchWithContext(path, fetchContext);\n      } else {\n        return Promise.resolve(defaultResponse);\n      }\n    },\n    {\n      enabled: fetchContext.ready && _.get(reactQueryOptions, \"enabled\", true),\n      keepPreviousData: true,\n      ...reactQueryOptions,\n    }\n  );\n}\n\nexport function useAction(path, method = \"post\", callbacks) {\n  const fetchContext = useFetchContext();\n  return useMutation(\n    (mutateParams) =>\n      fetchWithContext(path, fetchContext, {\n        method,\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: _.get(mutateParams, \"body\"),\n      }),\n    callbacks\n  );\n}\n"
  },
  {
    "path": "ui/src/data/misc.js",
    "content": "import { useFetch } from \"./common\";\n\nexport const useEventHandlers = () => {\n  return useFetch([\"event\"], \"/event\");\n};\n\nexport const useLogs = ({ taskId }) => {\n  return useFetch([\"taskLog\", taskId], `/tasks/${taskId}/log`);\n};\n"
  },
  {
    "path": "ui/src/data/task.js",
    "content": "import _ from \"lodash\";\nimport { useMemo } from \"react\";\nimport { useQuery, useQueries, useMutation } from \"react-query\";\nimport qs from \"qs\";\nimport { useFetchContext, fetchWithContext } from \"../plugins/fetch\";\nimport { useFetch } from \"./common\";\nimport Path from \"../utils/path\";\n\nconst STALE_TIME_SEARCH = 60000; // 1 min\n\nexport function useTask(taskName, defaultTask) {\n  let path;\n  if (taskName) {\n    path = `/metadata/taskdefs/${taskName}`;\n  }\n  return useFetch([\"taskDef\", taskName], path, {}, defaultTask);\n}\n\nexport function useTaskSearch({ searchReady, ...searchObj }) {\n  const fetchContext = useFetchContext();\n  const pathRoot = \"/tasks/search?\";\n  const { rowsPerPage, page, sort, freeText, query } = searchObj;\n\n  const isEmptySearch = _.isEmpty(query) && freeText === \"*\";\n\n  return useQuery(\n    [fetchContext.stack, pathRoot, searchObj],\n    () => {\n      if (isEmptySearch) {\n        return {\n          results: [],\n          totalHits: 0,\n        };\n      } else {\n        const path =\n          pathRoot +\n          qs.stringify({\n            start: (page - 1) * rowsPerPage,\n            size: rowsPerPage,\n            sort: sort,\n            freeText: freeText,\n            query: query,\n          });\n        return fetchWithContext(path, fetchContext);\n      }\n      // staletime to ensure stable view when paginating back and forth (even if underlying results change)\n    },\n    {\n      enabled: fetchContext.ready,\n      keepPreviousData: true,\n      staleTime: STALE_TIME_SEARCH,\n    }\n  );\n}\n\nexport function usePollData(taskName) {\n  const fetchContext = useFetchContext();\n  const pollDataPath = `/tasks/queue/polldata?taskType=${taskName}`;\n\n  return useQuery(\n    [fetchContext.stack, pollDataPath],\n    () => fetchWithContext(pollDataPath, fetchContext),\n    {\n      enabled: fetchContext.ready && !_.isEmpty(taskName),\n    }\n  );\n}\n\nexport function useQueueSize(taskName, domain) {\n  const fetchContext = useFetchContext();\n  const path = new Path(\"/tasks/queue/size\");\n  path.search.append(\"taskType\", taskName);\n\n  if (!_.isUndefined(domain)) {\n    path.search.append(\"domain\", domain);\n  }\n\n  return useQuery([fetchContext.stack, \"queueSize\", taskName, domain], () =>\n    fetchWithContext(path.toString(), fetchContext, {\n      enabled: fetchContext.ready,\n    })\n  );\n}\n\nexport function useQueueSizes(taskName, domains) {\n  const fetchContext = useFetchContext();\n\n  return useQueries(\n    domains\n      ? domains.map((domain) => {\n          const path = new Path(\"/tasks/queue/size\");\n          path.search.append(\"taskType\", taskName);\n\n          if (!_.isUndefined(domain)) {\n            path.search.append(\"domain\", domain);\n          }\n\n          return {\n            queryKey: [fetchContext.stack, \"queueSize\", taskName, domain],\n            queryFn: async () => {\n              const result = await fetchWithContext(\n                path.toString(),\n                fetchContext\n              );\n              return {\n                domain: domain,\n                size: result,\n              };\n            },\n            enabled: fetchContext.ready && !!domains,\n          };\n        })\n      : []\n  );\n}\n\nexport function useTaskNames() {\n  const { data } = useTaskDefs();\n  return useMemo(\n    () => (data ? Array.from(new Set(data.map((def) => def.name))).sort() : []),\n    [data]\n  );\n}\n\nexport function useTaskDefs() {\n  return useFetch([\"taskDefs\"], \"/metadata/taskdefs\");\n}\n\nexport function useSaveTask(callbacks) {\n  const path = \"/metadata/taskdefs\";\n  const fetchContext = useFetchContext();\n\n  return useMutation(({ body, isNew }) => {\n    return fetchWithContext(path, fetchContext, {\n      method: isNew ? \"post\" : \"put\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify(isNew ? [body] : body), // Note: application of [] is opposite of workflow\n    });\n  }, callbacks);\n}\n"
  },
  {
    "path": "ui/src/data/workflow.js",
    "content": "import { useMemo } from \"react\";\nimport { useQuery, useMutation, useQueryClient } from \"react-query\";\nimport { useFetchContext, fetchWithContext } from \"../plugins/fetch\";\nimport { useFetch, useFetchParallel } from \"./common\";\nimport { useEnv } from \"../plugins/env\";\nimport qs from \"qs\";\n\nconst STALE_TIME_WORKFLOW_DEFS = 600000; // 10 mins\nconst STALE_TIME_SEARCH = 60000; // 1 min\n\nexport function useWorkflowSearch(searchObj) {\n  const fetchContext = useFetchContext();\n  const pathRoot = \"/workflow/search?\";\n\n  return useQuery(\n    [fetchContext.stack, pathRoot, searchObj],\n    () => {\n      const { rowsPerPage, page, sort, freeText, query } = searchObj;\n      const path =\n        pathRoot +\n        qs.stringify({\n          start: (page - 1) * rowsPerPage,\n          size: rowsPerPage,\n          sort: sort,\n          freeText: freeText,\n          query: query,\n        });\n      return fetchWithContext(path, fetchContext);\n      // staletime to ensure stable view when paginating back and forth (even if underlying results change)\n    },\n    {\n      enabled: fetchContext.ready,\n      keepPreviousData: true,\n      staleTime: STALE_TIME_SEARCH,\n    }\n  );\n}\n\nexport function useWorkflow(workflowId) {\n  return useFetch([\"workflow\", workflowId], `/workflow/${workflowId}`, {\n    enabled: !!workflowId,\n  });\n}\n\nexport function useWorkflowsByIds(workflowIds, reactQueryOptions) {\n  return useFetchParallel(\n    workflowIds.map((workflowId) => [\"workflow\", workflowId]),\n    reactQueryOptions\n  );\n}\n\nexport function useInvalidateWorkflows() {\n  const { stack } = useEnv();\n  const client = useQueryClient();\n\n  return function (workflowIds) {\n    console.log(\"invalidating workflow Ids\", workflowIds);\n    client.invalidateQueries({\n      predicate: (query) =>\n        query.queryKey[0] === stack &&\n        query.queryKey[1] === \"workflow\" &&\n        workflowIds.includes(query.queryKey[2]),\n    });\n  };\n}\n\nexport function useWorkflowDef(\n  workflowName,\n  version,\n  defaultWorkflow,\n  reactQueryOptions = {}\n) {\n  let path;\n  const key = [\"workflowDef\", workflowName];\n  if (workflowName) {\n    path = `/metadata/workflow/${workflowName}`;\n    if (version) {\n      path += `?version=${version}`;\n      key.push(version);\n    }\n  }\n  return useFetch(key, path, reactQueryOptions, defaultWorkflow);\n}\n\nexport function useWorkflowDefs() {\n  return useFetch([\"workflowDefs\"], \"/metadata/workflow\", {\n    staleTime: STALE_TIME_WORKFLOW_DEFS,\n  });\n}\n\nexport function useLatestWorkflowDefs() {\n  const { data, ...rest } = useWorkflowDefs();\n\n  // Filter latest versions only\n  const workflows = useMemo(() => {\n    if (data) {\n      const unique = new Map();\n      for (let workflowDef of data) {\n        if (!unique.has(workflowDef.name)) {\n          unique.set(workflowDef.name, workflowDef);\n        } else if (unique.get(workflowDef.name).version < workflowDef.version) {\n          unique.set(workflowDef.name, workflowDef);\n        }\n      }\n\n      return Array.from(unique.values());\n    }\n  }, [data]);\n\n  return {\n    data: workflows,\n    ...rest,\n  };\n}\n\nexport function useSaveWorkflow(callbacks) {\n  const path = \"/metadata/workflow\";\n  const fetchContext = useFetchContext();\n\n  return useMutation(\n    ({ body, isNew }) =>\n      fetchWithContext(path, fetchContext, {\n        method: isNew ? \"post\" : \"put\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify(isNew ? body : [body]),\n      }),\n    callbacks\n  );\n}\n\nexport function useWorkflowNames() {\n  const { data } = useWorkflowDefs();\n  // Extract unique names\n  return useMemo(() => {\n    if (data) {\n      const nameSet = new Set(data.map((def) => def.name));\n      return Array.from(nameSet);\n    } else {\n      return [];\n    }\n  }, [data]);\n}\n\n// Version numbers do not necessarily start, or run contiguously from 1. Could be arbitrary integers e.g. 52335678.\n// By convention they should be monotonic (ever increasing) wrt time.\nexport function useWorkflowNamesAndVersions() {\n  const { data, ...rest } = useWorkflowDefs();\n\n  const newData = useMemo(() => {\n    const retval = new Map();\n    if (data) {\n      for (let def of data) {\n        let arr;\n        if (!retval.has(def.name)) {\n          arr = [];\n          retval.set(def.name, arr);\n        } else {\n          arr = retval.get(def.name);\n        }\n        arr.push({\n          version: def.version,\n          createTime: def.createTime,\n          updateTime: def.updateTime,\n        });\n      }\n\n      // Sort arrays in place\n      retval.forEach((val) => val.sort());\n    }\n    return retval;\n  }, [data]);\n\n  return { ...rest, data: newData };\n}\n\nexport function useStartWorkflow(callbacks) {\n  const path = \"/workflow\";\n  const fetchContext = useFetchContext();\n\n  return useMutation(\n    ({ body }) =>\n      fetchWithContext(\n        path,\n        fetchContext,\n        {\n          method: \"post\",\n          headers: {\n            \"Content-Type\": \"application/json\",\n          },\n          body: JSON.stringify(body),\n        },\n        false\n      ),\n    callbacks\n  );\n}\n"
  },
  {
    "path": "ui/src/hooks/useTime.js",
    "content": "import { useEffect, useState } from \"react\";\n\nexport const useTime = (enabled = true, refreshCycle = 1000) => {\n  const [now, setNow] = useState(new Date().getTime());\n\n  useEffect(() => {\n    const intervalId =\n      enabled && setInterval(() => setNow(new Date().getTime()), refreshCycle);\n\n    return () => clearInterval(intervalId);\n  }, [refreshCycle, setNow, enabled]);\n\n  return now;\n};\n"
  },
  {
    "path": "ui/src/index.css",
    "content": "body {\n  margin: 0;\n  min-height: 100vh;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n    \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n    sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  background-color: #efefef; /*gray13*/\n  overflow: hidden;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\n    monospace;\n}\n"
  },
  {
    "path": "ui/src/index.js",
    "content": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport \"./index.css\";\nimport App from \"./App\";\nimport * as serviceWorker from \"./serviceWorker\";\nimport { Provider as ThemeProvider } from \"./theme/provider\";\nimport { BrowserRouter } from \"react-router-dom\";\nimport CssBaseline from \"@material-ui/core/CssBaseline\";\nimport { QueryClient, QueryClientProvider } from \"react-query\";\nimport { ReactQueryDevtools } from \"react-query/devtools\";\nimport { getBasename } from \"./utils/helpers\";\n\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      refetchOnWindowFocus: false,\n      cacheTime: 600000, // 10 mins\n    },\n  },\n});\n\nReactDOM.render(\n  //<React.StrictMode>\n  <QueryClientProvider client={queryClient}>\n    <ThemeProvider>\n      <BrowserRouter basename={getBasename()}>\n        <CssBaseline />\n        <ReactQueryDevtools initialIsOpen={true} />\n\n        <App />\n      </BrowserRouter>\n    </ThemeProvider>\n  </QueryClientProvider>,\n  //</React.StrictMode>\n  document.getElementById(\"root\")\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"
  },
  {
    "path": "ui/src/pages/definition/EventHandler.jsx",
    "content": "import React, { useMemo } from \"react\";\nimport { useRouteMatch } from \"react-router-dom\";\nimport sharedStyles from \"../styles\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { Helmet } from \"react-helmet\";\nimport { ReactJson, LinearProgress, Heading, Paper } from \"../../components\";\nimport { useEventHandlers } from \"../../data/misc\";\n\nconst useStyles = makeStyles({\n  wrapper: {\n    display: \"flex\",\n    height: \"100%\",\n    alignItems: \"stretch\",\n    flexDirection: \"column\",\n  },\n  header: sharedStyles.header,\n  paper: {\n    flex: 1,\n    margin: 30,\n    paddingTop: 10,\n  },\n});\n\nexport default function EventHandlerDefinition() {\n  const classes = useStyles();\n  const match = useRouteMatch();\n\n  // TODO: Need API that returns individual event handler by name.\n  const { data, isFetching } = useEventHandlers();\n\n  const eventHandler = useMemo(\n    () => data && data.find((row) => row.name === match.params.name),\n    [data, match.params.name]\n  );\n\n  return (\n    <div className={classes.wrapper}>\n      <Helmet>\n        <title>\n          Conductor UI - Event Handler Definition - ${match.params.name}\n        </title>\n      </Helmet>\n      <div className={classes.header} style={{ paddingBottom: 20 }}>\n        <Heading level={1}>Event Handler Definition</Heading>\n        <Heading level={4}>{match.params.name}</Heading>\n      </div>\n      {isFetching && <LinearProgress />}\n      <Paper className={classes.paper}>\n        {eventHandler && <ReactJson src={eventHandler} />}\n      </Paper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definition/ResetConfirmationDialog.jsx",
    "content": "import React from \"react\";\nimport {\n  Dialog,\n  DialogActions,\n  DialogContent,\n  DialogTitle,\n} from \"@material-ui/core\";\nimport { Text, Button } from \"../../components\";\n\nexport default function ResetConfirmationDialog({\n  onClose,\n  onConfirm,\n  version,\n}) {\n  return (\n    <Dialog fullWidth maxWidth=\"sm\" open={version !== false} onClose={onClose}>\n      <DialogTitle>Confirmation</DialogTitle>\n      <DialogContent>\n        <Text>\n          You will lose all changes made in the editor. Are you sure to proceed?\n        </Text>\n      </DialogContent>\n      <DialogActions>\n        <Button onClick={() => onConfirm(version)}>Confirm</Button>\n        <Button variant=\"secondary\" onClick={onClose}>\n          Cancel\n        </Button>\n      </DialogActions>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definition/SaveTaskDialog.jsx",
    "content": "import { useRef, useState, useMemo, useEffect } from \"react\";\nimport { Dialog, Toolbar, Snackbar } from \"@material-ui/core\";\nimport Alert from \"@material-ui/lab/Alert\";\nimport { Text, Button, LinearProgress, Pill } from \"../../components\";\nimport { DiffEditor } from \"@monaco-editor/react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { useSaveTask, useTaskNames } from \"../../data/task\";\nimport _ from \"lodash\";\n\nconst useStyles = makeStyles({\n  rightButtons: {\n    display: \"flex\",\n    flexGrow: 1,\n    justifyContent: \"flex-end\",\n    gap: 8,\n  },\n  toolbar: {\n    paddingLeft: 20,\n  },\n});\n//const WORKFLOW_SAVED_SUCCESSFULLY = \"Workflow saved successfully.\";\nconst TASK_SAVE_FAILED = \"Failed to save the task definition.\";\n\nexport default function SaveTaskDialog({ onSuccess, onCancel, document }) {\n  const classes = useStyles();\n  const diffMonacoRef = useRef(null);\n  const [errorMsg, setErrorMsg] = useState();\n  const taskNames = useTaskNames();\n\n  const modified = useMemo(() => {\n    if (!taskNames || !document) return { text: \"\" };\n\n    const parsedModified = JSON.parse(document.modified);\n    const modifiedName = parsedModified.name;\n    const isNew = _.get(document, \"originalObj.name\") !== modifiedName;\n\n    return {\n      text: document.modified,\n      obj: parsedModified,\n      isNew: isNew,\n      isClash: isNew && taskNames.includes(modifiedName),\n    };\n  }, [document, taskNames]);\n\n  const { isLoading, mutate: saveTask } = useSaveTask({\n    onSuccess: (data) => {\n      console.log(\"onsuccess\", data);\n      onSuccess(modified.obj.name);\n    },\n    onError: (err) => {\n      console.log(\"onerror\", err);\n      const errObj = JSON.parse(err);\n      let errStr = errObj.validationErrors && errObj.validationErrors.length > 0\n        ? `${errObj.validationErrors[0].message}: ${errObj.validationErrors[0].path}`\n        : errObj.message;\n      setErrorMsg({\n        message: `${TASK_SAVE_FAILED} ${errStr}`,\n        dismissible: true,\n      });\n    },\n  });\n\n  useEffect(() => {\n    if (modified.isClash) {\n      setErrorMsg({\n        message: \"Cannot save task definition. Task name already in use.\",\n        dismissible: false,\n      });\n    } else {\n      setErrorMsg(undefined);\n    }\n  }, [modified]);\n\n  const handleSave = () => {\n    saveTask({ body: modified.obj, isNew: modified.isNew });\n  };\n\n  const diffEditorDidMount = (editor) => {\n    diffMonacoRef.current = editor;\n  };\n\n  return (\n    <Dialog fullScreen open={!!document} onClose={() => onCancel()}>\n      <Snackbar\n        open={!!errorMsg}\n        anchorOrigin={{ vertical: \"top\", horizontal: \"center\" }}\n        transitionDuration={{ exit: 0 }}\n      >\n        <Alert\n          severity=\"error\"\n          onClose={_.get(errorMsg, \"dismissible\") ? () => setErrorMsg() : null}\n        >\n          {_.get(errorMsg, \"message\")}\n        </Alert>\n      </Snackbar>\n\n      {isLoading && <LinearProgress />}\n\n      <Toolbar className={classes.toolbar}>\n        <Text>\n          Saving{\" \"}\n          <span style={{ fontWeight: \"bold\" }}>\n            {_.get(modified, \"obj.name\")}\n          </span>\n        </Text>\n\n        {modified.isNew && <Pill label=\"New\" color=\"yellow\" />}\n\n        <div className={classes.rightButtons}>\n          <Button onClick={handleSave} disabled={modified.isClash}>\n            Save\n          </Button>\n          <Button onClick={() => onCancel()} variant=\"secondary\">\n            Cancel\n          </Button>\n        </div>\n      </Toolbar>\n\n      {document && (\n        <DiffEditor\n          height={\"100%\"}\n          width={\"100%\"}\n          theme=\"vs-light\"\n          language=\"json\"\n          original={document.original}\n          modified={document.modified}\n          autoIndent={true}\n          onMount={diffEditorDidMount}\n          options={{\n            selectOnLineNumbers: true,\n            readOnly: true,\n            minimap: {\n              enabled: false,\n            },\n          }}\n        />\n      )}\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definition/SaveWorkflowDialog.jsx",
    "content": "import { useRef, useState, useMemo } from \"react\";\nimport {\n  Dialog,\n  Toolbar,\n  FormControlLabel,\n  Checkbox,\n  Snackbar,\n} from \"@material-ui/core\";\nimport Alert from \"@material-ui/lab/Alert\";\nimport { Text, Button, LinearProgress, Pill } from \"../../components\";\nimport { DiffEditor } from \"@monaco-editor/react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport {\n  useSaveWorkflow,\n  useWorkflowNamesAndVersions,\n} from \"../../data/workflow\";\nimport _ from \"lodash\";\nimport { useEffect } from \"react\";\n\nconst useStyles = makeStyles({\n  rightButtons: {\n    display: \"flex\",\n    flexGrow: 1,\n    justifyContent: \"flex-end\",\n    gap: 8,\n  },\n  toolbar: {\n    paddingLeft: 20,\n  },\n});\n//const WORKFLOW_SAVED_SUCCESSFULLY = \"Workflow saved successfully.\";\nconst WORKFLOW_SAVE_FAILED = \"Failed to save the workflow definition.\";\n\nexport default function SaveWorkflowDialog({ onSuccess, onCancel, document }) {\n  const classes = useStyles();\n  const diffMonacoRef = useRef(null);\n  const [errorMsg, setErrorMsg] = useState();\n  const [useAutoVersion, setUseAutoVersion] = useState(true);\n  const { data: namesAndVersions } = useWorkflowNamesAndVersions();\n\n  const modified = useMemo(() => {\n    if (!document || !namesAndVersions) return { text: \"\", obj: null };\n\n    const parsedModified = JSON.parse(document.modified);\n    const latestVersion = _.get(\n      _.last(namesAndVersions.get(parsedModified.name)),\n      \"version\",\n      0\n    );\n\n    if (useAutoVersion) {\n      parsedModified.version = _.isNumber(latestVersion)\n        ? latestVersion + 1\n        : 1;\n    }\n    const isNew = _.get(document, \"originalObj.name\") !== parsedModified.name;\n    const isClash = isNew && namesAndVersions.has(parsedModified.name);\n\n    return {\n      text: JSON.stringify(parsedModified, null, 2),\n      obj: parsedModified,\n      isClash: isClash,\n      isNew: isNew,\n    };\n  }, [document, useAutoVersion, namesAndVersions]);\n\n  useEffect(() => {\n    if (modified.isClash) {\n      setErrorMsg(\n        \"Cannot save workflow definition. Workflow name already in use.\"\n      );\n    } else {\n      setErrorMsg(undefined);\n    }\n  }, [modified]);\n\n  const { isLoading, mutate: saveWorkflow } = useSaveWorkflow({\n    onSuccess: (data) => {\n      console.log(\"onsuccess\", data);\n      onSuccess(modified.obj.name, modified.obj.version);\n    },\n    onError: (err) => {\n      console.log(\"onerror\", err);\n      const errObj = JSON.parse(err);\n      let errStr = errObj.validationErrors && errObj.validationErrors.length > 0\n        ? `${errObj.validationErrors[0].message}: ${errObj.validationErrors[0].path}`\n        : errObj.message;\n      setErrorMsg(`${WORKFLOW_SAVE_FAILED} ${errStr}`);\n    },\n  });\n\n  const handleSave = () => {\n    saveWorkflow({ body: modified.obj, isNew: modified.isNew });\n  };\n\n  const diffEditorDidMount = (editor) => {\n    diffMonacoRef.current = editor;\n  };\n\n  return (\n    <Dialog\n      fullScreen\n      open={!!document}\n      onClose={() => onCancel()}\n      TransitionProps={{\n        onEnter: () => setUseAutoVersion(true),\n      }}\n    >\n      <Snackbar\n        open={!!errorMsg}\n        onClose={() => setErrorMsg(null)}\n        anchorOrigin={{ vertical: \"top\", horizontal: \"center\" }}\n        transitionDuration={{ exit: 0 }}\n      >\n        <Alert onClose={() => setErrorMsg(null)} severity=\"error\">\n          {errorMsg}\n        </Alert>\n      </Snackbar>\n\n      {isLoading && <LinearProgress />}\n\n      <Toolbar className={classes.toolbar}>\n        <Text>\n          Saving{\" \"}\n          <span style={{ fontWeight: \"bold\" }}>\n            {_.get(modified, \"obj.name\")}\n          </span>\n        </Text>\n\n        {modified.isNew && <Pill label=\"New\" color=\"yellow\" />}\n\n        <div className={classes.rightButtons}>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={useAutoVersion}\n                onChange={(e) => setUseAutoVersion(e.target.checked)}\n                disabled={modified.isClash}\n              />\n            }\n            label=\"Automatically set version\"\n          />\n          <Button onClick={handleSave} disabled={modified.isClash}>\n            Save\n          </Button>\n          <Button onClick={() => onCancel()} variant=\"secondary\">\n            Cancel\n          </Button>\n        </div>\n      </Toolbar>\n\n      {document && (\n        <DiffEditor\n          height={\"100%\"}\n          width={\"100%\"}\n          theme=\"vs-light\"\n          language=\"json\"\n          original={document.original}\n          modified={modified.text}\n          autoIndent={true}\n          onMount={diffEditorDidMount}\n          options={{\n            selectOnLineNumbers: true,\n            readOnly: true,\n            minimap: {\n              enabled: false,\n            },\n          }}\n        />\n      )}\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definition/TaskDefinition.jsx",
    "content": "import React, { useMemo, useRef, useState } from \"react\";\nimport { Toolbar } from \"@material-ui/core\";\nimport { useRouteMatch } from \"react-router-dom\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { Helmet } from \"react-helmet\";\nimport _ from \"lodash\";\nimport { LinearProgress, Pill, Text, Button } from \"../../components\";\nimport Editor from \"@monaco-editor/react\";\nimport { configureMonaco } from \"../../schema/task\";\nimport { NEW_TASK_TEMPLATE } from \"../../schema/task\";\nimport ResetConfirmationDialog from \"./ResetConfirmationDialog\";\nimport SaveTaskDialog from \"./SaveTaskDialog\";\nimport { useTask } from \"../../data/task\";\nimport { usePushHistory } from \"../../components/NavLink\";\n\nconst useStyles = makeStyles({\n  wrapper: {\n    display: \"flex\",\n    height: \"100%\",\n    alignItems: \"stretch\",\n    flexDirection: \"column\",\n  },\n  name: {\n    fontWeight: \"bold\",\n  },\n  rightButtons: {\n    display: \"flex\",\n    flexGrow: 1,\n    justifyContent: \"flex-end\",\n    gap: 8,\n  },\n});\n\nexport default function TaskDefinition() {\n  const classes = useStyles();\n  const match = useRouteMatch();\n  const navigate = usePushHistory();\n\n  const [isModified, setIsModified] = useState(false);\n  const [jsonErrors, setJsonErrors] = useState([]);\n  const [resetDialog, setResetDialog] = useState(false);\n  const [saveDialog, setSaveDialog] = useState(null);\n\n  const editorRef = useRef();\n  const taskName = _.get(match, \"params.name\");\n\n  const {\n    data: taskDef,\n    isFetching,\n    refetch,\n  } = useTask(taskName, NEW_TASK_TEMPLATE);\n  const taskJson = useMemo(\n    () => (taskDef ? JSON.stringify(taskDef, null, 2) : \"\"),\n    [taskDef]\n  );\n\n  // Save\n  const handleOpenSave = () => {\n    setSaveDialog({\n      original: taskName ? taskJson : \"\",\n      originalObj: taskName ? taskDef : null,\n      modified: editorRef.current.getModel().getValue(),\n    });\n  };\n\n  const handleSaveSuccess = (name) => {\n    setSaveDialog(null);\n    setIsModified(false);\n\n    if (name === taskName) {\n      refetch();\n    } else {\n      navigate(`/taskDef/${name}`);\n    }\n  };\n\n  // Reset\n  const doReset = () => {\n    editorRef.current.getModel().setValue(taskJson);\n\n    setResetDialog(false);\n    setIsModified(false);\n  };\n\n  // Monaco Handlers\n  const handleEditorWillMount = (monaco) => {\n    configureMonaco(monaco);\n  };\n\n  const handleEditorDidMount = (editor) => {\n    editorRef.current = editor;\n  };\n\n  const handleValidate = (markers) => {\n    setJsonErrors(markers);\n  };\n\n  const handleChange = (v) => {\n    setIsModified(v !== taskJson);\n  };\n\n  return (\n    <>\n      <Helmet>\n        <title>Conductor UI - Task Definition - {taskName || \"New Task\"}</title>\n      </Helmet>\n\n      <SaveTaskDialog\n        document={saveDialog}\n        onCancel={() => setSaveDialog(null)}\n        onSuccess={handleSaveSuccess}\n      />\n\n      <ResetConfirmationDialog\n        version={resetDialog}\n        onConfirm={doReset}\n        onClose={() => setResetDialog(false)}\n      />\n\n      {isFetching && <LinearProgress />}\n      <div className={classes.wrapper}>\n        <Toolbar>\n          <Text className={classes.name}>{taskName || \"NEW\"}</Text>\n\n          {isModified ? (\n            <Pill color=\"yellow\" label=\"Modified\" />\n          ) : (\n            <Pill label=\"Unmodified\" />\n          )}\n          {!_.isEmpty(jsonErrors) && <Pill color=\"red\" label=\"Validation\" />}\n\n          <div className={classes.rightButtons}>\n            <Button\n              disabled={!_.isEmpty(jsonErrors) || !isModified}\n              onClick={handleOpenSave}\n            >\n              Save\n            </Button>\n            <Button\n              disabled={!isModified}\n              onClick={() => setResetDialog(true)}\n              variant=\"secondary\"\n            >\n              Reset\n            </Button>\n          </div>\n        </Toolbar>\n        <Editor\n          height=\"100%\"\n          width=\"100%\"\n          theme=\"vs-light\"\n          language=\"json\"\n          value={taskJson}\n          autoIndent={true}\n          beforeMount={handleEditorWillMount}\n          onMount={handleEditorDidMount}\n          onValidate={handleValidate}\n          onChange={handleChange}\n          options={{\n            selectOnLineNumbers: true,\n            minimap: {\n              enabled: false,\n            },\n          }}\n        />\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definition/WorkflowDefinition.jsx",
    "content": "import React, { useEffect, useMemo, useReducer, useRef, useState } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { useRouteMatch } from \"react-router-dom\";\nimport { Button, Text, Select, Pill, LinearProgress } from \"../../components\";\nimport { IconButton, MenuItem, Toolbar, Tooltip } from \"@material-ui/core\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { Helmet } from \"react-helmet\";\nimport _ from \"lodash\";\nimport Editor from \"@monaco-editor/react\";\nimport {\n  useWorkflowDef,\n  useWorkflowNamesAndVersions,\n} from \"../../data/workflow\";\nimport WorkflowDAG from \"../../components/diagram/WorkflowDAG\";\nimport WorkflowGraph from \"../../components/diagram/WorkflowGraph\";\nimport ResetConfirmationDialog from \"./ResetConfirmationDialog\";\nimport {\n  configureMonaco,\n  NEW_WORKFLOW_TEMPLATE,\n  JSON_FILE_NAME,\n} from \"../../schema/workflow\";\nimport SaveWorkflowDialog from \"./SaveWorkflowDialog\";\nimport update from \"immutability-helper\";\nimport { usePushHistory } from \"../../components/NavLink\";\nimport { timestampRenderer } from \"../../utils/helpers\";\n\nimport {\n  KeyboardArrowLeftRounded,\n  KeyboardArrowRightRounded,\n} from \"@material-ui/icons\";\n\nconst minCodePanelWidth = 500;\nconst useStyles = makeStyles({\n  wrapper: {\n    display: \"flex\",\n    height: \"100%\",\n    alignItems: \"stretch\",\n  },\n  workflowCodePanel: (workflowDefState) => ({\n    width: workflowDefState.toggleGraphPanel\n      ? workflowDefState.workflowCodePanelWidth\n      : \"100%\",\n    display: \"flex\",\n    flexFlow: \"column\",\n  }),\n  workflowGraph: (workflowDefState) => ({\n    display: workflowDefState.toggleGraphPanel ? \"block\" : \"none\",\n    flexGrow: 1,\n  }),\n  resizer: (workflowDefState) => ({\n    display: workflowDefState.toggleGraphPanel ? \"block\" : \"none\",\n    width: 8,\n    cursor: \"col-resize\",\n    backgroundColor: \"rgb(45, 45, 45, 0.05)\",\n    resize: \"horizontal\",\n    \"&:hover\": {\n      backgroundColor: \"rgb(45, 45, 45, 0.3)\",\n    },\n  }),\n  workflowName: {\n    fontWeight: \"bold\",\n  },\n  rightButtons: {\n    display: \"flex\",\n    flexGrow: 1,\n    justifyContent: \"flex-end\",\n    gap: 8,\n  },\n  editorLineDecorator: {\n    backgroundColor: \"rgb(45, 45, 45, 0.1)\"\n  }\n});\n\nconst actions = {\n  NEW_SAVE_COMPLETE: 1,\n  SAVE_COMPLETE: 2,\n  CONFIRMATION_DIALOG_OPEN: 3,\n  CONFIRMATION_DIALOG_CLOSE: 4,\n  UPDATE_CODE_PANEL_WIDTH: 5,\n  UPDATE_MODIFIED: 6,\n  TOGGLE_GRAPH_PANEL: 7,\n  SAVE_COMPLETE_CLOSE: 8,\n  SAVE_CONFIRMATION_CLOSE: 9,\n  SAVE_CONFIRMATION_OPEN: 10,\n};\n\nfunction workflowDefStateReducer(state, action) {\n  switch (action.type) {\n    case actions.TOGGLE_GRAPH_PANEL:\n      return update(state, {\n        toggleGraphPanel: {\n          $set: !state.toggleGraphPanel,\n        },\n      });\n    case actions.UPDATE_CODE_PANEL_WIDTH:\n      return update(state, {\n        workflowCodePanelWidth: {\n          $set: `${action.newWidth}px`,\n        },\n      });\n    default:\n      return state;\n  }\n}\n\nexport default function Workflow() {\n  const match = useRouteMatch();\n  const navigate = usePushHistory();\n  const [saveDialog, setSaveDialog] = useState(null);\n  const [resetDialog, setResetDialog] = useState(false); // false=idle, undefined=current_version, otherwise version id\n  const [isModified, setIsModified] = useState(false);\n  const [dag, setDag] = useState(null);\n  const [jsonErrors, setJsonErrors] = useState([]);\n  const [decorations, setDecorations] = useState([]);\n\n  const workflowName = _.get(match, \"params.name\");\n  const workflowVersion = _.get(match, \"params.version\"); // undefined for latest\n\n  const [workflowDefState, dispatch] = useReducer(workflowDefStateReducer, {\n    workflowCodePanelWidth: \"50%\",\n    toggleGraphPanel: true,\n  });\n  const classes = useStyles(workflowDefState);\n\n  const {\n    data: workflowDef,\n    isFetching,\n    refetch: refetchWorkflow,\n  } = useWorkflowDef(workflowName, workflowVersion, NEW_WORKFLOW_TEMPLATE);\n\n  const workflowJson = useMemo(\n    () => (workflowDef ? JSON.stringify(workflowDef, null, 2) : \"\"),\n    [workflowDef]\n  );\n\n  useEffect(() => {\n    if (workflowDef) {\n      setDag(new WorkflowDAG(null, workflowDef));\n    }\n  }, [workflowDef]);\n\n  const { data: namesAndVersions, refetch: refetchNamesAndVersions } =\n    useWorkflowNamesAndVersions();\n  const versions = useMemo(\n    () => namesAndVersions.get(workflowName) || [],\n    [namesAndVersions, workflowName]\n  );\n\n  // Refs\n  const editorRef = useRef();\n  const resizeRef = useRef();\n\n  // Resize Handle\n  const handleMouseDown = () => {\n    document.addEventListener(\"mouseup\", handleMouseUp, true);\n    document.addEventListener(\"mousemove\", handleMouseMove, true);\n  };\n\n  const handleMouseUp = () => {\n    document.removeEventListener(\"mouseup\", handleMouseUp, true);\n    document.removeEventListener(\"mousemove\", handleMouseMove, true);\n  };\n\n  const handleMouseMove = (e) => {\n    let boundingClientRect = ReactDOM.findDOMNode(\n      resizeRef.current\n    ).getBoundingClientRect();\n    const newWidth = Math.max(\n      minCodePanelWidth,\n      e.clientX - boundingClientRect.x\n    );\n    dispatch({ type: actions.UPDATE_CODE_PANEL_WIDTH, newWidth: newWidth });\n  };\n\n  // Version Change or Reset\n  const handleResetVersion = (version) => {\n    if (isModified) {\n      setResetDialog(version);\n    } else {\n      changeVersionOrReset(version);\n    }\n  };\n\n  const changeVersionOrReset = (version) => {\n    if (version === workflowVersion) {\n      // Reset to fetched version\n      editorRef.current.getModel().setValue(workflowJson);\n    } else if (_.isUndefined(version)) {\n      navigate(`/workflowDef/${workflowName}`);\n    } else {\n      navigate(`/workflowDef/${workflowName}/${version}`);\n    }\n\n    setResetDialog(false);\n    setIsModified(false);\n  };\n\n  // Saving\n  const handleOpenSave = () => {\n    const modified = editorRef.current.getValue();\n\n    setSaveDialog({\n      original: workflowName ? workflowJson : \"\",\n      originalObj: workflowName ? workflowDef : null,\n      modified: modified,\n    });\n  };\n\n  const handleSaveCancel = () => {\n    setSaveDialog(null);\n  };\n\n  const handleSaveSuccess = (name, version) => {\n    setSaveDialog(null);\n    setIsModified(false);\n    refetchNamesAndVersions();\n\n    if (name === workflowName && version === workflowVersion) {\n      refetchWorkflow();\n    } else {\n      navigate(`/workflowDef/${name}/${version}`);\n    }\n  };\n\n  // Monaco Handlers\n  const handleEditorWillMount = (monaco) => {\n    configureMonaco(monaco);\n  };\n\n  const handleEditorDidMount = (editor) => {\n    editorRef.current = editor;\n  };\n\n  const handleValidate = (markers) => {\n    setJsonErrors(markers);\n  };\n\n  const handleChange = (v) => {\n    setIsModified(v !== workflowJson);\n  };\n\n  const handleWorkflowNodeClick = (node) => {\n    let editor = editorRef.current.getModel()\n    let searchResult = editor.findMatches(`\"taskReferenceName\": \"${node.ref}\"`)\n    if (searchResult.length){\n      editorRef.current.revealLineInCenter(searchResult[0]?.range?.startLineNumber, 0);\n      setDecorations(editorRef.current.deltaDecorations(decorations, [\n        {\n          range: searchResult[0]?.range,\n          options: {\n            isWholeLine: true,\n            inlineClassName: classes.editorLineDecorator\n          }\n        }\n      ]))\n    }\n  }\n\n  return (\n    <>\n      <Helmet>\n        <title>\n          Conductor UI - Workflow Definition - {workflowName || \"New Workflow\"}\n        </title>\n      </Helmet>\n\n      <ResetConfirmationDialog\n        version={resetDialog}\n        onConfirm={changeVersionOrReset}\n        onClose={() => setResetDialog(false)}\n      />\n\n      <SaveWorkflowDialog\n        document={saveDialog}\n        onCancel={handleSaveCancel}\n        onSuccess={handleSaveSuccess}\n      />\n\n      {isFetching && <LinearProgress />}\n      <div className={classes.wrapper}>\n        <div className={classes.workflowCodePanel} ref={resizeRef}>\n          <Toolbar>\n            <Text className={classes.workflowName}>\n              {workflowName || \"NEW\"}\n            </Text>\n\n            <Select\n              disabled={!workflowDef}\n              value={_.isUndefined(workflowVersion) ? \"\" : workflowVersion}\n              displayEmpty\n              renderValue={(v) =>\n                v === \"\" ? \"Latest Version\" : `Version ${v}`\n              }\n              onChange={(evt) => handleResetVersion(evt.target.value)}\n            >\n              <MenuItem value=\"\">Latest Version</MenuItem>\n              {versions.map((row) => (\n                <MenuItem value={row.version} key={row.version}>\n                  Version {row.version} ({versionTime(row)})\n                </MenuItem>\n              ))}\n            </Select>\n\n            {isModified ? (\n              <Pill color=\"yellow\" label=\"Modified\" />\n            ) : (\n              <Pill label=\"Unmodified\" />\n            )}\n            {!_.isEmpty(jsonErrors) && (\n              <Tooltip\n                disableFocusListener\n                title=\"There are validation or syntax errors. Validation errors at the root level may be seen by hovering over the opening brace.\"\n              >\n                <div>\n                  <Pill color=\"red\" label=\"Validation\" />\n                </div>\n              </Tooltip>\n            )}\n\n            <div className={classes.rightButtons}>\n              <Button\n                disabled={!_.isEmpty(jsonErrors) || !isModified}\n                onClick={handleOpenSave}\n              >\n                Save\n              </Button>\n              <Button\n                disabled={!isModified}\n                onClick={() => handleResetVersion(workflowVersion)}\n                variant=\"secondary\"\n              >\n                Reset\n              </Button>\n\n              <IconButton\n                onClick={() => dispatch({ type: actions.TOGGLE_GRAPH_PANEL })}\n              >\n                {workflowDefState.toggleGraphPanel && (\n                  <KeyboardArrowRightRounded />\n                )}\n                {!workflowDefState.toggleGraphPanel && (\n                  <KeyboardArrowLeftRounded />\n                )}\n              </IconButton>\n            </div>\n          </Toolbar>\n          <Editor\n            height=\"100%\"\n            width=\"100%\"\n            theme=\"vs-light\"\n            language=\"json\"\n            value={workflowJson}\n            autoIndent={true}\n            beforeMount={handleEditorWillMount}\n            onMount={handleEditorDidMount}\n            onValidate={handleValidate}\n            onChange={handleChange}\n            options={{\n              smoothScrolling: true,\n              selectOnLineNumbers: true,\n              minimap: {\n                enabled: false,\n              },\n            }}\n            path={JSON_FILE_NAME}\n          />\n        </div>\n        <span\n          className={classes.resizer}\n          onMouseDown={(e) => handleMouseDown(e)}\n        />\n        <div className={classes.workflowGraph}>\n          {dag && <WorkflowGraph dag={dag} onClick={handleWorkflowNodeClick} />}\n        </div>\n      </div>\n    </>\n  );\n}\n\nfunction versionTime(versionObj) {\n  return (\n    versionObj &&\n    timestampRenderer(versionObj.updateTime || versionObj.createTime)\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definitions/EventHandler.jsx",
    "content": "import React from \"react\";\nimport { NavLink, DataTable } from \"../../components\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport Header from \"./Header\";\nimport sharedStyles from \"../styles\";\nimport { Helmet } from \"react-helmet\";\nimport { useEventHandlers } from \"../../data/misc\";\n\nconst useStyles = makeStyles(sharedStyles);\n\nconst columns = [\n  {\n    name: \"name\",\n    renderer: (name) => (\n      <NavLink path={`/eventHandlerDef/${name}`}>{name}</NavLink>\n    ),\n  },\n  { name: \"event\" },\n  { name: \"createTime\", type: \"date\" },\n  {\n    name: \"actions\",\n    renderer: (val) => JSON.stringify(val.map((action) => action.action)),\n  },\n];\n\nexport default function EventHandlers() {\n  const classes = useStyles();\n\n  const { data: eventHandlers, isFetching } = useEventHandlers();\n\n  return (\n    <div className={classes.wrapper}>\n      <Header tabIndex={2} loading={isFetching} />\n      <Helmet>\n        <title>Conductor UI - Event Handler Definitions</title>\n      </Helmet>\n\n      <div className={classes.tabContent}>\n        {eventHandlers && (\n          <DataTable\n            title={`${eventHandlers.length} results`}\n            localStorageKey=\"eventHandlersTable\"\n            defaultShowColumns={[\"name\", \"event\", \"actions\"]}\n            keyField=\"name\"\n            data={eventHandlers}\n            columns={columns}\n          />\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definitions/Header.jsx",
    "content": "import React from \"react\";\nimport { Tab, Tabs, NavLink, LinearProgress, Heading } from \"../../components\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport sharedStyles from \"../styles\";\n\nconst useStyles = makeStyles(sharedStyles);\n\nexport default function Header({ tabIndex, loading }) {\n  const classes = useStyles();\n\n  return (\n    <div>\n      {loading && <LinearProgress />}\n      <div className={classes.header}>\n        <Heading level={3} gutterBottom>\n          Definitions\n        </Heading>\n        <Tabs value={tabIndex}>\n          <Tab label=\"Workflows\" component={NavLink} path=\"/workflowDefs\" />\n          <Tab label=\"Tasks\" component={NavLink} path=\"/taskDefs\" />\n          <Tab\n            label=\"Event Handlers\"\n            component={NavLink}\n            path=\"/eventHandlerDef\"\n          />\n        </Tabs>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definitions/Task.jsx",
    "content": "import React from \"react\";\nimport { NavLink, DataTable, Button } from \"../../components\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport Header from \"./Header\";\nimport sharedStyles from \"../styles\";\nimport { Helmet } from \"react-helmet\";\nimport AddIcon from \"@material-ui/icons/Add\";\nimport { useTaskDefs } from \"../../data/task\";\n\nconst useStyles = makeStyles(sharedStyles);\n\nconst columns = [\n  {\n    name: \"name\",\n    renderer: (name) => <NavLink path={`/taskDef/${name}`}>{name}</NavLink>,\n  },\n  { name: \"description\", grow: 2 },\n  { name: \"createTime\", type: \"date\" },\n  { name: \"ownerEmail\" },\n  { name: \"inputKeys\", type: \"json\", sortable: false },\n  { name: \"outputKeys\", type: \"json\", sortable: false },\n  { name: \"timeoutPolicy\", grow: 0.5 },\n  { name: \"timeoutSeconds\", grow: 0.5 },\n  { name: \"retryCount\", grow: 0.5 },\n  { name: \"retryLogic\" },\n  { name: \"retryDelaySeconds\", grow: 0.5 },\n  { name: \"responseTimeoutSeconds\", grow: 0.5 },\n  { name: \"inputTemplate\", type: \"json\", sortable: false },\n  { name: \"rateLimitPerFrequency\", grow: 0.5 },\n  { name: \"rateLimitFrequencyInSeconds\", grow: 0.5 },\n  {\n    name: \"name\",\n    label: \"Executions\",\n    id: \"executions_link\",\n    grow: 0.5,\n    renderer: (name) => (\n      <NavLink path={`/search/by-tasks?tasks=${name}`} newTab>\n        Query\n      </NavLink>\n    ),\n    sortable: false,\n    searchable: false,\n  },\n  { name: \"concurrentExecLimit\" },\n  { name: \"pollTimeoutSeconds\" },\n];\n\nexport default function TaskDefinitions() {\n  const classes = useStyles();\n  const { data: tasks, isFetching } = useTaskDefs();\n\n  return (\n    <div className={classes.wrapper}>\n      <Helmet>\n        <title>Conductor UI - Task Definitions</title>\n      </Helmet>\n\n      <Header tabIndex={1} loading={isFetching} />\n\n      <div className={classes.tabContent}>\n        <div className={classes.buttonRow}>\n          <Button component={NavLink} path=\"/taskDef\" startIcon={<AddIcon />}>\n            New Task Definition\n          </Button>\n        </div>\n\n        {tasks && (\n          <DataTable\n            title={`${tasks.length} results`}\n            localStorageKey=\"tasksTable\"\n            defaultShowColumns={[\n              \"name\",\n              \"description\",\n              \"ownerEmail\",\n              \"timeoutPolicy\",\n              \"retryCount\",\n              \"executions_link\",\n            ]}\n            keyField=\"name\"\n            default\n            data={tasks}\n            columns={columns}\n          />\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/definitions/Workflow.jsx",
    "content": "import React, { useMemo } from \"react\";\nimport { NavLink, DataTable, Button } from \"../../components\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport _ from \"lodash\";\nimport { useQueryState } from \"react-router-use-location-state\";\nimport { useLatestWorkflowDefs } from \"../../data/workflow\";\nimport Header from \"./Header\";\nimport sharedStyles from \"../styles\";\nimport { Helmet } from \"react-helmet\";\nimport AddIcon from \"@material-ui/icons/Add\";\n\nconst useStyles = makeStyles(sharedStyles);\n\nconst columns = [\n  {\n    name: \"name\",\n    renderer: (val) => (\n      <NavLink path={`/workflowDef/${val.trim()}`}>{val.trim()}</NavLink>\n    ),\n  },\n  { name: \"description\", grow: 2 },\n  { name: \"createTime\", type: \"date\" },\n  { name: \"version\", label: \"Latest Version\", grow: 0.5 },\n  { name: \"schemaVersion\", grow: 0.5 },\n  { name: \"restartable\", grow: 0.5 },\n  { name: \"workflowStatusListenerEnabled\", grow: 0.5 },\n  { name: \"ownerEmail\" },\n  { name: \"inputParameters\", type: \"json\", sortable: false },\n  { name: \"outputParameters\", type: \"json\", sortable: false },\n  { name: \"timeoutPolicy\", grow: 0.5 },\n  { name: \"timeoutSeconds\", grow: 0.5 },\n  {\n    id: \"task_types\",\n    name: \"tasks\",\n    label: \"Task Types\",\n    searchable: \"calculated\",\n    sortable: false,\n    renderer: (val) => {\n      const taskTypeSet = new Set();\n      for (let task of val) {\n        taskTypeSet.add(task.type);\n      }\n      return Array.from(taskTypeSet).join(\", \");\n    },\n  },\n  {\n    id: \"task_count\",\n    name: \"tasks\",\n    label: \"Tasks\",\n    searchable: \"calculated\",\n    sortable: false,\n    grow: 0.5,\n    renderer: (val) => (_.isArray(val) ? val.length : 0),\n  },\n  {\n    id: \"executions_link\",\n    name: \"name\",\n    label: \"Executions\",\n    sortable: false,\n    searchable: false,\n    grow: 0.5,\n    renderer: (name) => (\n      <NavLink path={`/?workflowType=${name.trim()}`} newTab>\n        Query\n      </NavLink>\n    ),\n  },\n];\n\nexport default function WorkflowDefinitions() {\n  const classes = useStyles();\n\n  const { data, isFetching } = useLatestWorkflowDefs();\n\n  const [filterParam, setFilterParam] = useQueryState(\"filter\", \"\");\n  const filterObj = filterParam === \"\" ? undefined : JSON.parse(filterParam);\n\n  const handleFilterChange = (obj) => {\n    if (obj) {\n      setFilterParam(JSON.stringify(obj));\n    } else {\n      setFilterParam(\"\");\n    }\n  };\n\n  const workflows = useMemo(() => {\n    // Extract latest versions only\n    if (data) {\n      const unique = new Map();\n      const types = new Set();\n      for (let workflowDef of data) {\n        if (!unique.has(workflowDef.name)) {\n          unique.set(workflowDef.name, workflowDef);\n        } else if (unique.get(workflowDef.name).version < workflowDef.version) {\n          unique.set(workflowDef.name, workflowDef);\n        }\n\n        for (let task of workflowDef.tasks) {\n          types.add(task.type);\n        }\n      }\n\n      return Array.from(unique.values());\n    }\n  }, [data]);\n\n  return (\n    <div className={classes.wrapper}>\n      <Helmet>\n        <title>Conductor UI - Workflow Definitions</title>\n      </Helmet>\n      <Header tabIndex={0} loading={isFetching} />\n\n      <div className={classes.tabContent}>\n        <div className={classes.buttonRow}>\n          <Button\n            component={NavLink}\n            path=\"/workflowDef\"\n            startIcon={<AddIcon />}\n          >\n            New Workflow Definition\n          </Button>\n        </div>\n\n        {workflows && (\n          <DataTable\n            title={`${workflows.length} results`}\n            localStorageKey=\"definitionsTable\"\n            defaultShowColumns={[\n              \"name\",\n              \"description\",\n              \"version\",\n              \"createTime\",\n              \"ownerEmail\",\n              \"task_count\",\n              \"executions_link\",\n            ]}\n            keyField=\"name\"\n            onFilterChange={handleFilterChange}\n            initialFilterObj={filterObj}\n            data={workflows}\n            columns={columns}\n          />\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/ActionModule.jsx",
    "content": "import { makeStyles } from \"@material-ui/styles\";\nimport { isFailedTask } from \"../../utils/helpers\";\nimport { DropdownButton } from \"../../components\";\nimport { ListItemIcon, ListItemText } from \"@material-ui/core\";\nimport StopIcon from \"@material-ui/icons/Stop\";\nimport PauseIcon from \"@material-ui/icons/Pause\";\nimport ReplayIcon from \"@material-ui/icons/Replay\";\nimport ResumeIcon from \"@material-ui/icons/PlayArrow\";\nimport RedoIcon from \"@material-ui/icons/Redo\";\nimport FlareIcon from \"@material-ui/icons/Flare\";\n\nimport {\n  useRestartAction,\n  useRestartLatestAction,\n  useResumeAction,\n  useRetryResumeSubworkflowTasksAction,\n  useRetryAction,\n  useTerminateAction,\n  usePauseAction,\n} from \"../../data/actions\";\n\nconst useStyles = makeStyles({\n  terminate: {\n    color: \"red\",\n  },\n});\n\nexport default function ActionModule({ execution, triggerReload }) {\n  const classes = useStyles();\n  const { workflowId, workflowDefinition } = execution;\n\n  const restartAction = useRestartAction({ workflowId, onSuccess });\n  const restartLatestAction = useRestartLatestAction({ workflowId, onSuccess });\n  const retryAction = useRetryAction({ workflowId, onSuccess });\n  const retryResumeSubworkflowTasksAction =\n    useRetryResumeSubworkflowTasksAction({ workflowId, onSuccess });\n  const terminateAction = useTerminateAction({ workflowId, onSuccess });\n  const resumeAction = useResumeAction({ workflowId, onSuccess });\n  const pauseAction = usePauseAction({ workflowId, onSuccess });\n\n  const { restartable } = workflowDefinition;\n\n  function onSuccess() {\n    triggerReload();\n  }\n\n  const options = [];\n\n  // RESTART buttons\n  if (\n    [\"COMPLETED\", \"FAILED\", \"TIMED_OUT\", \"TERMINATED\"].includes(\n      execution.status\n    ) &&\n    restartable\n  ) {\n    options.push({\n      label: (\n        <>\n          <ListItemIcon>\n            <ReplayIcon />\n          </ListItemIcon>\n          <ListItemText>Restart with Current Definitions</ListItemText>\n        </>\n      ),\n      handler: () => restartAction.mutate(),\n    });\n\n    options.push({\n      label: (\n        <>\n          <ListItemIcon>\n            <FlareIcon />\n          </ListItemIcon>\n          <ListItemText>Restart with Latest Definitions</ListItemText>\n        </>\n      ),\n      handler: () => restartLatestAction.mutate(),\n    });\n  }\n\n  // PAUSE button\n  if (execution.status === \"RUNNING\") {\n    options.push({\n      label: (\n        <>\n          <ListItemIcon>\n            <PauseIcon />\n          </ListItemIcon>\n          <ListItemText>Pause</ListItemText>\n        </>\n      ),\n      handler: () => pauseAction.mutate(),\n    });\n  }\n\n  // RESUME button\n  if (execution.status === \"PAUSED\") {\n    options.push({\n      label: (\n        <>\n          <ListItemIcon>\n            <ResumeIcon />\n          </ListItemIcon>\n          <ListItemText>Resume</ListItemText>\n        </>\n      ),\n      handler: () => resumeAction.mutate(),\n    });\n  }\n\n  // RETRY (from task) button\n  if ([\"FAILED\", \"TIMED_OUT\", \"TERMINATED\"].includes(execution.status)) {\n    options.push({\n      label: (\n        <>\n          <ListItemIcon>\n            <RedoIcon />\n          </ListItemIcon>\n          <ListItemText>Retry - From failed task</ListItemText>\n        </>\n      ),\n      handler: () => retryAction.mutate(),\n    });\n  }\n\n  // RETRY (failed subworkflow) button\n  if (\n    [\"FAILED\", \"TIMED_OUT\", \"TERMINATED\"].includes(execution.status) &&\n    execution.tasks.find(\n      (task) =>\n        task.workflowTask.type === \"SUB_WORKFLOW\" && isFailedTask(task.status)\n    )\n  ) {\n    options.push({\n      label: (\n        <>\n          <ListItemIcon>\n            <RedoIcon />\n          </ListItemIcon>\n          <ListItemText>Retry - Resume failed subworkflow</ListItemText>\n        </>\n      ),\n      handler: () => retryResumeSubworkflowTasksAction.mutate(),\n    });\n  }\n\n  // RERUN button\n\n  // TERMINATE button\n  if ([\"RUNNING\", \"FAILED\", \"TIMED_OUT\", \"PAUSED\"].includes(execution.status)) {\n    options.push({\n      label: (\n        <>\n          <ListItemIcon className={classes.terminate}>\n            <StopIcon />\n          </ListItemIcon>\n          <ListItemText className={classes.terminate}>Terminate</ListItemText>\n        </>\n      ),\n      handler: () => terminateAction.mutate(),\n    });\n\n    options.push({\n      label: (\n        <>\n          <ListItemIcon className={classes.terminate}>\n            <StopIcon />\n          </ListItemIcon>\n          <ListItemText className={classes.terminate}>\n            Terminate with Reason\n          </ListItemText>\n        </>\n      ),\n      handler: () => {\n        const reason = window.prompt(\"Termination Reason\", \"\");\n        if (reason) terminateAction.mutate({ reason });\n      },\n    });\n  }\n\n  return <DropdownButton options={options}>Actions</DropdownButton>;\n}\n"
  },
  {
    "path": "ui/src/pages/execution/Execution.jsx",
    "content": "import React, { useMemo, useState, useEffect, useCallback } from \"react\";\nimport { useQueryState } from \"react-router-use-location-state\";\nimport Alert from \"@material-ui/lab/Alert\";\nimport {\n  Tabs,\n  Tab,\n  NavLink,\n  SecondaryButton,\n  LinearProgress,\n  Heading,\n} from \"../../components\";\nimport { Tooltip } from \"@material-ui/core\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { useRouteMatch } from \"react-router-dom\";\nimport TaskDetails from \"./TaskDetails\";\nimport ExecutionSummary from \"./ExecutionSummary\";\nimport ExecutionJson from \"./ExecutionJson\";\nimport InputOutput from \"./ExecutionInputOutput\";\nimport clsx from \"clsx\";\nimport ActionModule from \"./ActionModule\";\nimport IconButton from \"@material-ui/core/IconButton\";\nimport CloseIcon from \"@material-ui/icons/Close\";\nimport FullscreenIcon from \"@material-ui/icons/Fullscreen\";\nimport FullscreenExitIcon from \"@material-ui/icons/FullscreenExit\";\nimport RightPanel from \"./RightPanel\";\nimport WorkflowDAG from \"../../components/diagram/WorkflowDAG\";\nimport StatusBadge from \"../../components/StatusBadge\";\nimport { Helmet } from \"react-helmet\";\nimport sharedStyles from \"../styles\";\nimport rison from \"rison\";\nimport { useWorkflow } from \"../../data/workflow\";\n\nconst maxWindowWidth = window.innerWidth;\nconst INIT_DRAWER_WIDTH = 650;\n\nconst useStyles = makeStyles({\n  header: sharedStyles.header,\n  drawer: {\n    zIndex: 999,\n    position: \"absolute\",\n    top: 0,\n    right: 0,\n    bottom: 0,\n    width: (state) => (state.isFullWidth ? \"100%\" : state.drawerWidth),\n  },\n  drawerHeader: {\n    display: \"flex\",\n    alignItems: \"center\",\n    padding: 10,\n    justifyContent: \"flex-end\",\n    height: 80,\n    flexShrink: 0,\n    boxShadow: \"0 4px 8px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%)\",\n    zIndex: 1,\n    backgroundColor: \"#fff\",\n  },\n  dragger: {\n    display: (state) => (state.isFullWidth ? \"none\" : \"block\"),\n    width: \"5px\",\n    cursor: \"ew-resize\",\n    padding: \"4px 0 0\",\n    position: \"absolute\",\n    height: \"100%\",\n    zIndex: \"100\",\n    backgroundColor: \"#f4f7f9\",\n  },\n  drawerMain: {\n    paddingLeft: (state) => (state.isFullWidth ? 0 : 4),\n    height: \"100%\",\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n  drawerContent: {\n    flex: 1,\n    backgroundColor: \"#fff\",\n    display: \"flex\",\n    flexDirection: \"column\",\n    overflow: \"hidden\",\n  },\n  content: {\n    height: \"100%\",\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n  contentShift: {\n    marginRight: (state) => state.drawerWidth,\n  },\n  tabContent: {\n    flex: 1,\n    overflow: \"hidden\",\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n  headerSubtitle: {\n    marginBottom: 20,\n  },\n  fr: {\n    display: \"flex\",\n    position: \"relative\",\n    float: \"right\",\n    marginRight: 50,\n    marginTop: 10,\n    zIndex: 1,\n  },\n  frItem: {\n    display: \"flex\",\n    alignItems: \"center\",\n    marginRight: 15,\n  },\n  rightPanel: {\n    height: \"100%\",\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n});\n\nexport default function Execution() {\n  const match = useRouteMatch();\n\n  const {\n    data: execution,\n    isFetching,\n    refetch: refresh,\n  } = useWorkflow(match.params.id);\n\n  const [isFullWidth, setIsFullWidth] = useState(false);\n  const [isResizing, setIsResizing] = useState(false);\n  const [drawerWidth, setDrawerWidth] = useState(INIT_DRAWER_WIDTH);\n\n  const [tabIndex, setTabIndex] = useQueryState(\"tabIndex\", 0);\n  const [selectedTaskRison, setSelectedTaskRison] = useQueryState(\"task\", \"\");\n\n  const dag = useMemo(\n    () => (execution ? new WorkflowDAG(execution) : null),\n    [execution]\n  );\n\n  const selectedTask = useMemo(\n    () => selectedTaskRison && rison.decode(selectedTaskRison),\n    [selectedTaskRison]\n  );\n\n  const setSelectedTask = (taskPointer) => {\n    setSelectedTaskRison(rison.encode(taskPointer));\n  };\n\n  const classes = useStyles({\n    isFullWidth,\n    drawerWidth,\n  });\n\n  const handleMousemove = useCallback(\n    (e) => {\n      // we don't want to do anything if we aren't resizing.\n      if (!isResizing) {\n        return;\n      }\n\n      // Stop highlighting\n      e.preventDefault();\n      const offsetRight =\n        document.body.offsetWidth - (e.clientX - document.body.offsetLeft);\n      const minWidth = 0;\n      const maxWidth = maxWindowWidth - 100;\n      if (offsetRight > minWidth && offsetRight < maxWidth) {\n        setDrawerWidth(offsetRight);\n      }\n    },\n    [isResizing]\n  );\n\n  const handleMousedown = (e) => setIsResizing(true);\n\n  const handleClose = () => {\n    setSelectedTaskRison(null);\n  };\n\n  const handleFullScreen = () => {\n    setIsFullWidth(true);\n  };\n\n  const handleFullScreenExit = () => {\n    setIsFullWidth(false);\n  };\n\n  // On load and destroy only\n  useEffect(() => {\n    const mouseUp = (e) => setIsResizing(false);\n\n    document.addEventListener(\"mousemove\", handleMousemove);\n    document.addEventListener(\"mouseup\", mouseUp);\n\n    return () => {\n      document.removeEventListener(\"mousemove\", handleMousemove);\n      document.removeEventListener(\"mouseup\", mouseUp);\n    };\n  }, [handleMousemove]);\n\n  return (\n    <>\n      <Helmet>\n        <title>Conductor UI - Execution - {match.params.id}</title>\n      </Helmet>\n      <div\n        className={clsx(classes.content, {\n          [classes.contentShift]: !!selectedTask,\n        })}\n      >\n        {isFetching && <LinearProgress />}\n        {execution && (\n          <>\n            <div className={classes.header}>\n              <div className={classes.fr}>\n                {execution.parentWorkflowId && (\n                  <div className={classes.frItem}>\n                    <NavLink\n                      newTab\n                      path={`/execution/${execution.parentWorkflowId}`}\n                    >\n                      Parent Workflow\n                    </NavLink>\n                  </div>\n                )}\n                <div className={classes.frItem}>\n                  <NavLink newTab path={`/workflowDef/${execution.workflowName}`}>Definition</NavLink>\n                </div>\n                <SecondaryButton onClick={refresh} style={{ marginRight: 10 }}>\n                  Refresh\n                </SecondaryButton>\n                <ActionModule execution={execution} triggerReload={refresh} />\n              </div>\n              <Heading level={3} gutterBottom>\n                {execution.workflowType || execution.workflowName}{\" \"}\n                <StatusBadge status={execution.status} />\n              </Heading>\n              <Heading level={0} className={classes.headerSubtitle}>\n                {execution.workflowId}\n              </Heading>\n\n              {execution.reasonForIncompletion && (\n                <Alert severity=\"error\">\n                  {execution.reasonForIncompletion}\n                </Alert>\n              )}\n\n              <Tabs value={tabIndex} style={{ marginBottom: 0 }}>\n                <Tab label=\"Tasks\" onClick={() => setTabIndex(0)} />\n                <Tab label=\"Summary\" onClick={() => setTabIndex(1)} />\n                <Tab\n                  label=\"Workflow Input/Output\"\n                  onClick={() => setTabIndex(2)}\n                />\n                <Tab label=\"JSON\" onClick={() => setTabIndex(3)} />\n              </Tabs>\n            </div>\n            <div className={classes.tabContent}>\n              {tabIndex === 0 && (\n                <TaskDetails\n                  dag={dag}\n                  execution={execution}\n                  setSelectedTask={setSelectedTask}\n                  selectedTask={selectedTask}\n                />\n              )}\n              {tabIndex === 1 && <ExecutionSummary execution={execution} />}\n              {tabIndex === 2 && <InputOutput execution={execution} />}\n              {tabIndex === 3 && <ExecutionJson execution={execution} />}\n            </div>\n          </>\n        )}\n      </div>\n      {selectedTask && (\n        <div className={classes.drawer}>\n          <div\n            id=\"dragger\"\n            onMouseDown={(event) => handleMousedown(event)}\n            className={classes.dragger}\n          />\n          <div className={classes.drawerMain}>\n            <div className={classes.drawerHeader}>\n              {isFullWidth ? (\n                <Tooltip title=\"Restore sidebar\">\n                  <IconButton onClick={() => handleFullScreenExit()}>\n                    <FullscreenExitIcon />\n                  </IconButton>\n                </Tooltip>\n              ) : (\n                <Tooltip title=\"Maximize sidebar\">\n                  <IconButton onClick={() => handleFullScreen()}>\n                    <FullscreenIcon />\n                  </IconButton>\n                </Tooltip>\n              )}\n              <Tooltip title=\"Close sidebar\">\n                <IconButton onClick={() => handleClose()}>\n                  <CloseIcon />\n                </IconButton>\n              </Tooltip>\n            </div>\n            <div className={classes.drawerContent}>\n              <RightPanel\n                className={classes.rightPanel}\n                selectedTask={selectedTask}\n                dag={dag}\n                onTaskChange={setSelectedTask}\n              />\n            </div>\n          </div>\n        </div>\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/ExecutionInputOutput.jsx",
    "content": "import React from \"react\";\nimport { Paper, ReactJson } from \"../../components\";\nimport { makeStyles } from \"@material-ui/styles\";\n\nconst useStyles = makeStyles({\n  wrapper: {\n    margin: 30,\n    height: \"100%\",\n    display: \"flex\",\n    flexDirection: \"column\",\n    overflow: \"hidden\",\n  },\n  column: {\n    display: \"flex\",\n    flexDirection: \"row\",\n    gap: 15,\n    flex: 2,\n    marginBottom: 15,\n    overflow: \"hidden\",\n  },\n  paper: {\n    flex: 1,\n    overflow: \"hidden\",\n  },\n});\n\nexport default function InputOutput({ execution }) {\n  const classes = useStyles();\n  return (\n    <div className={classes.wrapper}>\n      <div className={classes.column}>\n        <Paper className={classes.paper}>\n          <ReactJson\n            className={classes.json}\n            src={execution.input}\n            label=\"Input\"\n          />\n        </Paper>\n        <Paper className={classes.paper}>\n          <ReactJson\n            className={classes.json}\n            src={execution.output}\n            label=\"Output\"\n          />\n        </Paper>\n      </div>\n      <Paper className={classes.paper}>\n        <ReactJson\n          className={classes.json}\n          src={execution.variables}\n          label=\"Variables\"\n        />\n      </Paper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/ExecutionJson.jsx",
    "content": "import React from \"react\";\nimport { Paper } from \"../../components\";\nimport ReactJson from \"../../components/ReactJson\";\nimport { makeStyles } from \"@material-ui/styles\";\n\nconst useStyles = makeStyles({\n  paper: {\n    margin: 30,\n    flex: 1,\n  },\n  wrapper: {\n    flex: 1,\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n});\n\nexport default function ExecutionJson({ execution }) {\n  const classes = useStyles();\n\n  return (\n    <div className={classes.wrapper}>\n      <Paper className={classes.paper}>\n        <ReactJson label=\"Unabridged Workflow JSON\" src={execution} />\n      </Paper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/ExecutionSummary.jsx",
    "content": "import React from \"react\";\nimport { Paper, NavLink, KeyValueTable } from \"../../components\";\nimport { makeStyles } from \"@material-ui/styles\";\n\nconst useStyles = makeStyles({\n  paper: {\n    margin: 30,\n  },\n  wrapper: {\n    overflowY: \"auto\",\n  },\n});\n\nexport default function ExecutionSummary({ execution }) {\n  const classes = useStyles();\n\n  // To accommodate unexecuted tasks, read type & name out of workflowTask\n  const data = [\n    { label: \"Workflow ID\", value: execution.workflowId },\n    { label: \"Status\", value: execution.status },\n    { label: \"Version\", value: execution.workflowVersion },\n    { label: \"Start Time\", value: execution.startTime, type: \"date\" },\n    { label: \"End Time\", value: execution.endTime, type: \"date\" },\n    {\n      label: \"Duration\",\n      value: execution.endTime - execution.startTime,\n      type: \"duration\",\n    },\n  ];\n\n  if (execution.parentWorkflowId) {\n    data.push({\n      label: \"Parent Workflow ID\",\n      value: (\n        <NavLink newTab path={`/execution/${execution.parentWorkflowId}`}>\n          {execution.parentWorkflowId}\n        </NavLink>\n      ),\n    });\n  }\n\n  if (execution.parentWorkflowTaskId) {\n    data.push({\n      label: \"Parent Task ID\",\n      value: execution.parentWorkflowTaskId,\n    });\n  }\n\n  if (execution.reasonForIncompletion) {\n    data.push({\n      label: \"Reason for Incompletion\",\n      value: execution.reasonForIncompletion,\n    });\n  }\n\n  return (\n    <div className={classes.wrapper}>\n      <Paper className={classes.paper}>\n        <KeyValueTable data={data} />\n      </Paper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/Legend.jsx",
    "content": "import React, { Component } from \"react\";\nimport WorkflowDAG from \"../../components/diagram/WorkflowDAG\";\nimport WorkflowGraph from \"../../components/diagram/WorkflowGraph\";\n\nconst workflowDef = {\n  tasks: [\n    {\n      name: \"fork_join\",\n      taskReferenceName: \"fork\",\n      type: \"FORK_JOIN\",\n      forkTasks: [\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkChild_grp1a\",\n          },\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkChild_grp1b\",\n          },\n        ],\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkchild_grp2\",\n          },\n        ],\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkchild_grp3\",\n          },\n        ],\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkchild_grp4\",\n          },\n        ],\n      ],\n    },\n    {\n      name: \"join\",\n      taskReferenceName: \"join\",\n      type: \"JOIN\",\n      joinOn: [\"forkChild_par1\", \"forkChild_par2\", \"forkChild_ser1\"],\n    },\n\n    {\n      name: \"decision\",\n      taskReferenceName: \"decision\",\n      type: \"DECISION\",\n      decisionCases: [\n        [\n          {\n            name: \"simple_task\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"completed\",\n          },\n        ],\n        [\n          {\n            name: \"simple_task\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"failed\",\n          },\n        ],\n      ],\n    },\n    {\n      name: \"exclusive_join\",\n      taskReferenceName: \"exclusiveJoin\",\n      type: \"EXCLUSIVE_JOIN\",\n      joinOn: [\"completed\", \"failed\"],\n      defaultExclusiveJoinTask: [\"completed\"],\n    },\n    {\n      name: \"subworkflow\",\n      taskReferenceName: \"subworkflow\",\n      type: \"SUB_WORKFLOW\",\n      subworkflowParam: { name: \"foo\" },\n    },\n    {\n      name: \"dynamic_fork\",\n      taskReferenceName: \"dynamic_fork\",\n      type: \"FORK_JOIN_DYNAMIC\",\n      dynamicForkTasksParam: \"dynamicTasks\",\n      dynamicForkTasksInputParamName: \"dynamicTasksInput\",\n    },\n    {\n      name: \"join\",\n      taskReferenceName: \"dynamic_join\",\n      type: \"JOIN\",\n    },\n  ],\n};\n\nclass Legend extends Component {\n  constructor() {\n    super();\n    this.state = {\n      dag: new WorkflowDAG(null, workflowDef),\n    };\n  }\n  render() {\n    const { dag } = this.state;\n    return (\n      <div style={{ display: \"flex\", flexDirection: \"row\" }}>\n        <WorkflowGraph dag={dag} />\n      </div>\n    );\n  }\n}\n\nexport default Legend;\n"
  },
  {
    "path": "ui/src/pages/execution/RightPanel.jsx",
    "content": "import { useState, useEffect, useMemo } from \"react\";\nimport { Tabs, Tab, ReactJson, Dropdown, Banner } from \"../../components\";\nimport { TabPanel, TabContext } from \"@material-ui/lab\";\n\nimport TaskSummary from \"./TaskSummary\";\nimport TaskLogs from \"./TaskLogs\";\n\nimport { makeStyles } from \"@material-ui/styles\";\nimport _ from \"lodash\";\nimport TaskPollData from \"./TaskPollData\";\n\nconst useStyles = makeStyles({\n  banner: {\n    margin: 15,\n  },\n  dfSelect: {\n    padding: 15,\n    backgroundColor: \"#efefef\",\n  },\n  tabPanel: {\n    padding: 0,\n    flex: 1,\n    overflowY: \"auto\",\n  },\n});\n\nexport default function RightPanel({ selectedTask, dag, onTaskChange }) {\n  const [tabIndex, setTabIndex] = useState(\"summary\");\n\n  const classes = useStyles();\n\n  useEffect(() => {\n    setTabIndex(\"summary\"); // Reset to Status Tab on ref change\n  }, [selectedTask]);\n\n  const taskResult = useMemo(\n    () => dag && dag.resolveTaskResult(selectedTask),\n    [dag, selectedTask]\n  );\n  const dfOptions = useMemo(\n    () => dag && dag.getSiblings(selectedTask),\n    [dag, selectedTask]\n  );\n  const retryOptions = useMemo(\n    () => dag && dag.getRetries(selectedTask),\n    [dag, selectedTask]\n  );\n\n  if (!taskResult) {\n    return null;\n  } else\n    return (\n      <TabContext value={tabIndex}>\n        {dfOptions && (\n          <div className={classes.dfSelect}>\n            <Dropdown\n              onChange={(e, v) => {\n                onTaskChange({ ref: v.ref });\n              }}\n              options={dfOptions}\n              disableClearable\n              value={dfOptions.find(\n                (opt) => opt.ref === taskResult.referenceTaskName\n              )}\n              getOptionLabel={(x) => `${dropdownIcon(x.status)} ${x.ref}`}\n              style={{ marginBottom: 20, width: 500 }}\n            />\n          </div>\n        )}\n\n        {_.size(retryOptions) > 1 && (\n          <div className={classes.dfSelect}>\n            <Dropdown\n              label=\"Retried Task - Select an instance\"\n              disableClearable\n              onChange={(e, v) => {\n                onTaskChange({\n                  id: v.taskId,\n                });\n              }}\n              options={retryOptions}\n              value={retryOptions.find(\n                (opt) => opt.taskId === taskResult.taskId\n              )}\n              getOptionLabel={(t) =>\n                `${dropdownIcon(t.status)} Attempt ${t.retryCount} - ${\n                  t.taskId\n                }`\n              }\n              style={{ marginBottom: 20, width: 500 }}\n            />\n          </div>\n        )}\n\n        <Tabs value={tabIndex} contextual onChange={(e, v) => setTabIndex(v)}>\n          {[\n            <Tab label=\"Summary\" value=\"summary\" key=\"summary\" />,\n            <Tab\n              label=\"Input\"\n              disabled={!taskResult.status}\n              value=\"input\"\n              key=\"input\"\n            />,\n            <Tab\n              label=\"Output\"\n              disabled={!taskResult.status}\n              value=\"output\"\n              key=\"output\"\n            />,\n            <Tab\n              label=\"Logs\"\n              disabled={!taskResult.status}\n              value=\"logs\"\n              key=\"logs\"\n            />,\n            <Tab\n              label=\"JSON\"\n              disabled={!taskResult.status}\n              value=\"json\"\n              key=\"json\"\n            />,\n            <Tab label=\"Definition\" value=\"definition\" key=\"definition\" />,\n            ...(_.get(taskResult, \"workflowTask.type\") === \"SIMPLE\"\n              ? [\n                  <Tab\n                    label=\"Poll Data\"\n                    disabled={!taskResult.status}\n                    value=\"pollData\"\n                    key=\"pollData\"\n                  />,\n                ]\n              : []),\n          ]}\n        </Tabs>\n        <>\n          <TabPanel className={classes.tabPanel} value=\"summary\">\n            <TaskSummary taskResult={taskResult} />\n          </TabPanel>\n          <TabPanel className={classes.tabPanel} value=\"input\">\n            {taskResult.externalInputPayloadStoragePath ? (\n              <Banner className={classes.banner}>\n                This task has externalized input. Please reference{\" \"}\n                <code>externalInputPayloadStoragePath</code> for the storage\n                location.\n              </Banner>\n            ) : (\n              <ReactJson src={taskResult.inputData} label=\"Task Input\" />\n            )}\n          </TabPanel>\n          <TabPanel className={classes.tabPanel} value=\"output\">\n            {taskResult.externalOutputPayloadStoragePath ? (\n              <Banner className={classes.banner}>\n                This task has externalized output. Please reference{\" \"}\n                <code>externalOutputPayloadStoragePath</code> for the storage\n                location.\n              </Banner>\n            ) : (\n              <ReactJson src={taskResult.outputData} label=\"Task Output\" />\n            )}\n          </TabPanel>\n          <TabPanel className={classes.tabPanel} value=\"pollData\">\n            <TaskPollData task={taskResult} />\n          </TabPanel>\n          <TabPanel className={classes.tabPanel} value=\"logs\">\n            <TaskLogs task={taskResult} />\n          </TabPanel>\n          <TabPanel className={classes.tabPanel} value=\"json\">\n            <ReactJson\n              src={taskResult}\n              label=\"Unabridged Task Execution Result\"\n            />\n          </TabPanel>\n          <TabPanel className={classes.tabPanel} value=\"definition\">\n            <ReactJson\n              src={taskResult.workflowTask}\n              label=\"Task Definition at Runtime\"\n            />\n          </TabPanel>\n        </>\n      </TabContext>\n    );\n}\n\nfunction dropdownIcon(status) {\n  let icon;\n  switch (status) {\n    case \"COMPLETED\":\n      icon = \"\\u2705\";\n      break; // Green-checkmark\n    case \"COMPLETED_WITH_ERRORS\":\n      icon = \"\\u2757\";\n      break; // Exclamation\n    case \"CANCELED\":\n      icon = \"\\uD83D\\uDED1\";\n      break; // stopsign\n    case \"IN_PROGRESS\":\n    case \"SCHEDULED\":\n      icon = \"\\u231B\";\n      break; // hourglass\n    default:\n      icon = \"\\u274C\"; // red-X\n  }\n  return icon + \"\\u2003\";\n}\n"
  },
  {
    "path": "ui/src/pages/execution/TaskDetails.jsx",
    "content": "import React, { useState } from \"react\";\nimport { Tabs, Tab, Paper } from \"../../components\";\nimport Timeline from \"./Timeline\";\nimport TaskList from \"./TaskList\";\nimport WorkflowGraph from \"../../components/diagram/WorkflowGraph\";\nimport { makeStyles } from \"@material-ui/styles\";\n\nconst useStyles = makeStyles({\n  taskWrapper: {\n    overflowY: \"auto\",\n    padding: 30,\n    height: \"100%\",\n  },\n});\n\nexport default function TaskDetails({\n  execution,\n  dag,\n  selectedTask,\n  setSelectedTask,\n}) {\n  const [tabIndex, setTabIndex] = useState(0);\n  const classes = useStyles();\n\n  return (\n    <div className={classes.taskWrapper}>\n      <Paper>\n        <Tabs value={tabIndex} contextual>\n          <Tab label=\"Diagram\" onClick={() => setTabIndex(0)} />\n          <Tab label=\"Task List\" onClick={() => setTabIndex(1)} />\n          <Tab label=\"Timeline\" onClick={() => setTabIndex(2)} />\n        </Tabs>\n\n        {tabIndex === 0 && (\n          <WorkflowGraph\n            selectedTask={selectedTask}\n            executionMode={true}\n            dag={dag}\n            onClick={setSelectedTask}\n          />\n        )}\n        {tabIndex === 1 && (\n          <TaskList\n            workflowId={execution.workflowId}\n            selectedTask={selectedTask}\n            tasks={execution.tasks}\n            dag={dag}\n            onClick={setSelectedTask}\n          />\n        )}\n        {tabIndex === 2 && (\n          <Timeline\n            selectedTask={selectedTask}\n            tasks={execution.tasks}\n            dag={dag}\n            onClick={setSelectedTask}\n          />\n        )}\n      </Paper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/TaskList.jsx",
    "content": "import { DataTable, TaskLink } from \"../../components\";\n\nexport default function TaskList({ selectedTask, tasks, workflowId }) {\n  const taskDetailFields = [\n    { name: \"seq\", grow: 0.2 },\n    {\n      name: \"taskId\",\n      renderer: (taskId) => (\n        <TaskLink workflowId={workflowId} taskId={taskId} />\n      ),\n      grow: 2,\n    },\n    { name: \"workflowTask.name\", id: \"taskName\", label: \"Task Name\" },\n    { name: \"referenceTaskName\", label: \"Ref\" },\n    { name: \"workflowTask.type\", id: \"taskType\", label: \"Type\", grow: 0.5 },\n    { name: \"scheduledTime\", type: \"date-ms\" },\n    { name: \"startTime\", type: \"date-ms\" },\n    { name: \"endTime\", type: \"date-ms\" },\n    { name: \"status\", grow: 0.8 },\n    { name: \"updateTime\", type: \"date-ms\" },\n    { name: \"callbackAfterSeconds\" },\n    { name: \"pollCount\", grow: 0.5 },\n  ];\n\n  return (\n    <DataTable\n      style={{ minHeight: 400 }}\n      data={tasks}\n      columns={taskDetailFields}\n      defaultShowColumns={[\n        \"seq\",\n        \"taskId\",\n        \"taskName\",\n        \"referenceTaskName\",\n        \"taskType\",\n        \"startTime\",\n        \"endTime\",\n        \"status\",\n      ]}\n      localStorageKey=\"taskListTable\"\n    />\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/TaskLogs.jsx",
    "content": "import React from \"react\";\nimport { useLogs } from \"../../data/misc\";\nimport { DataTable, Text, LinearProgress } from \"../../components\";\n\nexport default function TaskLogs({ task }) {\n  const { taskId } = task;\n  const { data: log, isFetching } = useLogs({ taskId });\n\n  if (isFetching) {\n    return <LinearProgress />;\n  }\n  return log && log.length > 0 ? (\n    <DataTable\n      data={log}\n      columns={[\n        { name: \"createdTime\", type: \"date\", label: \"Timestamp\" },\n        { name: \"log\", label: \"Entry\" },\n      ]}\n      title=\"Task Logs\"\n    />\n  ) : (\n    <Text style={{ margin: 15 }} variant=\"body1\">\n      No logs available\n    </Text>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/TaskPollData.jsx",
    "content": "import React from \"react\";\nimport { KeyValueTable, LinearProgress } from \"../../components\";\nimport { usePollData, useQueueSize } from \"../../data/task\";\nimport _ from \"lodash\";\nimport { timestampRenderer } from \"../../utils/helpers\";\n\nexport default function TaskPollData({ task }) {\n  const { data: pollData, isLoading } = usePollData(task.workflowTask.name);\n  const { data: queueSize, isLoadingQueueSize } = useQueueSize(\n    task.workflowTask.name,\n    task.domain\n  );\n\n  if (isLoading || isLoadingQueueSize) {\n    return <LinearProgress />;\n  }\n\n  const pollDataRow = pollData.find((row) => {\n    if (task.domain) {\n      return row.domain === task.domain;\n    } else {\n      return _.isUndefined(row.domain);\n    }\n  });\n\n  const data = [\n    { label: \"Task Name\", value: task.workflowTask.name },\n    { label: \"Domain\", value: _.defaultTo(task.domain, \"(No Domain Set)\") },\n  ];\n\n  if (pollDataRow) {\n    data.push({\n      label: \"Last Polled By Worker\",\n      value: pollDataRow.workerId,\n    });\n    data.push({\n      label: \"Last Poll Time\",\n      value: timestampRenderer(pollDataRow.lastPollTime),\n    });\n  }\n  if (queueSize !== undefined) {\n    data.push({\n      label: \"Current Queue Size\",\n      value: queueSize,\n    });\n  }\n\n  return <KeyValueTable data={data} />;\n}\n"
  },
  {
    "path": "ui/src/pages/execution/TaskSummary.jsx",
    "content": "import React from \"react\";\nimport _ from \"lodash\";\nimport { NavLink, KeyValueTable } from \"../../components\";\nimport { useTime } from \"../../hooks/useTime\";\n\nexport default function TaskSummary({ taskResult }) {\n  const now = useTime();\n\n  // To accommodate unexecuted tasks, read type & name & ref out of workflow\n  const data = [\n    { label: \"Task Type\", value: taskResult.workflowTask.type },\n    { label: \"Status\", value: taskResult.status || \"Not executed\" },\n    { label: \"Task Name\", value: taskResult.workflowTask.name },\n    {\n      label: \"Task Reference\",\n      value:\n        taskResult.referenceTaskName ||\n        taskResult.workflowTask.aliasForRef ||\n        taskResult.workflowTask.taskReferenceName,\n    },\n  ];\n\n  if (taskResult.domain) {\n    data.push({ label: \"Domain\", value: taskResult.domain });\n  }\n\n  if (taskResult.taskId) {\n    data.push({ label: \"Task Execution ID\", value: taskResult.taskId });\n  }\n\n  if (_.isFinite(taskResult.retryCount)) {\n    data.push({ label: \"Retry Count\", value: taskResult.retryCount });\n  }\n\n  if (taskResult.scheduledTime) {\n    data.push({\n      label: \"Scheduled Time\",\n      value: taskResult.scheduledTime > 0 && taskResult.scheduledTime,\n      type: \"date-ms\",\n    });\n  }\n  if (taskResult.startTime) {\n    data.push({\n      label: \"Start Time\",\n      value: taskResult.startTime > 0 && taskResult.startTime,\n      type: \"date-ms\",\n    });\n  }\n  if (taskResult.endTime) {\n    data.push({\n      label: \"End Time\",\n      value: taskResult.endTime,\n      type: \"date-ms\",\n    });\n  }\n  if (taskResult.startTime && taskResult.endTime) {\n    data.push({\n      label: \"Duration\",\n      value:\n        taskResult.startTime > 0 && taskResult.endTime - taskResult.startTime,\n      type: \"duration\",\n    });\n  }\n  if (taskResult.startTime && taskResult.status === \"IN_PROGRESS\") {\n    data.push({\n      label: \"Current Elapsed Time\",\n      value: taskResult.startTime > 0 && now - taskResult.startTime,\n      type: \"duration\",\n    });\n  }\n  if (!_.isNil(taskResult.retrycount)) {\n    data.push({ label: \"Retry Count\", value: taskResult.retryCount });\n  }\n  if (taskResult.reasonForIncompletion) {\n    data.push({\n      label: \"Reason for Incompletion\",\n      value: taskResult.reasonForIncompletion,\n    });\n  }\n  if (taskResult.workerId) {\n    data.push({\n      label: \"Worker\",\n      value: taskResult.workerId,\n      type: \"workerId\",\n    });\n  }\n  if (taskResult.taskType === \"DECISION\") {\n    data.push({\n      label: \"Evaluated Case\",\n      value:\n        _.has(taskResult, \"outputData.caseOutput[0]\") &&\n        taskResult.outputData.caseOutput[0],\n    });\n  }\n  if (taskResult.workflowTask.type === \"SUB_WORKFLOW\") {\n    data.push({\n      label: \"Subworkflow Definition\",\n      value: (\n        <NavLink\n          newTab\n          path={`/workflowDef/${taskResult.workflowTask.subWorkflowParam.name}`}\n        >\n          {taskResult.workflowTask.subWorkflowParam.name}{\" \"}\n        </NavLink>\n      ),\n    });\n    if (_.has(taskResult, \"subWorkflowId\")) {\n      data.push({\n        label: \"Subworkflow ID\",\n        value: (\n          <NavLink newTab path={`/execution/${taskResult.subWorkflowId}`}>\n            {taskResult.subWorkflowId}\n          </NavLink>\n        ),\n      });\n    }\n  }\n\n  if (taskResult.externalInputPayloadStoragePath) {\n    data.push({\n      label: \"Externalized Input\",\n      value: taskResult.externalInputPayloadStoragePath,\n    });\n  }\n\n  if (taskResult.externalOutputPayloadStoragePath) {\n    data.push({\n      label: \"Externalized Output\",\n      value: taskResult.externalOutputPayloadStoragePath,\n    });\n  }\n\n  return <KeyValueTable data={data} />;\n}\n"
  },
  {
    "path": "ui/src/pages/execution/Timeline.jsx",
    "content": "import React, { useMemo } from \"react\";\nimport Timeline from \"react-vis-timeline-2\";\nimport { timestampRenderer, durationRenderer } from \"../../utils/helpers\";\nimport _ from \"lodash\";\nimport \"./timeline.scss\";\nimport ZoomOutMapIcon from \"@material-ui/icons/ZoomOutMap\";\nimport { IconButton, Tooltip } from \"@material-ui/core\";\n\nexport default function TimelineComponent({\n  dag,\n  tasks,\n  onClick,\n  selectedTask,\n}) {\n  const timelineRef = React.useRef();\n  /*\n  const selectedId = useMemo(() => {\n    if(selectedTask){\n      const taskResult = dag.resolveTaskResult(selectedTask);\n      return _.get(taskResult, \"taskId\")\n    }\n  }, [dag, selectedTask]);\n  */\n  const selectedId = null;\n\n  const { items, groups } = useMemo(() => {\n    const groupMap = new Map();\n    for (const task of tasks) {\n      groupMap.set(task.referenceTaskName, {\n        id: task.referenceTaskName,\n        content: `${task.referenceTaskName} (${task.workflowTask.name})`,\n      });\n    }\n\n    const items = tasks\n      .filter((t) => t.startTime > 0 || t.endTime > 0)\n      .map((task) => {\n        const dfParent = dag.graph\n          .predecessors(task.referenceTaskName)\n          .map((t) => dag.graph.node(t))\n          .find((t) => t.type === \"FORK_JOIN_DYNAMIC\");\n        const startTime =\n          task.startTime > 0\n            ? new Date(task.startTime)\n            : new Date(task.endTime);\n        const endTime =\n          task.endTime > 0 ? new Date(task.endTime) : new Date(task.startTime);\n        const duration = durationRenderer(\n          endTime.getTime() - startTime.getTime()\n        );\n        const retval = {\n          id: task.taskId,\n          group: task.referenceTaskName,\n          content: `${duration}`,\n          start: startTime,\n          end: endTime,\n          title: `${task.referenceTaskName} (${\n            task.status\n          })<br/>${timestampRenderer(startTime)} - ${timestampRenderer(\n            endTime\n          )}`,\n          className: `status_${task.status}`,\n        };\n\n        if (dfParent || task.type === \"FORK_JOIN_DYNAMIC\") {\n          //retval.subgroup=task.referenceTaskName\n          const gp = groupMap.get(dfParent.ref);\n          if (!gp.nestedGroups) {\n            gp.nestedGroups = [];\n          }\n          groupMap.get(task.referenceTaskName).treeLevel = 2;\n          gp.nestedGroups.push(task.referenceTaskName);\n        }\n\n        return retval;\n      });\n\n    return {\n      items: items,\n      groups: Array.from(groupMap.values()),\n    };\n  }, [tasks, dag]);\n\n  const onFit = () => {\n    timelineRef.current.timeline.fit();\n  };\n\n  const handleClick = (e) => {\n    const { group, item, what } = e;\n    if (group && what !== \"background\") {\n      if (_.size(dag.graph.node(group).taskResults) > 1) {\n        onClick({\n          ref: group,\n          taskId: item,\n        });\n      } else {\n        onClick({ ref: group });\n      }\n    }\n  };\n\n  return (\n    <div>\n      <div style={{ marginLeft: 15 }}>\n        Ctrl-scroll to zoom.{\" \"}\n        <Tooltip title=\"Zoom to Fit\">\n          <IconButton onClick={onFit}>\n            <ZoomOutMapIcon />\n          </IconButton>\n        </Tooltip>\n      </div>\n      <div className=\"timeline-container\">\n        <Timeline\n          ref={timelineRef}\n          initialGroups={groups}\n          initialItems={items}\n          selection={selectedId}\n          clickHandler={handleClick}\n          options={{\n            orientation: \"top\",\n            zoomKey: \"ctrlKey\",\n            type: \"range\",\n            stack: false,\n          }}\n        />\n      </div>\n      <br />\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/execution/timeline.scss",
    "content": "@mixin barColor($colorfg, $colorbg: #fff) {\n  background-color: $colorbg;\n  border-color: $colorfg;\n  color: $colorfg;\n}\n\n.vis-timeline {\n  border: none;\n}\n\n.vis-panel {\n  &.vis-top,\n  &.vis-center {\n    border-left: none;\n  }\n}\n.vis-label {\n  .vis-inner {\n    margin-left: 5px;\n  }\n  &.vis-nested-group.vis-group-level-2 {\n    background: white;\n  }\n}\n\n.vis-item {\n  &.status_COMPLETED {\n    @include barColor(#163e1d, #aee1b8);\n  }\n  &.status_COMPLETED_WITH_ERRORS {\n    @include barColor(#8b5b02, #feeac5);\n  }\n  &.status_IN_PROGRESS,\n  &.status_SCHEDULED {\n    @include barColor(#11497a, #cbe2f7);\n  }\n  //&.status_CANCELED { @include barColor(#26194b, #ded5f8); }\n  &.status_FAILED,\n  &.status_FAILED_WITH_TERMINAL_ERROR,\n  &.status_TIMED_OUT,\n  &.status_DF_PARTIAL,\n  &.status_CANCELED {\n    @include barColor(#7f050b, #f9c6c9);\n  }\n  &.status_SKIPPED {\n    @include barColor(gray);\n  }\n  &.vis-selected {\n    filter: brightness(70%);\n  }\n  .vis-item-content {\n    font-size: 10px;\n    padding: 0px 3px 0px 3px;\n  }\n}\n"
  },
  {
    "path": "ui/src/pages/executions/BulkActionModule.jsx",
    "content": "import React, { useState } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogActions,\n  DialogTitle,\n} from \"@material-ui/core\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport {\n  DataTable,\n  DropdownButton,\n  LinearProgress,\n  PrimaryButton,\n  Heading,\n} from \"../../components\";\nimport {\n  useBulkRestartAction,\n  useBulkRestartLatestAction,\n  useBulkResumeAction,\n  useBulkTerminateAction,\n  useBulkPauseAction,\n  useBulkRetryAction,\n  useBulkTerminateWithReasonAction,\n} from \"../../data/bulkactions\";\n\nconst useStyles = makeStyles({\n  actionBar: {\n    display: \"flex\",\n    alignItems: \"center\",\n    paddingRight: 10,\n    \"&>div, &>p\": {\n      marginRight: 10,\n    },\n    width: \"100%\",\n    justifyContent: \"space-between\",\n  },\n});\n\nexport default function BulkActionModule({ selectedRows }) {\n  const selectedIds = selectedRows.map((row) => row.workflowId);\n  const [results, setResults] = useState();\n  const classes = useStyles();\n\n  const { mutate: pauseAction, isLoading: pauseLoading } = useBulkPauseAction({\n    onSuccess,\n  });\n  const { mutate: resumeAction, isLoading: resumeLoading } =\n    useBulkResumeAction({ onSuccess });\n  const { mutate: restartCurrentAction, isLoading: restartCurrentLoading } =\n    useBulkRestartAction({ onSuccess });\n  const { mutate: restartLatestAction, isLoading: restartLatestLoading } =\n    useBulkRestartLatestAction({ onSuccess });\n  const { mutate: retryAction, isLoading: retryLoading } = useBulkRetryAction({\n    onSuccess,\n  });\n  const { mutate: terminateAction, isLoading: terminateLoading } =\n    useBulkTerminateAction({ onSuccess });\n  const {\n    mutate: terminateWithReasonAction,\n    isLoading: terminateWithReasonLoading,\n  } = useBulkTerminateWithReasonAction({ onSuccess });\n\n  const isLoading =\n    pauseLoading ||\n    resumeLoading ||\n    restartCurrentLoading ||\n    restartLatestLoading ||\n    retryLoading ||\n    terminateLoading ||\n    terminateWithReasonLoading;\n\n  function onSuccess(data, variables, context) {\n    const retval = {\n      bulkErrorResults: Object.entries(data.bulkErrorResults).map(\n        ([key, value]) => ({\n          workflowId: key,\n          message: value,\n        })\n      ),\n      bulkSuccessfulResults: data.bulkSuccessfulResults.map((value) => ({\n        workflowId: value,\n      })),\n    };\n    setResults(retval);\n  }\n\n  function handleClose() {\n    setResults(null);\n  }\n\n  return (\n    <div className={classes.actionBar}>\n      <Heading level={0}>{selectedRows.length} Workflows Selected.</Heading>\n      <DropdownButton\n        className={classes.actionButton}\n        options={[\n          {\n            label: \"Pause\",\n            handler: () => pauseAction({ body: JSON.stringify(selectedIds) }),\n          },\n          {\n            label: \"Resume\",\n            handler: () => resumeAction({ body: JSON.stringify(selectedIds) }),\n          },\n          {\n            label: \"Restart with current definitions\",\n            handler: () =>\n              restartCurrentAction({ body: JSON.stringify(selectedIds) }),\n          },\n          {\n            label: \"Restart with latest definitions\",\n            handler: () =>\n              restartLatestAction({ body: JSON.stringify(selectedIds) }),\n          },\n          {\n            label: \"Retry\",\n            handler: () => retryAction({ body: JSON.stringify(selectedIds) }),\n          },\n          {\n            label: \"Terminate\",\n            handler: () =>\n              terminateAction({ body: JSON.stringify(selectedIds) }),\n          },\n          {\n            label: \"Terminate with Reason\",\n            handler: () => {\n              const reason = window.prompt(\"Termination Reason\", \"\");\n              if (reason) {\n                terminateWithReasonAction({\n                  body: JSON.stringify(selectedIds),\n                  reason,\n                });\n              }\n            },\n          },\n        ]}\n      >\n        Bulk Action\n      </DropdownButton>\n      {(results || isLoading) && (\n        <Dialog\n          open={true}\n          fullScreen\n          onClose={handleClose}\n          style={{ padding: 30 }}\n        >\n          <DialogTitle>\n            <Heading level={3} style={{ padding: 15 }}>\n              Batch Actions\n            </Heading>\n            {isLoading && <LinearProgress />}\n          </DialogTitle>\n          <DialogContent>\n            {results && (\n              <React.Fragment>\n                <DataTable\n                  title=\"Successful Operations\"\n                  columns={[{ name: \"workflowId\" }]}\n                  data={results.bulkSuccessfulResults}\n                  pagination={false}\n                  showColumnSelector={false}\n                />\n                <DataTable\n                  title=\"Failed Operations\"\n                  columns={[\n                    { name: \"workflowId\" },\n                    { name: \"message\", wrap: true },\n                  ]}\n                  data={results.bulkErrorResults}\n                  pagination={false}\n                  showColumnSelector={false}\n                />\n              </React.Fragment>\n            )}\n          </DialogContent>\n          <DialogActions>\n            <PrimaryButton onClick={handleClose}>Close</PrimaryButton>\n          </DialogActions>\n        </Dialog>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/executions/ResultsTable.jsx",
    "content": "import React, { useState, useRef, useEffect } from \"react\";\nimport {\n  Paper,\n  NavLink,\n  DataTable,\n  LinearProgress,\n  TertiaryButton,\n  Text,\n} from \"../../components\";\nimport { Alert, AlertTitle } from \"@material-ui/lab\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport BulkActionModule from \"./BulkActionModule\";\nimport executionsStyles from \"./executionsStyles\";\nimport sharedStyles from \"../styles\";\n\nconst useStyles = makeStyles({\n  ...executionsStyles,\n  ...sharedStyles,\n});\n\nconst executionFields = [\n  { name: \"startTime\", type: \"date\" },\n  {\n    name: \"workflowId\",\n    grow: 2,\n    renderer: (workflowId) => (\n      <NavLink path={`/execution/${workflowId}`}>{workflowId}</NavLink>\n    ),\n  },\n  { name: \"workflowType\", grow: 2 },\n  { name: \"version\", grow: 0.5 },\n  { name: \"correlationId\", grow: 2 },\n  { name: \"updateTime\", type: \"date\" },\n  { name: \"endTime\", type: \"date\" },\n  { name: \"status\" },\n  { name: \"input\", grow: 2, wrap: true },\n  { name: \"output\", grow: 2 },\n  { name: \"reasonForIncompletion\" },\n  { name: \"executionTime\" },\n  { name: \"event\" },\n  { name: \"failedReferenceTaskNames\", grow: 2 },\n  { name: \"externalInputPayloadStoragePath\" },\n  { name: \"externalOutputPayloadStoragePath\" },\n  { name: \"priority\" },\n];\n\nfunction ShowMore({\n  rowsPerPage,\n  rowCount,\n  onChangePage,\n  onChangeRowsPerPage,\n  currentPage,\n}) {\n  return (\n    <div style={{ textAlign: \"center\", padding: 15 }}>\n      <TertiaryButton onClick={() => onChangePage(currentPage + 1)}>\n        Show More Results\n      </TertiaryButton>\n    </div>\n  );\n}\n\nexport default function ResultsTable({\n  resultObj,\n  error,\n  busy,\n  page,\n  rowsPerPage,\n  sort,\n  setPage,\n  setSort,\n  setRowsPerPage,\n  showMore,\n}) {\n  const classes = useStyles();\n  let totalHits = 0;\n  if (resultObj) {\n    if (resultObj.totalHits) {\n      totalHits = resultObj.totalHits;\n    } else {\n      if (resultObj.results) {\n        totalHits = resultObj.results.length;\n      }\n    }\n  }\n  const [selectedRows, setSelectedRows] = useState([]);\n  const [toggleCleared, setToggleCleared] = useState(false);\n  const tableRef = useRef(null);\n\n  const defaultSortField = sort ? sort.split(\":\")[0] : null;\n  const defaultSortDirection = sort ? sort.split(\":\")[1] : null;\n\n  useEffect(() => {\n    setSelectedRows([]);\n    setToggleCleared((t) => !t);\n  }, [resultObj]);\n\n  return (\n    <Paper className={classes.paper}>\n      {busy && <LinearProgress />}\n      {error && (\n        <Alert severity=\"error\">\n          <AlertTitle>Query Failed</AlertTitle>\n          {error.message}\n        </Alert>\n      )}\n      {!resultObj && !error && (\n        <Text className={classes.clickSearch}>\n          Click \"Search\" to submit query.\n        </Text>\n      )}\n      {resultObj && (\n        <DataTable\n          title={totalHits > 0 && ` Page ${page} of ${totalHits}`}\n          data={resultObj.results}\n          columns={executionFields}\n          defaultShowColumns={[\n            \"startTime\",\n            \"workflowType\",\n            \"workflowId\",\n            \"endTime\",\n            \"status\",\n          ]}\n          localStorageKey=\"executionsTable\"\n          keyField=\"workflowId\"\n          paginationServer\n          paginationTotalRows={totalHits}\n          paginationDefaultPage={page}\n          paginationPerPage={rowsPerPage}\n          onChangeRowsPerPage={(rowsPerPage) => setRowsPerPage(rowsPerPage)}\n          onChangePage={(page) => setPage(page)}\n          sortServer\n          defaultSortField={defaultSortField}\n          defaultSortAsc={defaultSortDirection === \"ASC\"}\n          onSort={(column, sortDirection) => {\n            setSort(column.id, sortDirection);\n          }}\n          selectableRows\n          contextComponent={\n            <BulkActionModule\n              selectedRows={selectedRows}\n              popperAnchorEl={tableRef.current}\n            />\n          }\n          onSelectedRowsChange={({ selectedRows }) =>\n            setSelectedRows(selectedRows)\n          }\n          clearSelectedRows={toggleCleared}\n          customStyles={{\n            header: {\n              style: {\n                overflow: \"visible\",\n              },\n            },\n            contextMenu: {\n              style: {\n                display: \"none\",\n              },\n              activeStyle: {\n                display: \"flex\",\n              },\n            },\n          }}\n          paginationComponent={showMore ? ShowMore : null}\n        />\n      )}\n    </Paper>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/executions/SearchTabs.jsx",
    "content": "import React from \"react\";\nimport { Tab, Tabs, NavLink } from \"../../components\";\n\nexport default function SearchTabs({ tabIndex }) {\n  return (\n    <Tabs value={tabIndex}>\n      <Tab label=\"Workflows\" component={NavLink} path=\"/\" />\n      <Tab label=\"Tasks\" component={NavLink} path=\"/search/tasks\" />\n    </Tabs>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/executions/TaskResultsTable.jsx",
    "content": "import React, { useState, useRef, useEffect } from \"react\";\nimport {\n  Paper,\n  NavLink,\n  DataTable,\n  LinearProgress,\n  TertiaryButton,\n  Text,\n} from \"../../components\";\nimport { Alert, AlertTitle } from \"@material-ui/lab\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport BulkActionModule from \"./BulkActionModule\";\nimport executionsStyles from \"./executionsStyles\";\nimport sharedStyles from \"../styles\";\nimport TaskLink from \"../../components/TaskLink\";\nimport { SEARCH_TASK_TYPES_SET } from \"../../utils/constants\";\n\nconst useStyles = makeStyles({\n  ...executionsStyles,\n  ...sharedStyles,\n});\n\nconst executionFields = [\n  { name: \"updateTime\", label: \"Update Time\", type: \"date\" },\n  { name: \"scheduledTime\", label: \"Scheduled Time\", type: \"date\" },\n  { name: \"startTime\", label: \"Start Time\", type: \"date\" },\n  { name: \"endTime\", label: \"End Time\", type: \"date\" },\n  {\n    name: \"taskId\",\n    label: \"Task ID\",\n    grow: 1.5,\n    renderer: (taskId, row) => (\n      <TaskLink taskId={taskId} workflowId={row.workflowId} />\n    ),\n  },\n  {\n    name: \"taskDefName\",\n    label: \"Task Name\",\n    grow: 1.5,\n    renderer: (taskDefName) =>\n      SEARCH_TASK_TYPES_SET.has(taskDefName) ? \"-\" : taskDefName,\n  },\n  {\n    name: \"taskType\",\n    label: \"Task Type\",\n    grow: 0.6,\n    sortable: false,\n    renderer: (taskType) =>\n      SEARCH_TASK_TYPES_SET.has(taskType) ? taskType : \"SIMPLE\",\n  },\n  {\n    name: \"workflowId\",\n    label: \"Workflow ID\",\n    grow: 2,\n    renderer: (workflowId) => (\n      <NavLink path={`/execution/${workflowId}`}>{workflowId}</NavLink>\n    ),\n  },\n  { name: \"workflowType\", label: \"Workflow Name\", grow: 1.5 },\n  {\n    name: \"executionTime\",\n    label: \"Execution Time\",\n    grow: 0.6,\n    sortable: false,\n  },\n  {\n    name: \"queueWaitTime\",\n    label: \"Queue Wait Time\",\n    grow: 0.6,\n    sortable: false,\n  },\n  {\n    name: \"workflowPriority\",\n    label: \"Workflow Priority\",\n    grow: 0.6,\n    sortable: false,\n  },\n  {\n    name: \"status\",\n    label: \"Status\",\n    sortable: false,\n  },\n  { name: \"input\", label: \"Input\", grow: 3, sortable: false, wrap: true },\n  { name: \"output\", label: \"Output\", grow: 3, sortable: false, wrap: true },\n  {\n    name: \"reasonForIncompletion\",\n    label: \"Reason for Incompletion\",\n    grow: 3,\n    sortable: false,\n    wrap: true,\n  },\n];\n\nfunction ShowMore({\n  rowsPerPage,\n  rowCount,\n  onChangePage,\n  onChangeRowsPerPage,\n  currentPage,\n}) {\n  return (\n    <div style={{ textAlign: \"center\", padding: 15 }}>\n      <TertiaryButton onClick={() => onChangePage(currentPage + 1)}>\n        Show More Results\n      </TertiaryButton>\n    </div>\n  );\n}\n\nexport default function ResultsTable({\n  resultObj,\n  error,\n  busy,\n  page,\n  rowsPerPage,\n  sort,\n  setPage,\n  setSort,\n  setRowsPerPage,\n  showMore,\n}) {\n  const classes = useStyles();\n  let totalHits = 0;\n  if (resultObj) {\n    if (resultObj.totalHits) {\n      totalHits = resultObj.totalHits;\n    } else {\n      if (resultObj.results) {\n        totalHits = resultObj.results.length;\n      }\n    }\n  }\n  const [selectedRows, setSelectedRows] = useState([]);\n  const [toggleCleared, setToggleCleared] = useState(false);\n  const tableRef = useRef(null);\n\n  const defaultSortField = sort ? sort.split(\":\")[0] : null;\n  const defaultSortDirection = sort ? sort.split(\":\")[1] : null;\n\n  useEffect(() => {\n    setSelectedRows([]);\n    setToggleCleared((t) => !t);\n  }, [resultObj]);\n\n  return (\n    <Paper className={classes.paper}>\n      {busy && <LinearProgress />}\n      {error && (\n        <Alert severity=\"error\">\n          <AlertTitle>Query Failed</AlertTitle>\n          {error.message}\n        </Alert>\n      )}\n      {!resultObj && !error && (\n        <Text className={classes.clickSearch}>\n          Click \"Search\" to submit query.\n        </Text>\n      )}\n      {resultObj && (\n        <DataTable\n          title={totalHits > 0 && ` Page ${page} of ${totalHits}`}\n          data={resultObj.results}\n          columns={executionFields}\n          defaultShowColumns={[\n            \"updateTime\",\n            \"taskId\",\n            \"taskDefName\",\n            \"workflowType\",\n            \"executionType\",\n            \"taskType\",\n            \"status\",\n          ]}\n          localStorageKey=\"taskResultsTable\"\n          keyField=\"taskId\"\n          paginationServer\n          paginationTotalRows={totalHits}\n          paginationDefaultPage={page}\n          paginationPerPage={rowsPerPage}\n          onChangeRowsPerPage={(rowsPerPage) => setRowsPerPage(rowsPerPage)}\n          onChangePage={(page) => setPage(page)}\n          sortServer\n          defaultSortField={defaultSortField}\n          defaultSortAsc={defaultSortDirection === \"ASC\"}\n          onSort={(column, sortDirection) => {\n            setSort(column.id, sortDirection);\n          }}\n          selectableRows\n          contextComponent={\n            <BulkActionModule\n              selectedRows={selectedRows}\n              popperAnchorEl={tableRef.current}\n            />\n          }\n          onSelectedRowsChange={({ selectedRows }) =>\n            setSelectedRows(selectedRows)\n          }\n          clearSelectedRows={toggleCleared}\n          customStyles={{\n            header: {\n              style: {\n                overflow: \"visible\",\n              },\n            },\n            contextMenu: {\n              style: {\n                display: \"none\",\n              },\n              activeStyle: {\n                display: \"flex\",\n              },\n            },\n          }}\n          paginationComponent={showMore ? ShowMore : null}\n        />\n      )}\n    </Paper>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/executions/TaskSearch.jsx",
    "content": "import React, { useState } from \"react\";\nimport _ from \"lodash\";\nimport { FormControl, Grid, InputLabel } from \"@material-ui/core\";\nimport {\n  Paper,\n  Heading,\n  PrimaryButton,\n  Dropdown,\n  Input,\n  TaskNameInput,\n  WorkflowNameInput,\n} from \"../../components\";\n\nimport { TASK_STATUSES, SEARCH_TASK_TYPES_SET } from \"../../utils/constants\";\nimport { useQueryState } from \"react-router-use-location-state\";\nimport SearchTabs from \"./SearchTabs\";\nimport TaskResultsTable from \"./TaskResultsTable\";\nimport DateRangePicker from \"../../components/DateRangePicker\";\nimport { DEFAULT_ROWS_PER_PAGE } from \"../../components/DataTable\";\nimport { useTaskSearch } from \"../../data/task\";\n\nimport { makeStyles } from \"@material-ui/styles\";\nimport clsx from \"clsx\";\nimport executionsStyles from \"./executionsStyles\";\nimport sharedStyles from \"../styles\";\n\nconst useStyles = makeStyles({\n  ...executionsStyles,\n  ...sharedStyles,\n});\n\nconst DEFAULT_SORT = \"startTime:DESC\";\nconst MS_IN_DAY = 86400000;\nconst taskTypeOptions = Array.from(SEARCH_TASK_TYPES_SET.values());\n\nexport default function WorkflowPanel() {\n  const classes = useStyles();\n\n  const [freeText, setFreeText] = useQueryState(\"freeText\", \"\");\n  const [status, setStatus] = useQueryState(\"status\", []);\n  const [taskName, setTaskName] = useQueryState(\"taskName\", []);\n  const [taskId, setTaskId] = useQueryState(\"taskId\", \"\");\n  const [taskType, setTaskType] = useQueryState(\"taskType\", []);\n  const [startFrom, setStartFrom] = useQueryState(\"startFrom\", \"\");\n  const [startTo, setStartTo] = useQueryState(\"startTo\", \"\");\n  const [lookback, setLookback] = useQueryState(\"lookback\", \"\");\n  const [workflowType, setWorkflowType] = useQueryState(\"workflowType\", []);\n\n  const [page, setPage] = useQueryState(\"page\", 1);\n  const [rowsPerPage, setRowsPerPage] = useQueryState(\n    \"rowsPerPage\",\n    DEFAULT_ROWS_PER_PAGE\n  );\n  const [sort, setSort] = useQueryState(\"sort\", DEFAULT_SORT);\n  const [queryFT, setQueryFT] = useState(buildQuery);\n\n  const {\n    data: resultObj,\n    error,\n    isFetching,\n    refetch,\n  } = useTaskSearch({\n    page,\n    rowsPerPage,\n    sort,\n    query: queryFT.query,\n    freeText: queryFT.freeText,\n  });\n\n  function buildQuery() {\n    const clauses = [];\n    if (!_.isEmpty(taskName)) {\n      clauses.push(`taskDefName IN (${taskName.join(\",\")})`);\n    }\n    if (!_.isEmpty(taskType)) {\n      clauses.push(`taskType IN (${taskType.join(\",\")})`);\n    }\n    if (!_.isEmpty(taskId)) {\n      clauses.push(`taskId=\"${taskId}\"`);\n    }\n    if (!_.isEmpty(status)) {\n      clauses.push(`status IN (${status.join(\",\")})`);\n    }\n    if (!_.isEmpty(lookback)) {\n      clauses.push(`updateTime>${new Date().getTime() - lookback * MS_IN_DAY}`);\n    }\n    if (!_.isEmpty(startFrom)) {\n      clauses.push(`updateTime>${new Date(startFrom).getTime()}`);\n    }\n    if (!_.isEmpty(startTo)) {\n      clauses.push(`updateTime<${new Date(startTo).getTime()}`);\n    }\n    if (!_.isEmpty(workflowType)) {\n      clauses.push(`workflowType IN (${workflowType.join(\",\")})`);\n    }\n    return {\n      query: clauses.join(\" AND \"),\n      freeText: _.isEmpty(freeText) ? \"*\" : freeText,\n    };\n  }\n\n  function doSearch() {\n    setPage(1);\n    const oldQueryFT = queryFT;\n    const newQueryFT = buildQuery();\n    setQueryFT(newQueryFT);\n\n    // Only force refetch if query didn't change. Else let react-query detect difference and refetch automatically\n    if (_.isEqual(oldQueryFT, newQueryFT)) {\n      refetch();\n    }\n  }\n\n  const handlePage = (page) => {\n    setPage(page);\n  };\n\n  const handleSort = (changedColumn, direction) => {\n    const sort = `${changedColumn}:${direction.toUpperCase()}`;\n    setPage(1);\n    setSort(sort);\n  };\n\n  const handleRowsPerPage = (rowsPerPage) => {\n    setPage(1);\n    setRowsPerPage(rowsPerPage);\n  };\n\n  const handleLookback = (val) => {\n    setStartFrom(\"\");\n    setStartTo(\"\");\n    setLookback(val);\n  };\n\n  const handleStartFrom = (val) => {\n    setLookback(\"\");\n    setStartFrom(val);\n  };\n\n  const handleStartTo = (val) => {\n    setLookback(\"\");\n    setStartTo(val);\n  };\n\n  return (\n    <div className={clsx([classes.wrapper, classes.padded])}>\n      <Heading level={3} className={classes.heading}>\n        Search Executions\n      </Heading>\n      <Paper className={classes.paper}>\n        <SearchTabs tabIndex={1} />\n        <Grid container spacing={3} className={classes.controls}>\n          <Grid item xs={3}>\n            <TaskNameInput\n              fullWidth\n              onChange={(evt, val) => setTaskName(val)}\n              value={taskName}\n            />\n          </Grid>\n          <Grid item xs={3}>\n            <Input\n              fullWidth\n              label=\"Task ID\"\n              defaultValue={taskId}\n              onBlur={setTaskId}\n              clearable\n            />\n          </Grid>\n          <Grid item xs={3}>\n            <Dropdown\n              label=\"Task Status\"\n              fullWidth\n              options={TASK_STATUSES}\n              multiple\n              onChange={(evt, val) => setStatus(val)}\n              value={status}\n            />\n          </Grid>\n          <Grid item xs={3}>\n            <Dropdown\n              label=\"Task Type (Leave blank to include SIMPLE Tasks)\"\n              fullWidth\n              options={taskTypeOptions}\n              multiple\n              onChange={(evt, val) => setTaskType(val)}\n              value={taskType}\n            />\n          </Grid>\n          <Grid item xs={3}>\n            <WorkflowNameInput\n              fullWidth\n              onChange={(evt, val) => setWorkflowType(val)}\n              value={workflowType}\n            />\n          </Grid>\n          <Grid item xs={4}>\n            <DateRangePicker\n              disabled={!_.isEmpty(lookback)}\n              label=\"Update Time\"\n              from={startFrom}\n              to={startTo}\n              onFromChange={handleStartFrom}\n              onToChange={handleStartTo}\n            />\n          </Grid>\n          <Grid item xs={1}>\n            <Input\n              fullWidth\n              label=\"Lookback (days)\"\n              defaultValue={lookback}\n              onBlur={handleLookback}\n              type=\"number\"\n              clearable\n              disabled={!_.isEmpty(startFrom) || !_.isEmpty(startTo)}\n            />\n          </Grid>\n\n          <Grid item xs={3}>\n            <Input\n              fullWidth\n              label=\"Lucene-syntax Query (Double-quote strings for Free Text)\"\n              defaultValue={freeText}\n              onBlur={setFreeText}\n              clearable\n            />\n          </Grid>\n          <Grid item xs={1}>\n            <FormControl>\n              <InputLabel>&nbsp;</InputLabel>\n              <PrimaryButton onClick={doSearch}>Search</PrimaryButton>\n            </FormControl>\n          </Grid>\n        </Grid>\n      </Paper>\n      <TaskResultsTable\n        resultObj={resultObj}\n        error={error}\n        busy={isFetching}\n        page={page}\n        rowsPerPage={rowsPerPage}\n        sort={sort}\n        setPage={handlePage}\n        setRowsPerPage={handleRowsPerPage}\n        setSort={handleSort}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/executions/WorkflowSearch.jsx",
    "content": "import React, { useState } from \"react\";\nimport _ from \"lodash\";\nimport { FormControl, Grid, InputLabel } from \"@material-ui/core\";\nimport {\n  Paper,\n  Heading,\n  PrimaryButton,\n  Dropdown,\n  Input,\n  WorkflowNameInput,\n} from \"../../components\";\n\nimport { workflowStatuses } from \"../../utils/constants\";\nimport { useQueryState } from \"react-router-use-location-state\";\nimport SearchTabs from \"./SearchTabs\";\nimport ResultsTable from \"./ResultsTable\";\nimport DateRangePicker from \"../../components/DateRangePicker\";\nimport { DEFAULT_ROWS_PER_PAGE } from \"../../components/DataTable\";\nimport { useWorkflowSearch } from \"../../data/workflow\";\n\nimport { makeStyles } from \"@material-ui/styles\";\nimport clsx from \"clsx\";\nimport executionsStyles from \"./executionsStyles\";\nimport sharedStyles from \"../styles\";\n\nconst useStyles = makeStyles({\n  ...executionsStyles,\n  ...sharedStyles,\n});\n\nconst DEFAULT_SORT = \"startTime:DESC\";\nconst MS_IN_DAY = 86400000;\n\nexport default function WorkflowPanel() {\n  const classes = useStyles();\n\n  const [freeText, setFreeText] = useQueryState(\"freeText\", \"\");\n  const [status, setStatus] = useQueryState(\"status\", []);\n  const [workflowType, setWorkflowType] = useQueryState(\"workflowType\", []);\n  const [workflowId, setWorkflowId] = useQueryState(\"workflowId\", \"\");\n  const [startFrom, setStartFrom] = useQueryState(\"startFrom\", \"\");\n  const [startTo, setStartTo] = useQueryState(\"startTo\", \"\");\n  const [lookback, setLookback] = useQueryState(\"lookback\", \"\");\n\n  const [page, setPage] = useQueryState(\"page\", 1);\n  const [rowsPerPage, setRowsPerPage] = useQueryState(\n    \"rowsPerPage\",\n    DEFAULT_ROWS_PER_PAGE\n  );\n  const [sort, setSort] = useQueryState(\"sort\", DEFAULT_SORT);\n  const [queryFT, setQueryFT] = useState(buildQuery);\n\n  const {\n    data: resultObj,\n    error,\n    isFetching,\n    refetch,\n  } = useWorkflowSearch({\n    page,\n    rowsPerPage,\n    sort,\n    query: queryFT.query,\n    freeText: queryFT.freeText,\n  });\n\n  function buildQuery() {\n    const clauses = [];\n    if (!_.isEmpty(workflowType)) {\n      clauses.push(`workflowType IN (${workflowType.join(\",\")})`);\n    }\n    if (!_.isEmpty(workflowId)) {\n      clauses.push(`workflowId=\"${workflowId}\"`);\n    }\n    if (!_.isEmpty(status)) {\n      clauses.push(`status IN (${status.join(\",\")})`);\n    }\n    if (!_.isEmpty(lookback)) {\n      clauses.push(`startTime>${new Date().getTime() - lookback * MS_IN_DAY}`);\n    }\n    if (!_.isEmpty(startFrom)) {\n      clauses.push(`startTime>${new Date(startFrom).getTime()}`);\n    }\n    if (!_.isEmpty(startTo)) {\n      clauses.push(`startTime<${new Date(startTo).getTime()}`);\n    }\n\n    return {\n      query: clauses.join(\" AND \"),\n      freeText: _.isEmpty(freeText) ? \"*\" : freeText,\n    };\n  }\n\n  function doSearch() {\n    setPage(1);\n    const oldQueryFT = queryFT;\n    const newQueryFT = buildQuery();\n    setQueryFT(newQueryFT);\n\n    // Only force refetch if query didn't change. Else let react-query detect difference and refetch automatically\n    if (_.isEqual(oldQueryFT, newQueryFT)) {\n      refetch();\n    }\n  }\n\n  const handlePage = (page) => {\n    setPage(page);\n  };\n\n  const handleSort = (changedColumn, direction) => {\n    const sort = `${changedColumn}:${direction.toUpperCase()}`;\n    setPage(1);\n    setSort(sort);\n  };\n\n  const handleRowsPerPage = (rowsPerPage) => {\n    setPage(1);\n    setRowsPerPage(rowsPerPage);\n  };\n\n  const handleLookback = (val) => {\n    setStartFrom(\"\");\n    setStartTo(\"\");\n    setLookback(val);\n  };\n\n  const handleStartFrom = (val) => {\n    setLookback(\"\");\n    setStartFrom(val);\n  };\n\n  const handleStartTo = (val) => {\n    setLookback(\"\");\n    setStartTo(val);\n  };\n\n  return (\n    <div className={clsx([classes.wrapper, classes.padded])}>\n      <Heading level={3} className={classes.heading}>\n        Search Executions\n      </Heading>\n      <Paper className={classes.paper}>\n        <SearchTabs tabIndex={0} />\n        <Grid container spacing={3} className={classes.controls}>\n          <Grid item xs={5}>\n            <WorkflowNameInput\n              fullWidth\n              label=\"Workflow Name\"\n              onChange={(evt, val) => setWorkflowType(val)}\n              value={workflowType}\n            />\n          </Grid>\n          <Grid item xs={3}>\n            <Input\n              fullWidth\n              label=\"Workflow ID\"\n              defaultValue={workflowId}\n              onBlur={setWorkflowId}\n              clearable\n            />\n          </Grid>\n          <Grid item xs={4}>\n            <Dropdown\n              label=\"Status\"\n              fullWidth\n              options={workflowStatuses}\n              multiple\n              onChange={(evt, val) => setStatus(val)}\n              value={status}\n            />\n          </Grid>\n          <Grid item xs={4}>\n            <DateRangePicker\n              disabled={!_.isEmpty(lookback)}\n              label=\"Start Time\"\n              from={startFrom}\n              to={startTo}\n              onFromChange={handleStartFrom}\n              onToChange={handleStartTo}\n            />\n          </Grid>\n          <Grid item xs={1}>\n            <Input\n              fullWidth\n              label=\"Lookback (days)\"\n              defaultValue={lookback}\n              onBlur={handleLookback}\n              type=\"number\"\n              clearable\n              disabled={!_.isEmpty(startFrom) || !_.isEmpty(startTo)}\n            />\n          </Grid>\n\n          <Grid item xs={6}>\n            <Input\n              fullWidth\n              label=\"Lucene-syntax Query (Double-quote strings for Free Text Search)\"\n              defaultValue={freeText}\n              onBlur={setFreeText}\n              clearable\n            />\n          </Grid>\n          <Grid item xs={1}>\n            <FormControl>\n              <InputLabel>&nbsp;</InputLabel>\n              <PrimaryButton onClick={doSearch}>Search</PrimaryButton>\n            </FormControl>\n          </Grid>\n        </Grid>\n      </Paper>\n      <ResultsTable\n        resultObj={resultObj}\n        error={error}\n        busy={isFetching}\n        page={page}\n        rowsPerPage={rowsPerPage}\n        sort={sort}\n        setPage={handlePage}\n        setRowsPerPage={handleRowsPerPage}\n        setSort={handleSort}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/executions/executionsStyles.js",
    "content": "export default {\n  clickSearch: {\n    width: \"100%\",\n    padding: 30,\n    display: \"block\",\n    textAlign: \"center\",\n  },\n  paper: {\n    marginBottom: 30,\n  },\n  heading: {\n    marginBottom: 30,\n  },\n  controls: {\n    padding: 15,\n  },\n  popupIndicator: {\n    backgroundColor: \"red\",\n  },\n  banner: {\n    marginBottom: 15,\n  },\n};\n"
  },
  {
    "path": "ui/src/pages/kitchensink/DataTableDemo.jsx",
    "content": "import React from \"react\";\nimport data from \"./sampleMovieData\";\nimport { DataTable } from \"../../components\";\n\nexport default () => {\n  const columns = [\n    { name: \"title\" },\n    { name: \"director\" },\n    { name: \"year\" },\n    { name: \"plot\", grow: 0.5 },\n  ];\n\n  return (\n    <>\n      <DataTable title=\"Movie List\" columns={columns} data={data} />\n    </>\n  );\n};\n"
  },
  {
    "path": "ui/src/pages/kitchensink/DiagramTest.jsx",
    "content": "import React, { Component } from \"react\";\nimport WorkflowDAG from \"../../components/diagram/WorkflowDAG\";\nimport WorkflowGraph from \"../../components/diagram/WorkflowGraph\";\nconst workflowDef = {\n  tasks: [\n    {\n      name: \"fork_join\",\n      taskReferenceName: \"fork\",\n      type: \"FORK_JOIN\",\n      forkTasks: [\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkChild_grp1a\",\n          },\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkChild_grp1b\",\n          },\n        ],\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkchild_grp2\",\n          },\n        ],\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkchild_grp3\",\n          },\n        ],\n        [\n          {\n            name: \"forkChild\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"forkchild_grp4\",\n          },\n        ],\n      ],\n    },\n    {\n      name: \"join\",\n      taskReferenceName: \"join\",\n      type: \"JOIN\",\n      joinOn: [\"forkChild_par1\", \"forkChild_par2\", \"forkChild_ser1\"],\n    },\n\n    {\n      name: \"decision\",\n      taskReferenceName: \"decision\",\n      type: \"DECISION\",\n      decisionCases: [\n        [\n          {\n            name: \"simple_task\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"completed\",\n          },\n        ],\n        [\n          {\n            name: \"simple_task\",\n            type: \"SIMPLE\",\n            taskReferenceName: \"failed\",\n          },\n        ],\n      ],\n    },\n    {\n      name: \"exclusive_join\",\n      taskReferenceName: \"exclusiveJoin\",\n      type: \"EXCLUSIVE_JOIN\",\n      joinOn: [\"completed\", \"failed\"],\n      defaultExclusiveJoinTask: [\"completed\"],\n    },\n    {\n      name: \"subworkflow\",\n      taskReferenceName: \"subworkflow\",\n      type: \"SUB_WORKFLOW\",\n      subworkflowParam: { name: \"foo\" },\n    },\n    {\n      name: \"dynamic_fork\",\n      taskReferenceName: \"dynamic_fork\",\n      type: \"FORK_JOIN_DYNAMIC\",\n      dynamicForkTasksParam: \"dynamicTasks\",\n      dynamicForkTasksInputParamName: \"dynamicTasksInput\",\n    },\n    {\n      name: \"join\",\n      taskReferenceName: \"dynamic_join\",\n      type: \"JOIN\",\n    },\n  ],\n};\n\nclass DiagramTest extends Component {\n  constructor() {\n    super();\n    this.state = {\n      dag: new WorkflowDAG(null, workflowDef),\n    };\n  }\n\n  render() {\n    const { dag } = this.state;\n    return (\n      <div style={{ display: \"flex\", flexDirection: \"row\" }}>\n        <WorkflowGraph dag={dag} />\n      </div>\n    );\n  }\n}\n\nexport default DiagramTest;\n"
  },
  {
    "path": "ui/src/pages/kitchensink/Dropdown.jsx",
    "content": "import { useState } from \"react\";\nimport { Paper, Heading, Select } from \"../../components\";\nimport { MenuItem } from \"@material-ui/core\";\nimport top100Films from \"./sampleMovieData\";\nimport Dropdown from \"../../components/Dropdown\";\n\nexport default function () {\n  const [value, setValue] = useState(10);\n  const [dropdownValue, setDropdownValue] = useState();\n  const [dropdownValues, setDropdownValues] = useState([]);\n\n  return (\n    <Paper style={{ padding: 15 }}>\n      <Heading level={3} gutterBottom>\n        Select\n      </Heading>\n\n      <Select\n        style={{ marginBottom: 10 }}\n        value={value}\n        onChange={(evt) => setValue(evt.target.value)}\n      >\n        <MenuItem value={10}>Ten</MenuItem>\n        <MenuItem value={20}>Twenty</MenuItem>\n        <MenuItem value={30}>Thirty</MenuItem>\n      </Select>\n\n      <Select\n        style={{ marginBottom: 20 }}\n        label=\"With Label\"\n        value={value}\n        onChange={(evt) => setValue(evt.target.value)}\n      >\n        <MenuItem value={10}>Ten</MenuItem>\n        <MenuItem value={20}>Twenty</MenuItem>\n        <MenuItem value={30}>Thirty</MenuItem>\n      </Select>\n\n      <Select\n        fullWidth\n        style={{ marginBottom: 20 }}\n        label=\"Fullwidth\"\n        value={value}\n        onChange={(evt) => setValue(evt.target.value)}\n      >\n        <MenuItem value={10}>Ten</MenuItem>\n        <MenuItem value={20}>Twenty</MenuItem>\n        <MenuItem value={30}>Thirty</MenuItem>\n      </Select>\n\n      <Dropdown\n        style={{ marginBottom: 20, width: 300 }}\n        label=\"Autocomplete\"\n        disableClearable\n        options={top100Films}\n        value={dropdownValue}\n        getOptionLabel={(option) => option.title}\n        onChange={(e, v) => setDropdownValue(v)}\n      />\n\n      <Dropdown\n        style={{ marginBottom: 20, width: 300 }}\n        label=\"Autocomplete Loading\"\n        disableClearable\n        options={top100Films}\n        value={dropdownValue}\n        getOptionLabel={(option) => option.title}\n        loading\n      />\n\n      <Dropdown\n        style={{ marginBottom: 20, width: 300 }}\n        label=\"Autocomplete Disabled\"\n        disabled\n        options={top100Films}\n        value={dropdownValue}\n        getOptionLabel={(option) => option.title}\n        onChange={(e, v) => setDropdownValue(v)}\n      />\n\n      <Dropdown\n        style={{ marginBottom: 20, width: 300 }}\n        label=\"Autocomplete Clearable\"\n        options={top100Films}\n        value={dropdownValue}\n        getOptionLabel={(option) => option.title}\n        onChange={(e, v) => setDropdownValue(v)}\n      />\n\n      <Dropdown\n        fullWidth\n        debug\n        style={{ marginBottom: 20 }}\n        label=\"Autocomplete Fullwidth\"\n        disableClearable\n        options={top100Films}\n        value={dropdownValue}\n        getOptionLabel={(option) => option.title}\n        onChange={(e, v) => setDropdownValue(v)}\n      />\n\n      <Dropdown\n        multiple\n        label=\"Multiple Pills\"\n        options={top100Films}\n        getOptionLabel={(option) => option.title}\n        style={{ width: 500 }}\n        value={dropdownValues}\n        onChange={(e, v) => setDropdownValues(v)}\n      />\n    </Paper>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/kitchensink/EnhancedTable.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport clsx from \"clsx\";\nimport { lighten, makeStyles } from \"@material-ui/core/styles\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableContainer,\n  TableHead,\n  TablePagination,\n  TableRow,\n  TableSortLabel,\n  Toolbar,\n  Checkbox,\n  FormControlLabel,\n  Switch,\n} from \"@material-ui/core\";\nimport { Paper, Text, Heading } from \"../../components\";\n\nfunction createData(name, calories, fat, carbs, protein) {\n  return { name, calories, fat, carbs, protein };\n}\n\nconst rows = [\n  createData(\"Cupcake\", 305, 3.7, 67, 4.3),\n  createData(\"Donut\", 452, 25.0, 51, 4.9),\n  createData(\"Eclair\", 262, 16.0, 24, 6.0),\n  createData(\"Frozen yoghurt\", 159, 6.0, 24, 4.0),\n  createData(\"Gingerbread\", 356, 16.0, 49, 3.9),\n  createData(\"Honeycomb\", 408, 3.2, 87, 6.5),\n  createData(\"Ice cream sandwich\", 237, 9.0, 37, 4.3),\n  createData(\"Jelly Bean\", 375, 0.0, 94, 0.0),\n  createData(\"KitKat\", 518, 26.0, 65, 7.0),\n  createData(\"Lollipop\", 392, 0.2, 98, 0.0),\n  createData(\"Marshmallow\", 318, 0, 81, 2.0),\n  createData(\"Nougat\", 360, 19.0, 9, 37.0),\n  createData(\"Oreo\", 437, 18.0, 63, 4.0),\n];\n\nfunction descendingComparator(a, b, orderBy) {\n  if (b[orderBy] < a[orderBy]) {\n    return -1;\n  }\n  if (b[orderBy] > a[orderBy]) {\n    return 1;\n  }\n  return 0;\n}\n\nfunction getComparator(order, orderBy) {\n  return order === \"desc\"\n    ? (a, b) => descendingComparator(a, b, orderBy)\n    : (a, b) => -descendingComparator(a, b, orderBy);\n}\n\nfunction stableSort(array, comparator) {\n  const stabilizedThis = array.map((el, index) => [el, index]);\n  stabilizedThis.sort((a, b) => {\n    const order = comparator(a[0], b[0]);\n    if (order !== 0) return order;\n    return a[1] - b[1];\n  });\n  return stabilizedThis.map((el) => el[0]);\n}\n\nconst headCells = [\n  {\n    id: \"name\",\n    numeric: false,\n    disablePadding: true,\n    label: \"Dessert (100g serving)\",\n  },\n  { id: \"calories\", numeric: true, disablePadding: false, label: \"Calories\" },\n  { id: \"fat\", numeric: true, disablePadding: false, label: \"Fat (g)\" },\n  { id: \"carbs\", numeric: true, disablePadding: false, label: \"Carbs (g)\" },\n  { id: \"protein\", numeric: true, disablePadding: false, label: \"Protein (g)\" },\n];\n\nfunction EnhancedTableHead(props) {\n  const {\n    classes,\n    onSelectAllClick,\n    order,\n    orderBy,\n    numSelected,\n    rowCount,\n    onRequestSort,\n  } = props;\n  const createSortHandler = (property) => (event) => {\n    onRequestSort(event, property);\n  };\n\n  return (\n    <TableHead>\n      <TableRow>\n        <TableCell padding=\"checkbox\">\n          <Checkbox\n            indeterminate={numSelected > 0 && numSelected < rowCount}\n            checked={rowCount > 0 && numSelected === rowCount}\n            onChange={onSelectAllClick}\n            inputProps={{ \"aria-label\": \"select all desserts\" }}\n          />\n        </TableCell>\n        {headCells.map((headCell) => (\n          <TableCell\n            key={headCell.id}\n            align={headCell.numeric ? \"right\" : \"left\"}\n            padding={headCell.disablePadding ? \"none\" : \"normal\"}\n            sortDirection={orderBy === headCell.id ? order : false}\n          >\n            <TableSortLabel\n              active={orderBy === headCell.id}\n              direction={orderBy === headCell.id ? order : \"asc\"}\n              onClick={createSortHandler(headCell.id)}\n            >\n              {headCell.label}\n              {orderBy === headCell.id ? (\n                <span className={classes.visuallyHidden}>\n                  {order === \"desc\" ? \"sorted descending\" : \"sorted ascending\"}\n                </span>\n              ) : null}\n            </TableSortLabel>\n          </TableCell>\n        ))}\n      </TableRow>\n    </TableHead>\n  );\n}\n\nEnhancedTableHead.propTypes = {\n  classes: PropTypes.object.isRequired,\n  numSelected: PropTypes.number.isRequired,\n  onRequestSort: PropTypes.func.isRequired,\n  onSelectAllClick: PropTypes.func.isRequired,\n  order: PropTypes.oneOf([\"asc\", \"desc\"]).isRequired,\n  orderBy: PropTypes.string.isRequired,\n  rowCount: PropTypes.number.isRequired,\n};\n\nconst useToolbarStyles = makeStyles((theme) => ({\n  root: {\n    paddingLeft: theme.spacing(2),\n    paddingRight: theme.spacing(1),\n  },\n  highlight:\n    theme.palette.type === \"light\"\n      ? {\n          color: theme.palette.secondary.main,\n          backgroundColor: lighten(theme.palette.secondary.light, 0.85),\n        }\n      : {\n          color: theme.palette.text.primary,\n          backgroundColor: theme.palette.secondary.dark,\n        },\n  title: {\n    flex: \"1 1 100%\",\n  },\n}));\n\nconst EnhancedTableToolbar = (props) => {\n  const classes = useToolbarStyles();\n  const { numSelected } = props;\n\n  return (\n    <Toolbar\n      className={clsx(classes.root, {\n        [classes.highlight]: numSelected > 0,\n      })}\n    >\n      {numSelected > 0 ? <Text>{numSelected} selected</Text> : null}\n    </Toolbar>\n  );\n};\n\nEnhancedTableToolbar.propTypes = {\n  numSelected: PropTypes.number.isRequired,\n};\n\nconst useStyles = makeStyles((theme) => ({\n  root: {\n    width: \"100%\",\n  },\n  paper: {\n    width: \"100%\",\n    marginBottom: theme.spacing(2),\n  },\n  table: {\n    minWidth: 750,\n  },\n  visuallyHidden: {\n    border: 0,\n    clip: \"rect(0 0 0 0)\",\n    height: 1,\n    margin: -1,\n    overflow: \"hidden\",\n    padding: 0,\n    position: \"absolute\",\n    top: 20,\n    width: 1,\n  },\n}));\n\nexport default function EnhancedTable() {\n  const classes = useStyles();\n  const [order, setOrder] = React.useState(\"asc\");\n  const [orderBy, setOrderBy] = React.useState(\"calories\");\n  const [selected, setSelected] = React.useState([]);\n  const [page, setPage] = React.useState(0);\n  const [dense, setDense] = React.useState(false);\n  const [rowsPerPage, setRowsPerPage] = React.useState(5);\n\n  const handleRequestSort = (event, property) => {\n    const isAsc = orderBy === property && order === \"asc\";\n    setOrder(isAsc ? \"desc\" : \"asc\");\n    setOrderBy(property);\n  };\n\n  const handleSelectAllClick = (event) => {\n    if (event.target.checked) {\n      const newSelecteds = rows.map((n) => n.name);\n      setSelected(newSelecteds);\n      return;\n    }\n    setSelected([]);\n  };\n\n  const handleClick = (event, name) => {\n    const selectedIndex = selected.indexOf(name);\n    let newSelected = [];\n\n    if (selectedIndex === -1) {\n      newSelected = newSelected.concat(selected, name);\n    } else if (selectedIndex === 0) {\n      newSelected = newSelected.concat(selected.slice(1));\n    } else if (selectedIndex === selected.length - 1) {\n      newSelected = newSelected.concat(selected.slice(0, -1));\n    } else if (selectedIndex > 0) {\n      newSelected = newSelected.concat(\n        selected.slice(0, selectedIndex),\n        selected.slice(selectedIndex + 1)\n      );\n    }\n\n    setSelected(newSelected);\n  };\n\n  const handleChangePage = (event, newPage) => {\n    setPage(newPage);\n  };\n\n  const handleChangeRowsPerPage = (event) => {\n    setRowsPerPage(parseInt(event.target.value, 10));\n    setPage(0);\n  };\n\n  const handleChangeDense = (event) => {\n    setDense(event.target.checked);\n  };\n\n  const isSelected = (name) => selected.indexOf(name) !== -1;\n\n  const emptyRows =\n    rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);\n\n  return (\n    <div className={classes.root}>\n      <Paper className={classes.paper}>\n        <Heading level={3} style={{ padding: 15 }}>\n          Native MUI Table\n        </Heading>\n        <EnhancedTableToolbar numSelected={selected.length} />\n        <TableContainer>\n          <Table className={classes.table} size={dense ? \"small\" : \"medium\"}>\n            <EnhancedTableHead\n              classes={classes}\n              numSelected={selected.length}\n              order={order}\n              orderBy={orderBy}\n              onSelectAllClick={handleSelectAllClick}\n              onRequestSort={handleRequestSort}\n              rowCount={rows.length}\n            />\n            <TableBody>\n              {stableSort(rows, getComparator(order, orderBy))\n                .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)\n                .map((row, index) => {\n                  const isItemSelected = isSelected(row.name);\n                  const labelId = `enhanced-table-checkbox-${index}`;\n\n                  return (\n                    <TableRow\n                      hover\n                      onClick={(event) => handleClick(event, row.name)}\n                      role=\"checkbox\"\n                      aria-checked={isItemSelected}\n                      tabIndex={-1}\n                      key={row.name}\n                      selected={isItemSelected}\n                    >\n                      <TableCell padding=\"checkbox\">\n                        <Checkbox\n                          checked={isItemSelected}\n                          inputProps={{ \"aria-labelledby\": labelId }}\n                        />\n                      </TableCell>\n                      <TableCell\n                        component=\"th\"\n                        id={labelId}\n                        scope=\"row\"\n                        padding=\"none\"\n                      >\n                        {row.name}\n                      </TableCell>\n                      <TableCell align=\"right\">{row.calories}</TableCell>\n                      <TableCell align=\"right\">{row.fat}</TableCell>\n                      <TableCell align=\"right\">{row.carbs}</TableCell>\n                      <TableCell align=\"right\">{row.protein}</TableCell>\n                    </TableRow>\n                  );\n                })}\n              {emptyRows > 0 && (\n                <TableRow style={{ height: (dense ? 33 : 53) * emptyRows }}>\n                  <TableCell colSpan={6} />\n                </TableRow>\n              )}\n            </TableBody>\n          </Table>\n        </TableContainer>\n        <TablePagination\n          rowsPerPageOptions={[5, 10, 25, 100]}\n          component=\"div\"\n          count={rows.length}\n          rowsPerPage={rowsPerPage}\n          page={page}\n          onPageChange={handleChangePage}\n          onRowsPerPageChange={handleChangeRowsPerPage}\n          SelectProps={{ native: false }}\n        />\n        <FormControlLabel\n          style={{ margin: 20 }}\n          control={<Switch checked={dense} onChange={handleChangeDense} />}\n          label=\"Dense padding\"\n        />\n      </Paper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/kitchensink/Examples.jsx",
    "content": "export default function Examples() {\n  return null;\n}\n"
  },
  {
    "path": "ui/src/pages/kitchensink/Gantt.jsx",
    "content": "import React, { Component } from \"react\";\nimport Timeline from \"react-vis-timeline-2\";\nimport moment from \"moment\";\nimport { Paper } from \"../../components\";\n\nfunction createItem(id, startTime) {\n  return {\n    id: id,\n    group: id,\n    content: \"item \" + id,\n    start: startTime,\n    end: startTime.clone().add(1, \"minute\"),\n  };\n}\n\nconst initialGroups = [],\n  initialItems = [];\nconst now = moment().minutes(0).seconds(0).milliseconds(0);\nconst itemCount = 20;\nfor (let i = 0; i < itemCount; i++) {\n  const start = now.clone().add(Math.random() * 200, \"minutes\");\n  initialGroups.push({ id: i, content: \"group \" + i });\n  initialItems.push(createItem(i, start));\n}\n\nexport default class Gantt extends Component {\n  timelineRef = React.createRef();\n\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      selectedIds: [],\n    };\n  }\n\n  /*\n\tonAddItem = () => {\n\t\tvar nextId = this.timelineRef.current.items.length + 1;\n\t\tconst group = Math.floor(Math.random() * groupCount);\n\t\tthis.timelineRef.current.items.add(createItem(nextId, group, names[group], moment()));\n\t\tthis.timelineRef.current.timeline.fit();\n\t};\n  */\n\n  onFit = () => {\n    this.timelineRef.current.timeline.fit();\n  };\n\n  render() {\n    return (\n      <Paper style={{ padding: 15 }}>\n        <p className=\"header\">This example demonstrate using groups.</p>\n        <button onClick={this.onAddItem}>Add Item</button>\n        <button onClick={this.onFit}>Fit Screen</button>\n        <div className=\"timeline-container\">\n          <Timeline\n            ref={this.timelineRef}\n            clickHandler={this.clickHandler}\n            selection={this.state.selectedIds}\n            initialGroups={initialGroups}\n            initialItems={initialItems}\n            options={{\n              orientation: \"top\",\n              zoomKey: \"ctrlKey\",\n              type: \"range\",\n            }}\n          />\n        </div>\n        <br />\n      </Paper>\n    );\n  }\n\n  clickHandler = () => {\n    const { group } = this.props;\n    var items = this.timelineRef.current.items.get();\n    const selectedIds = items\n      .filter((item) => item.group === group)\n      .map((item) => item.id);\n    this.setState({\n      selectedIds,\n    });\n  };\n}\n"
  },
  {
    "path": "ui/src/pages/kitchensink/KitchenSink.jsx",
    "content": "import React, { useState } from \"react\";\nimport { Form, Formik } from \"formik\";\nimport {\n  Checkbox,\n  Grid,\n  Switch,\n  MenuItem,\n  InputLabel,\n  FormControl,\n  IconButton,\n  Toolbar,\n} from \"@material-ui/core\";\nimport DeleteIcon from \"@material-ui/icons/Delete\";\nimport {\n  PrimaryButton,\n  SecondaryButton,\n  TertiaryButton,\n  ButtonGroup,\n  SplitButton,\n  DropdownButton,\n  Paper,\n  Tab,\n  Tabs,\n  NavLink,\n  Heading,\n  Text,\n  Input,\n  Select,\n  Button,\n} from \"../../components\";\nimport ZoomInIcon from \"@material-ui/icons/ZoomIn\";\nimport * as Yup from \"yup\";\nimport EnhancedTable from \"./EnhancedTable\";\nimport DataTableDemo from \"./DataTableDemo\";\n\nimport sharedStyles from \"../styles\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport clsx from \"clsx\";\nimport FormikInput from \"../../components/formik/FormikInput\";\nimport FormikJsonInput from \"../../components/formik/FormikJsonInput\";\nimport Dropdown from \"./Dropdown\";\n\nconst useStyles = makeStyles(sharedStyles);\n\nexport default function KitchenSink() {\n  const classes = useStyles();\n  return (\n    <div className={clsx([classes.wrapper, classes.padded])}>\n      <Grid container spacing={5}>\n        <Grid item xs={12}>\n          <p>This is a Hawkins-like theme based on vanilla Material-UI.</p>\n        </Grid>\n        <Grid item xs={12}>\n          <FormikSection />\n        </Grid>\n        <Grid item xs={12}>\n          <NavLink path=\"/kitchen/gantt\">Gantt</NavLink>\n        </Grid>\n        <Grid item xs={12}>\n          <HeadingSection />\n        </Grid>\n        <Grid item xs={12}>\n          <TabsSection />\n        </Grid>\n        <Grid item xs={12}>\n          <Buttons />\n        </Grid>\n        <Grid item xs={12}>\n          <Toggles />\n        </Grid>\n        <Grid item xs={12}>\n          <Checkboxes />\n        </Grid>\n        <Grid item xs={12}>\n          <Inputs />\n        </Grid>\n        <Grid item xs={12}>\n          <Dropdown />\n        </Grid>\n        <Grid item xs={12}>\n          <ToolbarSection />\n        </Grid>\n        <Grid item xs={12}>\n          <EnhancedTable />\n        </Grid>\n        <Grid item xs={12}>\n          <DataTableDemo />\n        </Grid>\n      </Grid>\n    </div>\n  );\n}\n\nconst FormikSection = () => {\n  const [formState, setFormState] = useState();\n  return (\n    <Paper padded>\n      <Heading level={3}>Formik</Heading>\n      <Formik\n        initialValues={{\n          firstName: \"\",\n          lastName: \"\",\n          description: \"\",\n        }}\n        validationSchema={Yup.object({\n          firstName: Yup.string()\n            .min(15, \"Must be 15 characters or more\")\n            .required(\"Required\"),\n        })}\n        onSubmit={(values) => setFormState(values)}\n      >\n        <Form>\n          <FormikInput label=\"First Name\" name=\"firstName\" />\n          <FormikInput label=\"Last Name\" name=\"lastName\" />\n          <FormikJsonInput label=\"Description\" name=\"description\" />\n          <Button type=\"submit\">Submit</Button>\n        </Form>\n      </Formik>\n      <code>\n        <pre>{JSON.stringify(formState)}</pre>\n      </code>\n    </Paper>\n  );\n};\n\nconst ToolbarSection = () => {\n  return (\n    <Paper padded>\n      <Heading level={3} gutterBottom>\n        Toolbar\n      </Heading>\n\n      <Toolbar>\n        <Text>Label</Text>\n        <Select value={10}>\n          <MenuItem value={10}>Ten</MenuItem>\n          <MenuItem value={20}>Twenty</MenuItem>\n          <MenuItem value={30}>Thirty</MenuItem>\n        </Select>{\" \"}\n        <Button>Primary</Button>\n        <IconButton>\n          <ZoomInIcon />\n        </IconButton>\n      </Toolbar>\n    </Paper>\n  );\n};\n\nconst HeadingSection = () => {\n  return (\n    <Paper padded>\n      <Heading level={0}>Heading Level Zero</Heading>\n      <Heading level={1}>Heading Level One</Heading>\n      <Heading level={2}>Heading Level Two</Heading>\n      <Heading level={3}>Heading Level Three</Heading>\n      <Heading level={4}>Heading Level Four</Heading>\n      <Heading level={5}>Heading Level Five</Heading>\n      <Text level={0}>Text Level Zero</Text>\n      <Text level={1}>Text Level One</Text>\n      <Text level={2}>Text Level Two</Text>\n\n      <div>Default &lt;div&gt;</div>\n      <div>Default &lt;p&gt;</div>\n    </Paper>\n  );\n};\n\nconst TabsSection = () => {\n  const [tabIndex, setTabIndex] = useState(0);\n  return (\n    <Paper padded>\n      <Heading level={3} gutterBottom>\n        Tabs\n      </Heading>\n      <Heading level={2} gutterBottom>\n        Page Level\n      </Heading>\n      <Heading level={1} gutterBottom>\n        Full Width\n      </Heading>\n      <Paper variant=\"outlined\" style={{ width: 800, marginBottom: 30 }}>\n        <Tabs value={tabIndex} variant=\"fullWidth\">\n          <Tab label=\"Tab A\" onClick={() => setTabIndex(0)} />\n          <Tab label=\"Tab B\" onClick={() => setTabIndex(1)} />\n          <Tab label=\"Tab C\" onClick={() => setTabIndex(2)} />\n          <Tab label=\"Tab D\" onClick={() => setTabIndex(3)} />\n        </Tabs>\n        <div style={{ padding: 15 }}>Tab content {tabIndex}</div>\n      </Paper>\n\n      <Heading level={1} gutterBottom>\n        Fixed Width\n      </Heading>\n      <Paper variant=\"outlined\" style={{ width: 800, marginBottom: 30 }}>\n        <Tabs value={tabIndex}>\n          <Tab label=\"Tab A\" onClick={() => setTabIndex(0)} />\n          <Tab label=\"Tab B\" onClick={() => setTabIndex(1)} />\n          <Tab label=\"Tab C\" onClick={() => setTabIndex(2)} />\n          <Tab label=\"Tab D\" onClick={() => setTabIndex(3)} />\n        </Tabs>\n        <div style={{ padding: 15 }}>Tab content {tabIndex}</div>\n      </Paper>\n\n      <Heading level={2} gutterBottom>\n        Contextual\n      </Heading>\n\n      <Heading level={1} gutterBottom>\n        Full Width\n      </Heading>\n      <Paper variant=\"outlined\" style={{ width: 500, marginBottom: 30 }}>\n        <Tabs value={tabIndex} variant=\"fullWidth\" contextual>\n          <Tab label=\"Tab A\" onClick={() => setTabIndex(0)} />\n          <Tab label=\"Tab B\" onClick={() => setTabIndex(1)} />\n          <Tab label=\"Tab C\" onClick={() => setTabIndex(2)} />\n          <Tab label=\"Tab D\" onClick={() => setTabIndex(3)} />\n        </Tabs>\n        <div style={{ padding: 15 }}>Tab content {tabIndex}</div>\n      </Paper>\n      <Heading level={1} gutterBottom>\n        Fixed Width\n      </Heading>\n\n      <Paper variant=\"outlined\" style={{ width: 800 }}>\n        <Tabs value={tabIndex} contextual>\n          <Tab label=\"Tab A\" onClick={() => setTabIndex(0)} />\n          <Tab label=\"Tab B\" onClick={() => setTabIndex(1)} />\n          <Tab label=\"Tab C\" onClick={() => setTabIndex(2)} />\n          <Tab label=\"Tab D\" onClick={() => setTabIndex(3)} />\n        </Tabs>\n        <div style={{ padding: 15 }}>Tab content {tabIndex}</div>\n      </Paper>\n    </Paper>\n  );\n};\n\nconst Buttons = () => (\n  <Paper style={{ padding: 15 }}>\n    <Heading level={3} gutterBottom>\n      Button\n    </Heading>\n\n    <Grid container spacing={4}>\n      <Grid item>\n        <PrimaryButton>Primary</PrimaryButton>\n      </Grid>\n      <Grid item>\n        <SecondaryButton>Secondary</SecondaryButton>\n      </Grid>\n      <Grid item>\n        <TertiaryButton>Tertiary</TertiaryButton>\n      </Grid>\n      <Grid item>\n        <ButtonGroup\n          options={[{ label: \"One\" }, { label: \"Two\" }, { label: \"Three\" }]}\n        />\n      </Grid>\n      <Grid item>\n        <SplitButton\n          options={[\n            {\n              label: \"Create a merge commit\",\n              handler: () => alert(\"you clicked 1\"),\n            },\n            {\n              label: \"Squash and merge\",\n              handler: () => alert(\"you clicked 2\"),\n            },\n            {\n              label: \"Rebase and merge\",\n              handler: () => alert(\"you clicked 3\"),\n            },\n          ]}\n          onPrimaryClick={() => alert(\"main button\")}\n        >\n          Split Button\n        </SplitButton>\n      </Grid>\n      <Grid item>\n        <DropdownButton\n          options={[\n            {\n              label: \"Create a merge commit\",\n              handler: () => alert(\"you clicked 1\"),\n            },\n            {\n              label: \"Squash and merge\",\n              handler: () => alert(\"you clicked 2\"),\n            },\n            {\n              label: \"Rebase and merge\",\n              handler: () => alert(\"you clicked 3\"),\n            },\n          ]}\n        >\n          Dropdown Button\n        </DropdownButton>\n      </Grid>\n      <Grid item>\n        <IconButton>\n          <DeleteIcon />\n        </IconButton>\n      </Grid>\n      <Grid item xs={12}>\n        <ButtonGroup\n          label=\"Button Group with Label\"\n          options={[{ label: \"One\" }, { label: \"Two\" }, { label: \"Three\" }]}\n        />\n      </Grid>\n    </Grid>\n  </Paper>\n);\n\nconst Toggles = () => {\n  const [toggleChecked, setToggleChecked] = useState(false);\n\n  return (\n    <Paper style={{ padding: 15 }}>\n      <Heading level={3} gutterBottom>\n        Toggle\n      </Heading>\n      <Switch\n        checked={toggleChecked}\n        onChange={() => setToggleChecked(!toggleChecked)}\n        color=\"primary\"\n      />\n    </Paper>\n  );\n};\n\nconst Checkboxes = () => {\n  const [toggleChecked, setToggleChecked] = useState(false);\n\n  return (\n    <Paper style={{ padding: 15 }}>\n      <Heading level={3} gutterBottom>\n        Checkbox\n      </Heading>\n      <Checkbox\n        checked={toggleChecked}\n        onChange={() => setToggleChecked(!toggleChecked)}\n        color=\"primary\"\n      />\n    </Paper>\n  );\n};\n\nconst Inputs = () => (\n  <Paper style={{ padding: 15 }}>\n    <Heading level={3} gutterBottom>\n      Input\n    </Heading>\n\n    <Input\n      label=\"Input Label via label attribute\"\n      style={{ marginBottom: 20 }}\n    />\n\n    <Input label=\"Disabled\" disabled style={{ marginBottom: 20 }} />\n\n    <Input label=\"Fullwidth\" fullWidth style={{ marginBottom: 20 }} />\n\n    <Input label=\"Clearable\" clearable style={{ marginBottom: 20 }} />\n\n    <FormControl style={{ display: \"block\", marginBottom: 20 }}>\n      <InputLabel>Input Label via FormControl/InputLabel</InputLabel>\n      <Input />\n    </FormControl>\n\n    <Input label=\"DateTime\" type=\"datetime-local\" />\n  </Paper>\n);\n"
  },
  {
    "path": "ui/src/pages/kitchensink/sampleMovieData.js",
    "content": "// Author https://github.com/yegor-sytnyk/movies-list\n\nexport default [\n  {\n    id: 1,\n    title: \"Beetlejuice\",\n    year: \"1988\",\n    runtime: \"92\",\n    genres: [\"Comedy\", \"Fantasy\"],\n    director: \"Tim Burton\",\n    actors: \"Alec Baldwin, Geena Davis, Annie McEnroe, Maurice Page\",\n    plot: 'A couple of recently deceased ghosts contract the services of a \"bio-exorcist\" in order to remove the obnoxious new owners of their house.',\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTUwODE3MDE0MV5BMl5BanBnXkFtZTgwNTk1MjI4MzE@._V1_SX300.jpg\",\n  },\n  {\n    id: 2,\n    title: \"The Cotton Club\",\n    year: \"1984\",\n    runtime: \"127\",\n    genres: [\"Crime\", \"Drama\", \"Music\"],\n    director: \"Francis Ford Coppola\",\n    actors: \"Richard Gere, Gregory Hines, Diane Lane, Lonette McKee\",\n    plot: \"The Cotton Club was a famous night club in Harlem. The story follows the people that visited the club, those that ran it, and is peppered with the Jazz music that made it so famous.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTU5ODAyNzA4OV5BMl5BanBnXkFtZTcwNzYwNTIzNA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 3,\n    title: \"The Shawshank Redemption\",\n    year: \"1994\",\n    runtime: \"142\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Frank Darabont\",\n    actors: \"Tim Robbins, Morgan Freeman, Bob Gunton, William Sadler\",\n    plot: \"Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 4,\n    title: \"Crocodile Dundee\",\n    year: \"1986\",\n    runtime: \"97\",\n    genres: [\"Adventure\", \"Comedy\"],\n    director: \"Peter Faiman\",\n    actors: \"Paul Hogan, Linda Kozlowski, John Meillon, David Gulpilil\",\n    plot: \"An American reporter goes to the Australian outback to meet an eccentric crocodile poacher and invites him to New York City.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTg0MTU1MTg4NF5BMl5BanBnXkFtZTgwMDgzNzYxMTE@._V1_SX300.jpg\",\n  },\n  {\n    id: 5,\n    title: \"Valkyrie\",\n    year: \"2008\",\n    runtime: \"121\",\n    genres: [\"Drama\", \"History\", \"Thriller\"],\n    director: \"Bryan Singer\",\n    actors: \"Tom Cruise, Kenneth Branagh, Bill Nighy, Tom Wilkinson\",\n    plot: \"A dramatization of the 20 July assassination and political coup plot by desperate renegade German Army officers against Hitler during World War II.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTg3Njc2ODEyN15BMl5BanBnXkFtZTcwNTAwMzc3NA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 6,\n    title: \"Ratatouille\",\n    year: \"2007\",\n    runtime: \"111\",\n    genres: [\"Animation\", \"Comedy\", \"Family\"],\n    director: \"Brad Bird, Jan Pinkava\",\n    actors: \"Patton Oswalt, Ian Holm, Lou Romano, Brian Dennehy\",\n    plot: \"A rat who can cook makes an unusual alliance with a young kitchen worker at a famous restaurant.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTMzODU0NTkxMF5BMl5BanBnXkFtZTcwMjQ4MzMzMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 7,\n    title: \"City of God\",\n    year: \"2002\",\n    runtime: \"130\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Fernando Meirelles, Kátia Lund\",\n    actors:\n      \"Alexandre Rodrigues, Leandro Firmino, Phellipe Haagensen, Douglas Silva\",\n    plot: \"Two boys growing up in a violent neighborhood of Rio de Janeiro take different paths: one becomes a photographer, the other a drug dealer.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjA4ODQ3ODkzNV5BMl5BanBnXkFtZTYwOTc4NDI3._V1_SX300.jpg\",\n  },\n  {\n    id: 8,\n    title: \"Memento\",\n    year: \"2000\",\n    runtime: \"113\",\n    genres: [\"Mystery\", \"Thriller\"],\n    director: \"Christopher Nolan\",\n    actors: \"Guy Pearce, Carrie-Anne Moss, Joe Pantoliano, Mark Boone Junior\",\n    plot: \"A man juggles searching for his wife's murderer and keeping his short-term memory loss from being an obstacle.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNThiYjM3MzktMDg3Yy00ZWQ3LTk3YWEtN2M0YmNmNWEwYTE3XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg\",\n  },\n  {\n    id: 9,\n    title: \"The Intouchables\",\n    year: \"2011\",\n    runtime: \"112\",\n    genres: [\"Biography\", \"Comedy\", \"Drama\"],\n    director: \"Olivier Nakache, Eric Toledano\",\n    actors: \"François Cluzet, Omar Sy, Anne Le Ny, Audrey Fleurot\",\n    plot: \"After he becomes a quadriplegic from a paragliding accident, an aristocrat hires a young man from the projects to be his caregiver.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTYxNDA3MDQwNl5BMl5BanBnXkFtZTcwNTU4Mzc1Nw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 10,\n    title: \"Stardust\",\n    year: \"2007\",\n    runtime: \"127\",\n    genres: [\"Adventure\", \"Family\", \"Fantasy\"],\n    director: \"Matthew Vaughn\",\n    actors: \"Ian McKellen, Bimbo Hart, Alastair MacIntosh, David Kelly\",\n    plot: \"In a countryside town bordering on a magical land, a young man makes a promise to his beloved that he'll retrieve a fallen star by venturing into the magical realm.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjkyMTE1OTYwNF5BMl5BanBnXkFtZTcwMDIxODYzMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 11,\n    title: \"Apocalypto\",\n    year: \"2006\",\n    runtime: \"139\",\n    genres: [\"Action\", \"Adventure\", \"Drama\"],\n    director: \"Mel Gibson\",\n    actors:\n      \"Rudy Youngblood, Dalia Hernández, Jonathan Brewer, Morris Birdyellowhead\",\n    plot: \"As the Mayan kingdom faces its decline, the rulers insist the key to prosperity is to build more temples and offer human sacrifices. Jaguar Paw, a young man captured for sacrifice, flees to avoid his fate.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNTM1NjYyNTY5OV5BMl5BanBnXkFtZTcwMjgwNTMzMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 12,\n    title: \"Taxi Driver\",\n    year: \"1976\",\n    runtime: \"113\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Martin Scorsese\",\n    actors: \"Diahnne Abbott, Frank Adu, Victor Argo, Gino Ardito\",\n    plot: \"A mentally unstable Vietnam War veteran works as a night-time taxi driver in New York City where the perceived decadence and sleaze feeds his urge for violent action, attempting to save a preadolescent prostitute in the process.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNGQxNDgzZWQtZTNjNi00M2RkLWExZmEtNmE1NjEyZDEwMzA5XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg\",\n  },\n  {\n    id: 13,\n    title: \"No Country for Old Men\",\n    year: \"2007\",\n    runtime: \"122\",\n    genres: [\"Crime\", \"Drama\", \"Thriller\"],\n    director: \"Ethan Coen, Joel Coen\",\n    actors: \"Tommy Lee Jones, Javier Bardem, Josh Brolin, Woody Harrelson\",\n    plot: \"Violence and mayhem ensue after a hunter stumbles upon a drug deal gone wrong and more than two million dollars in cash near the Rio Grande.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjA5Njk3MjM4OV5BMl5BanBnXkFtZTcwMTc5MTE1MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 14,\n    title: \"Planet 51\",\n    year: \"2009\",\n    runtime: \"91\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Jorge Blanco, Javier Abad, Marcos Martínez\",\n    actors: \"Jessica Biel, John Cleese, Gary Oldman, Dwayne Johnson\",\n    plot: \"An alien civilization is invaded by Astronaut Chuck Baker, who believes that the planet was uninhabited. Wanted by the military, Baker must get back to his ship before it goes into orbit without him.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTUyOTAyNTA5Ml5BMl5BanBnXkFtZTcwODU2OTM0Mg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 15,\n    title: \"Looper\",\n    year: \"2012\",\n    runtime: \"119\",\n    genres: [\"Action\", \"Crime\", \"Drama\"],\n    director: \"Rian Johnson\",\n    actors: \"Joseph Gordon-Levitt, Bruce Willis, Emily Blunt, Paul Dano\",\n    plot: \"In 2074, when the mob wants to get rid of someone, the target is sent into the past, where a hired gun awaits - someone like Joe - who one day learns the mob wants to 'close the loop' by sending back Joe's future self for assassination.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTY3NTY0MjEwNV5BMl5BanBnXkFtZTcwNTE3NDA1OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 16,\n    title: \"Corpse Bride\",\n    year: \"2005\",\n    runtime: \"77\",\n    genres: [\"Animation\", \"Drama\", \"Family\"],\n    director: \"Tim Burton, Mike Johnson\",\n    actors: \"Johnny Depp, Helena Bonham Carter, Emily Watson, Tracey Ullman\",\n    plot: \"When a shy groom practices his wedding vows in the inadvertent presence of a deceased young woman, she rises from the grave assuming he has married her.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTk1MTY1NjU4MF5BMl5BanBnXkFtZTcwNjIzMTEzMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 17,\n    title: \"The Third Man\",\n    year: \"1949\",\n    runtime: \"93\",\n    genres: [\"Film-Noir\", \"Mystery\", \"Thriller\"],\n    director: \"Carol Reed\",\n    actors: \"Joseph Cotten, Alida Valli, Orson Welles, Trevor Howard\",\n    plot: \"Pulp novelist Holly Martins travels to shadowy, postwar Vienna, only to find himself investigating the mysterious death of an old friend, Harry Lime.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjMwNzMzMTQ0Ml5BMl5BanBnXkFtZTgwNjExMzUwNjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 18,\n    title: \"The Beach\",\n    year: \"2000\",\n    runtime: \"119\",\n    genres: [\"Adventure\", \"Drama\", \"Romance\"],\n    director: \"Danny Boyle\",\n    actors:\n      \"Leonardo DiCaprio, Daniel York, Patcharawan Patarakijjanon, Virginie Ledoyen\",\n    plot: \"Twenty-something Richard travels to Thailand and finds himself in possession of a strange map. Rumours state that it leads to a solitary beach paradise, a tropical bliss - excited and intrigued, he sets out to find it.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BN2ViYTFiZmUtOTIxZi00YzIxLWEyMzUtYjQwZGNjMjNhY2IwXkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_SX300.jpg\",\n  },\n  {\n    id: 19,\n    title: \"Scarface\",\n    year: \"1983\",\n    runtime: \"170\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Brian De Palma\",\n    actors:\n      \"Al Pacino, Steven Bauer, Michelle Pfeiffer, Mary Elizabeth Mastrantonio\",\n    plot: \"In Miami in 1980, a determined Cuban immigrant takes over a drug cartel and succumbs to greed.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjAzOTM4MzEwNl5BMl5BanBnXkFtZTgwMzU1OTc1MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 20,\n    title: \"Sid and Nancy\",\n    year: \"1986\",\n    runtime: \"112\",\n    genres: [\"Biography\", \"Drama\", \"Music\"],\n    director: \"Alex Cox\",\n    actors: \"Gary Oldman, Chloe Webb, David Hayman, Debby Bishop\",\n    plot: \"Morbid biographical story of Sid Vicious, bassist with British punk group the Sex Pistols, and his girlfriend Nancy Spungen. When the Sex Pistols break up after their fateful US tour, ...\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjExNjA5NzY4M15BMl5BanBnXkFtZTcwNjQ2NzI5NA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 21,\n    title: \"Black Swan\",\n    year: \"2010\",\n    runtime: \"108\",\n    genres: [\"Drama\", \"Thriller\"],\n    director: \"Darren Aronofsky\",\n    actors: \"Natalie Portman, Mila Kunis, Vincent Cassel, Barbara Hershey\",\n    plot: 'A committed dancer wins the lead role in a production of Tchaikovsky\\'s \"Swan Lake\" only to find herself struggling to maintain her sanity.',\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNzY2NzI4OTE5MF5BMl5BanBnXkFtZTcwMjMyNDY4Mw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 22,\n    title: \"Inception\",\n    year: \"2010\",\n    runtime: \"148\",\n    genres: [\"Action\", \"Adventure\", \"Sci-Fi\"],\n    director: \"Christopher Nolan\",\n    actors: \"Leonardo DiCaprio, Joseph Gordon-Levitt, Ellen Page, Tom Hardy\",\n    plot: \"A thief, who steals corporate secrets through use of dream-sharing technology, is given the inverse task of planting an idea into the mind of a CEO.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 23,\n    title: \"The Deer Hunter\",\n    year: \"1978\",\n    runtime: \"183\",\n    genres: [\"Drama\", \"War\"],\n    director: \"Michael Cimino\",\n    actors: \"Robert De Niro, John Cazale, John Savage, Christopher Walken\",\n    plot: \"An in-depth examination of the ways in which the U.S. Vietnam War impacts and disrupts the lives of people in a small industrial town in Pennsylvania.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYzYmRmZTQtYjk2NS00MDdlLTkxMDAtMTE2YTM2ZmNlMTBkXkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg\",\n  },\n  {\n    id: 24,\n    title: \"Chasing Amy\",\n    year: \"1997\",\n    runtime: \"113\",\n    genres: [\"Comedy\", \"Drama\", \"Romance\"],\n    director: \"Kevin Smith\",\n    actors: \"Ethan Suplee, Ben Affleck, Scott Mosier, Jason Lee\",\n    plot: \"Holden and Banky are comic book artists. Everything's going good for them until they meet Alyssa, also a comic book artist. Holden falls for her, but his hopes are crushed when he finds out she's gay.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BZDM3MTg2MGUtZDM0MC00NzMwLWE5NjItOWFjNjA2M2I4YzgxXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg\",\n  },\n  {\n    id: 25,\n    title: \"Django Unchained\",\n    year: \"2012\",\n    runtime: \"165\",\n    genres: [\"Drama\", \"Western\"],\n    director: \"Quentin Tarantino\",\n    actors: \"Jamie Foxx, Christoph Waltz, Leonardo DiCaprio, Kerry Washington\",\n    plot: \"With the help of a German bounty hunter, a freed slave sets out to rescue his wife from a brutal Mississippi plantation owner.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMjIyNTQ5NjQ1OV5BMl5BanBnXkFtZTcwODg1MDU4OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 26,\n    title: \"The Silence of the Lambs\",\n    year: \"1991\",\n    runtime: \"118\",\n    genres: [\"Crime\", \"Drama\", \"Thriller\"],\n    director: \"Jonathan Demme\",\n    actors:\n      \"Jodie Foster, Lawrence A. Bonney, Kasi Lemmons, Lawrence T. Wrentz\",\n    plot: \"A young F.B.I. cadet must confide in an incarcerated and manipulative killer to receive his help on catching another serial killer who skins his victims.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQ2NzkzMDI4OF5BMl5BanBnXkFtZTcwMDA0NzE1NA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 27,\n    title: \"American Beauty\",\n    year: \"1999\",\n    runtime: \"122\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Sam Mendes\",\n    actors: \"Kevin Spacey, Annette Bening, Thora Birch, Wes Bentley\",\n    plot: \"A sexually frustrated suburban father has a mid-life crisis after becoming infatuated with his daughter's best friend.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjM4NTI5NzYyNV5BMl5BanBnXkFtZTgwNTkxNTYxMTE@._V1_SX300.jpg\",\n  },\n  {\n    id: 28,\n    title: \"Snatch\",\n    year: \"2000\",\n    runtime: \"102\",\n    genres: [\"Comedy\", \"Crime\"],\n    director: \"Guy Ritchie\",\n    actors: \"Benicio Del Toro, Dennis Farina, Vinnie Jones, Brad Pitt\",\n    plot: \"Unscrupulous boxing promoters, violent bookmakers, a Russian gangster, incompetent amateur robbers, and supposedly Jewish jewelers fight to track down a priceless stolen diamond.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTA2NDYxOGYtYjU1Mi00Y2QzLTgxMTQtMWI1MGI0ZGQ5MmU4XkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_SX300.jpg\",\n  },\n  {\n    id: 29,\n    title: \"Midnight Express\",\n    year: \"1978\",\n    runtime: \"121\",\n    genres: [\"Crime\", \"Drama\", \"Thriller\"],\n    director: \"Alan Parker\",\n    actors: \"Brad Davis, Irene Miracle, Bo Hopkins, Paolo Bonacelli\",\n    plot: \"Billy Hayes, an American college student, is caught smuggling drugs out of Turkey and thrown into prison.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQyMDA5MzkyOF5BMl5BanBnXkFtZTgwOTYwNTcxMTE@._V1_SX300.jpg\",\n  },\n  {\n    id: 30,\n    title: \"Pulp Fiction\",\n    year: \"1994\",\n    runtime: \"154\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Quentin Tarantino\",\n    actors: \"Tim Roth, Amanda Plummer, Laura Lovelace, John Travolta\",\n    plot: \"The lives of two mob hit men, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTkxMTA5OTAzMl5BMl5BanBnXkFtZTgwNjA5MDc3NjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 31,\n    title: \"Lock, Stock and Two Smoking Barrels\",\n    year: \"1998\",\n    runtime: \"107\",\n    genres: [\"Comedy\", \"Crime\"],\n    director: \"Guy Ritchie\",\n    actors: \"Jason Flemyng, Dexter Fletcher, Nick Moran, Jason Statham\",\n    plot: \"A botched card game in London triggers four friends, thugs, weed-growers, hard gangsters, loan sharks and debt collectors to collide with each other in a series of unexpected events, all for the sake of weed, cash and two antique shotguns.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTAyN2JmZmEtNjAyMy00NzYwLThmY2MtYWQ3OGNhNjExMmM4XkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_SX300.jpg\",\n  },\n  {\n    id: 32,\n    title: \"Lucky Number Slevin\",\n    year: \"2006\",\n    runtime: \"110\",\n    genres: [\"Crime\", \"Drama\", \"Mystery\"],\n    director: \"Paul McGuigan\",\n    actors: \"Josh Hartnett, Bruce Willis, Lucy Liu, Morgan Freeman\",\n    plot: \"A case of mistaken identity lands Slevin into the middle of a war being plotted by two of the city's most rival crime bosses: The Rabbi and The Boss. Slevin is under constant surveillance by relentless Detective Brikowski as well as the infamous assassin Goodkat and finds himself having to hatch his own ingenious plot to get them before they get him.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMzc1OTEwMTk4OF5BMl5BanBnXkFtZTcwMTEzMDQzMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 33,\n    title: \"Rear Window\",\n    year: \"1954\",\n    runtime: \"112\",\n    genres: [\"Mystery\", \"Thriller\"],\n    director: \"Alfred Hitchcock\",\n    actors: \"James Stewart, Grace Kelly, Wendell Corey, Thelma Ritter\",\n    plot: \"A wheelchair-bound photographer spies on his neighbours from his apartment window and becomes convinced one of them has committed murder.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNGUxYWM3M2MtMGM3Mi00ZmRiLWE0NGQtZjE5ODI2OTJhNTU0XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg\",\n  },\n  {\n    id: 34,\n    title: \"Pan's Labyrinth\",\n    year: \"2006\",\n    runtime: \"118\",\n    genres: [\"Drama\", \"Fantasy\", \"War\"],\n    director: \"Guillermo del Toro\",\n    actors: \"Ivana Baquero, Sergi López, Maribel Verdú, Doug Jones\",\n    plot: \"In the falangist Spain of 1944, the bookish young stepdaughter of a sadistic army officer escapes into an eerie but captivating fantasy world.\",\n    posterUrl: \"\",\n  },\n  {\n    id: 35,\n    title: \"Shutter Island\",\n    year: \"2010\",\n    runtime: \"138\",\n    genres: [\"Mystery\", \"Thriller\"],\n    director: \"Martin Scorsese\",\n    actors: \"Leonardo DiCaprio, Mark Ruffalo, Ben Kingsley, Max von Sydow\",\n    plot: \"In 1954, a U.S. marshal investigates the disappearance of a murderess who escaped from a hospital for the criminally insane.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTMxMTIyNzMxMV5BMl5BanBnXkFtZTcwOTc4OTI3Mg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 36,\n    title: \"Reservoir Dogs\",\n    year: \"1992\",\n    runtime: \"99\",\n    genres: [\"Crime\", \"Drama\", \"Thriller\"],\n    director: \"Quentin Tarantino\",\n    actors: \"Harvey Keitel, Tim Roth, Michael Madsen, Chris Penn\",\n    plot: \"After a simple jewelry heist goes terribly wrong, the surviving criminals begin to suspect that one of them is a police informant.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNjE5ZDJiZTQtOGE2YS00ZTc5LTk0OGUtOTg2NjdjZmVlYzE2XkEyXkFqcGdeQXVyMzM4MjM0Nzg@._V1_SX300.jpg\",\n  },\n  {\n    id: 37,\n    title: \"The Shining\",\n    year: \"1980\",\n    runtime: \"146\",\n    genres: [\"Drama\", \"Horror\"],\n    director: \"Stanley Kubrick\",\n    actors: \"Jack Nicholson, Shelley Duvall, Danny Lloyd, Scatman Crothers\",\n    plot: \"A family heads to an isolated hotel for the winter where an evil and spiritual presence influences the father into violence, while his psychic son sees horrific forebodings from the past and of the future.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BODMxMjE3NTA4Ml5BMl5BanBnXkFtZTgwNDc0NTIxMDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 38,\n    title: \"Midnight in Paris\",\n    year: \"2011\",\n    runtime: \"94\",\n    genres: [\"Comedy\", \"Fantasy\", \"Romance\"],\n    director: \"Woody Allen\",\n    actors: \"Owen Wilson, Rachel McAdams, Kurt Fuller, Mimi Kennedy\",\n    plot: \"While on a trip to Paris with his fiancée's family, a nostalgic screenwriter finds himself mysteriously going back to the 1920s everyday at midnight.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTM4NjY1MDQwMl5BMl5BanBnXkFtZTcwNTI3Njg3NA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 39,\n    title: \"Les Misérables\",\n    year: \"2012\",\n    runtime: \"158\",\n    genres: [\"Drama\", \"Musical\", \"Romance\"],\n    director: \"Tom Hooper\",\n    actors: \"Hugh Jackman, Russell Crowe, Anne Hathaway, Amanda Seyfried\",\n    plot: \"In 19th-century France, Jean Valjean, who for decades has been hunted by the ruthless policeman Javert after breaking parole, agrees to care for a factory worker's daughter. The decision changes their lives forever.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTQ4NDI3NDg4M15BMl5BanBnXkFtZTcwMjY5OTI1OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 40,\n    title: \"L.A. Confidential\",\n    year: \"1997\",\n    runtime: \"138\",\n    genres: [\"Crime\", \"Drama\", \"Mystery\"],\n    director: \"Curtis Hanson\",\n    actors: \"Kevin Spacey, Russell Crowe, Guy Pearce, James Cromwell\",\n    plot: \"As corruption grows in 1950s LA, three policemen - one strait-laced, one brutal, and one sleazy - investigate a series of murders with their own brand of justice.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNWEwNDhhNWUtYWMzNi00ZTNhLWFiZDAtMjBjZmJhMTU0ZTY2XkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_SX300.jpg\",\n  },\n  {\n    id: 41,\n    title: \"Moneyball\",\n    year: \"2011\",\n    runtime: \"133\",\n    genres: [\"Biography\", \"Drama\", \"Sport\"],\n    director: \"Bennett Miller\",\n    actors: \"Brad Pitt, Jonah Hill, Philip Seymour Hoffman, Robin Wright\",\n    plot: \"Oakland A's general manager Billy Beane's successful attempt to assemble a baseball team on a lean budget by employing computer-generated analysis to acquire new players.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxOTU3Mzc1M15BMl5BanBnXkFtZTcwMzk1ODUzNg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 42,\n    title: \"The Hangover\",\n    year: \"2009\",\n    runtime: \"100\",\n    genres: [\"Comedy\"],\n    director: \"Todd Phillips\",\n    actors: \"Bradley Cooper, Ed Helms, Zach Galifianakis, Justin Bartha\",\n    plot: \"Three buddies wake up from a bachelor party in Las Vegas, with no memory of the previous night and the bachelor missing. They make their way around the city in order to find their friend before his wedding.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTU1MDA1MTYwMF5BMl5BanBnXkFtZTcwMDcxMzA1Mg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 43,\n    title: \"The Great Beauty\",\n    year: \"2013\",\n    runtime: \"141\",\n    genres: [\"Drama\"],\n    director: \"Paolo Sorrentino\",\n    actors: \"Toni Servillo, Carlo Verdone, Sabrina Ferilli, Carlo Buccirosso\",\n    plot: \"Jep Gambardella has seduced his way through the lavish nightlife of Rome for decades, but after his 65th birthday and a shock from the past, Jep looks past the nightclubs and parties to find a timeless landscape of absurd, exquisite beauty.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQ0ODg1OTQ2Nl5BMl5BanBnXkFtZTgwNTc2MDY1MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 44,\n    title: \"Gran Torino\",\n    year: \"2008\",\n    runtime: \"116\",\n    genres: [\"Drama\"],\n    director: \"Clint Eastwood\",\n    actors: \"Clint Eastwood, Christopher Carley, Bee Vang, Ahney Her\",\n    plot: \"Disgruntled Korean War veteran Walt Kowalski sets out to reform his neighbor, a Hmong teenager who tried to steal Kowalski's prized possession: a 1972 Gran Torino.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTQyMTczMTAxMl5BMl5BanBnXkFtZTcwOTc1ODE0Mg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 45,\n    title: \"Mary and Max\",\n    year: \"2009\",\n    runtime: \"92\",\n    genres: [\"Animation\", \"Comedy\", \"Drama\"],\n    director: \"Adam Elliot\",\n    actors: \"Toni Collette, Philip Seymour Hoffman, Barry Humphries, Eric Bana\",\n    plot: \"A tale of friendship between two unlikely pen pals: Mary, a lonely, eight-year-old girl living in the suburbs of Melbourne, and Max, a forty-four-year old, severely obese man living in New York.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQ1NDIyNTA1Nl5BMl5BanBnXkFtZTcwMjc2Njk3OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 46,\n    title: \"Flight\",\n    year: \"2012\",\n    runtime: \"138\",\n    genres: [\"Drama\", \"Thriller\"],\n    director: \"Robert Zemeckis\",\n    actors:\n      \"Nadine Velazquez, Denzel Washington, Carter Cabassa, Adam C. Edwards\",\n    plot: \"An airline pilot saves almost all his passengers on his malfunctioning airliner which eventually crashed, but an investigation into the accident reveals something troubling.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTUxMjI1OTMxNl5BMl5BanBnXkFtZTcwNjc3NTY1OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 47,\n    title: \"One Flew Over the Cuckoo's Nest\",\n    year: \"1975\",\n    runtime: \"133\",\n    genres: [\"Drama\"],\n    director: \"Milos Forman\",\n    actors: \"Michael Berryman, Peter Brocco, Dean R. Brooks, Alonzo Brown\",\n    plot: \"A criminal pleads insanity after getting into trouble again and once in the mental institution rebels against the oppressive nurse and rallies up the scared patients.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BYmJkODkwOTItZThjZC00MTE0LWIxNzQtYTM3MmQwMGI1OWFiXkEyXkFqcGdeQXVyNjUwNzk3NDc@._V1_SX300.jpg\",\n  },\n  {\n    id: 48,\n    title: \"Requiem for a Dream\",\n    year: \"2000\",\n    runtime: \"102\",\n    genres: [\"Drama\"],\n    director: \"Darren Aronofsky\",\n    actors: \"Ellen Burstyn, Jared Leto, Jennifer Connelly, Marlon Wayans\",\n    plot: \"The drug-induced utopias of four Coney Island people are shattered when their addictions run deep.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTkzODMzODYwOF5BMl5BanBnXkFtZTcwODM2NjA2NQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 49,\n    title: \"The Truman Show\",\n    year: \"1998\",\n    runtime: \"103\",\n    genres: [\"Comedy\", \"Drama\", \"Sci-Fi\"],\n    director: \"Peter Weir\",\n    actors: \"Jim Carrey, Laura Linney, Noah Emmerich, Natascha McElhone\",\n    plot: \"An insurance salesman/adjuster discovers his entire life is actually a television show.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMDIzODcyY2EtMmY2MC00ZWVlLTgwMzAtMjQwOWUyNmJjNTYyXkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_SX300.jpg\",\n  },\n  {\n    id: 50,\n    title: \"The Artist\",\n    year: \"2011\",\n    runtime: \"100\",\n    genres: [\"Comedy\", \"Drama\", \"Romance\"],\n    director: \"Michel Hazanavicius\",\n    actors: \"Jean Dujardin, Bérénice Bejo, John Goodman, James Cromwell\",\n    plot: \"A silent movie star meets a young dancer, but the arrival of talking pictures sends their careers in opposite directions.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMzk0NzQxMTM0OV5BMl5BanBnXkFtZTcwMzU4MDYyNQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 51,\n    title: \"Forrest Gump\",\n    year: \"1994\",\n    runtime: \"142\",\n    genres: [\"Comedy\", \"Drama\"],\n    director: \"Robert Zemeckis\",\n    actors:\n      \"Tom Hanks, Rebecca Williams, Sally Field, Michael Conner Humphreys\",\n    plot: \"Forrest Gump, while not intelligent, has accidentally been present at many historic moments, but his true love, Jenny Curran, eludes him.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BYThjM2MwZGMtMzg3Ny00NGRkLWE4M2EtYTBiNWMzOTY0YTI4XkEyXkFqcGdeQXVyNDYyMDk5MTU@._V1_SX300.jpg\",\n  },\n  {\n    id: 52,\n    title: \"The Hobbit: The Desolation of Smaug\",\n    year: \"2013\",\n    runtime: \"161\",\n    genres: [\"Adventure\", \"Fantasy\"],\n    director: \"Peter Jackson\",\n    actors: \"Ian McKellen, Martin Freeman, Richard Armitage, Ken Stott\",\n    plot: \"The dwarves, along with Bilbo Baggins and Gandalf the Grey, continue their quest to reclaim Erebor, their homeland, from Smaug. Bilbo Baggins is in possession of a mysterious and magical ring.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMzU0NDY0NDEzNV5BMl5BanBnXkFtZTgwOTIxNDU1MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 53,\n    title: \"Vicky Cristina Barcelona\",\n    year: \"2008\",\n    runtime: \"96\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Woody Allen\",\n    actors:\n      \"Rebecca Hall, Scarlett Johansson, Christopher Evan Welch, Chris Messina\",\n    plot: \"Two girlfriends on a summer holiday in Spain become enamored with the same painter, unaware that his ex-wife, with whom he has a tempestuous relationship, is about to re-enter the picture.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTU2NDQ4MTg2MV5BMl5BanBnXkFtZTcwNDUzNjU3MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 54,\n    title: \"Slumdog Millionaire\",\n    year: \"2008\",\n    runtime: \"120\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Danny Boyle, Loveleen Tandan\",\n    actors: \"Dev Patel, Saurabh Shukla, Anil Kapoor, Rajendranath Zutshi\",\n    plot: 'A Mumbai teen reflects on his upbringing in the slums when he is accused of cheating on the Indian Version of \"Who Wants to be a Millionaire?\"',\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTU2NTA5NzI0N15BMl5BanBnXkFtZTcwMjUxMjYxMg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 55,\n    title: \"Lost in Translation\",\n    year: \"2003\",\n    runtime: \"101\",\n    genres: [\"Drama\"],\n    director: \"Sofia Coppola\",\n    actors:\n      \"Scarlett Johansson, Bill Murray, Akiko Takeshita, Kazuyoshi Minamimagoe\",\n    plot: \"A faded movie star and a neglected young woman form an unlikely bond after crossing paths in Tokyo.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTI2NDI5ODk4N15BMl5BanBnXkFtZTYwMTI3NTE3._V1_SX300.jpg\",\n  },\n  {\n    id: 56,\n    title: \"Match Point\",\n    year: \"2005\",\n    runtime: \"119\",\n    genres: [\"Drama\", \"Romance\", \"Thriller\"],\n    director: \"Woody Allen\",\n    actors:\n      \"Jonathan Rhys Meyers, Alexander Armstrong, Paul Kaye, Matthew Goode\",\n    plot: \"At a turning point in his life, a former tennis pro falls for an actress who happens to be dating his friend and soon-to-be brother-in-law.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTMzNzY4MzE5NF5BMl5BanBnXkFtZTcwMzQ1MDMzMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 57,\n    title: \"Psycho\",\n    year: \"1960\",\n    runtime: \"109\",\n    genres: [\"Horror\", \"Mystery\", \"Thriller\"],\n    director: \"Alfred Hitchcock\",\n    actors: \"Anthony Perkins, Vera Miles, John Gavin, Janet Leigh\",\n    plot: \"A Phoenix secretary embezzles $40,000 from her employer's client, goes on the run, and checks into a remote motel run by a young man under the domination of his mother.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMDI3OWRmOTEtOWJhYi00N2JkLTgwNGItMjdkN2U0NjFiZTYwXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg\",\n  },\n  {\n    id: 58,\n    title: \"North by Northwest\",\n    year: \"1959\",\n    runtime: \"136\",\n    genres: [\"Action\", \"Adventure\", \"Crime\"],\n    director: \"Alfred Hitchcock\",\n    actors: \"Cary Grant, Eva Marie Saint, James Mason, Jessie Royce Landis\",\n    plot: \"A hapless New York advertising executive is mistaken for a government agent by a group of foreign spies, and is pursued across the country while he looks for a way to survive.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMjQwMTQ0MzgwNl5BMl5BanBnXkFtZTgwNjc4ODE4MzE@._V1_SX300.jpg\",\n  },\n  {\n    id: 59,\n    title: \"Madagascar: Escape 2 Africa\",\n    year: \"2008\",\n    runtime: \"89\",\n    genres: [\"Animation\", \"Action\", \"Adventure\"],\n    director: \"Eric Darnell, Tom McGrath\",\n    actors: \"Ben Stiller, Chris Rock, David Schwimmer, Jada Pinkett Smith\",\n    plot: \"The animals try to fly back to New York City, but crash-land on an African wildlife refuge, where Alex is reunited with his parents.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjExMDA4NDcwMl5BMl5BanBnXkFtZTcwODAxNTQ3MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 60,\n    title: \"Despicable Me 2\",\n    year: \"2013\",\n    runtime: \"98\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Pierre Coffin, Chris Renaud\",\n    actors: \"Steve Carell, Kristen Wiig, Benjamin Bratt, Miranda Cosgrove\",\n    plot: \"When Gru, the world's most super-bad turned super-dad has been recruited by a team of officials to stop lethal muscle and a host of Gru's own, He has to fight back with new gadgetry, cars, and more minion madness.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjExNjAyNTcyMF5BMl5BanBnXkFtZTgwODQzMjQ3MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 61,\n    title: \"Downfall\",\n    year: \"2004\",\n    runtime: \"156\",\n    genres: [\"Biography\", \"Drama\", \"History\"],\n    director: \"Oliver Hirschbiegel\",\n    actors:\n      \"Bruno Ganz, Alexandra Maria Lara, Corinna Harfouch, Ulrich Matthes\",\n    plot: \"Traudl Junge, the final secretary for Adolf Hitler, tells of the Nazi dictator's final days in his Berlin bunker at the end of WWII.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTM1OTI1MjE2Nl5BMl5BanBnXkFtZTcwMTEwMzc4NA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 62,\n    title: \"Madagascar\",\n    year: \"2005\",\n    runtime: \"86\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Eric Darnell, Tom McGrath\",\n    actors: \"Ben Stiller, Chris Rock, David Schwimmer, Jada Pinkett Smith\",\n    plot: \"Spoiled by their upbringing with no idea what wild life is really like, four animals from New York Central Zoo escape, unwittingly assisted by four absconding penguins, and find themselves in Madagascar, among a bunch of merry lemurs\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY4NDUwMzQxMF5BMl5BanBnXkFtZTcwMDgwNjgyMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 63,\n    title: \"Madagascar 3: Europe's Most Wanted\",\n    year: \"2012\",\n    runtime: \"93\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Eric Darnell, Tom McGrath, Conrad Vernon\",\n    actors: \"Ben Stiller, Chris Rock, David Schwimmer, Jada Pinkett Smith\",\n    plot: \"Alex, Marty, Gloria and Melman are still fighting to get home to their beloved Big Apple. Their journey takes them through Europe where they find the perfect cover: a traveling circus, which they reinvent - Madagascar style.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTM2MTIzNzk2MF5BMl5BanBnXkFtZTcwMDcwMzQxNw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 64,\n    title: \"God Bless America\",\n    year: \"2011\",\n    runtime: \"105\",\n    genres: [\"Comedy\", \"Crime\"],\n    director: \"Bobcat Goldthwait\",\n    actors:\n      \"Joel Murray, Tara Lynne Barr, Melinda Page Hamilton, Mackenzie Brooke Smith\",\n    plot: \"On a mission to rid society of its most repellent citizens, terminally ill Frank makes an unlikely accomplice in 16-year-old Roxy.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQwMTc1MzA4NF5BMl5BanBnXkFtZTcwNzQwMTgzNw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 65,\n    title: \"The Social Network\",\n    year: \"2010\",\n    runtime: \"120\",\n    genres: [\"Biography\", \"Drama\"],\n    director: \"David Fincher\",\n    actors: \"Jesse Eisenberg, Rooney Mara, Bryan Barter, Dustin Fitzsimons\",\n    plot: \"Harvard student Mark Zuckerberg creates the social networking site that would become known as Facebook, but is later sued by two brothers who claimed he stole their idea, and the co-founder who was later squeezed out of the business.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTM2ODk0NDAwMF5BMl5BanBnXkFtZTcwNTM1MDc2Mw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 66,\n    title: \"The Pianist\",\n    year: \"2002\",\n    runtime: \"150\",\n    genres: [\"Biography\", \"Drama\", \"War\"],\n    director: \"Roman Polanski\",\n    actors: \"Adrien Brody, Emilia Fox, Michal Zebrowski, Ed Stoppard\",\n    plot: \"A Polish Jewish musician struggles to survive the destruction of the Warsaw ghetto of World War II.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTc4OTkyOTA3OF5BMl5BanBnXkFtZTYwMDIxNjk5._V1_SX300.jpg\",\n  },\n  {\n    id: 67,\n    title: \"Alive\",\n    year: \"1993\",\n    runtime: \"120\",\n    genres: [\"Adventure\", \"Biography\", \"Drama\"],\n    director: \"Frank Marshall\",\n    actors: \"Ethan Hawke, Vincent Spano, Josh Hamilton, Bruce Ramsay\",\n    plot: \"Uruguayan rugby team stranded in the snow swept Andes are forced to use desperate measures to survive after a plane crash.\",\n    posterUrl: \"\",\n  },\n  {\n    id: 68,\n    title: \"Casablanca\",\n    year: \"1942\",\n    runtime: \"102\",\n    genres: [\"Drama\", \"Romance\", \"War\"],\n    director: \"Michael Curtiz\",\n    actors: \"Humphrey Bogart, Ingrid Bergman, Paul Henreid, Claude Rains\",\n    plot: \"In Casablanca, Morocco in December 1941, a cynical American expatriate meets a former lover, with unforeseen complications.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjQwNDYyNTk2N15BMl5BanBnXkFtZTgwMjQ0OTMyMjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 69,\n    title: \"American Gangster\",\n    year: \"2007\",\n    runtime: \"157\",\n    genres: [\"Biography\", \"Crime\", \"Drama\"],\n    director: \"Ridley Scott\",\n    actors: \"Denzel Washington, Russell Crowe, Chiwetel Ejiofor, Josh Brolin\",\n    plot: \"In 1970s America, a detective works to bring down the drug empire of Frank Lucas, a heroin kingpin from Manhattan, who is smuggling the drug into the country from the Far East.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTkyNzY5MDA5MV5BMl5BanBnXkFtZTcwMjg4MzI3MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 70,\n    title: \"Catch Me If You Can\",\n    year: \"2002\",\n    runtime: \"141\",\n    genres: [\"Biography\", \"Crime\", \"Drama\"],\n    director: \"Steven Spielberg\",\n    actors: \"Leonardo DiCaprio, Tom Hanks, Christopher Walken, Martin Sheen\",\n    plot: \"The true story of Frank Abagnale Jr. who, before his 19th birthday, successfully conned millions of dollars' worth of checks as a Pan Am pilot, doctor, and legal prosecutor.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5MzYzNjc5NV5BMl5BanBnXkFtZTYwNTUyNTc2._V1_SX300.jpg\",\n  },\n  {\n    id: 71,\n    title: \"American History X\",\n    year: \"1998\",\n    runtime: \"119\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Tony Kaye\",\n    actors: \"Edward Norton, Edward Furlong, Beverly D'Angelo, Jennifer Lien\",\n    plot: \"A former neo-nazi skinhead tries to prevent his younger brother from going down the same wrong path that he did.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BZjA0MTM4MTQtNzY5MC00NzY3LWI1ZTgtYzcxMjkyMzU4MDZiXkEyXkFqcGdeQXVyNDYyMDk5MTU@._V1_SX300.jpg\",\n  },\n  {\n    id: 72,\n    title: \"Casino\",\n    year: \"1995\",\n    runtime: \"178\",\n    genres: [\"Biography\", \"Crime\", \"Drama\"],\n    director: \"Martin Scorsese\",\n    actors: \"Robert De Niro, Sharon Stone, Joe Pesci, James Woods\",\n    plot: \"Greed, deception, money, power, and murder occur between two best friends, a mafia underboss and a casino owner, for a trophy wife over a gambling empire.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTcxOWYzNDYtYmM4YS00N2NkLTk0NTAtNjg1ODgwZjAxYzI3XkEyXkFqcGdeQXVyNTA4NzY1MzY@._V1_SX300.jpg\",\n  },\n  {\n    id: 73,\n    title: \"Pirates of the Caribbean: At World's End\",\n    year: \"2007\",\n    runtime: \"169\",\n    genres: [\"Action\", \"Adventure\", \"Fantasy\"],\n    director: \"Gore Verbinski\",\n    actors: \"Johnny Depp, Geoffrey Rush, Orlando Bloom, Keira Knightley\",\n    plot: \"Captain Barbossa, Will Turner and Elizabeth Swann must sail off the edge of the map, navigate treachery and betrayal, find Jack Sparrow, and make their final alliances for one last decisive battle.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIyNjkxNzEyMl5BMl5BanBnXkFtZTYwMjc3MDE3._V1_SX300.jpg\",\n  },\n  {\n    id: 74,\n    title: \"Pirates of the Caribbean: On Stranger Tides\",\n    year: \"2011\",\n    runtime: \"136\",\n    genres: [\"Action\", \"Adventure\", \"Fantasy\"],\n    director: \"Rob Marshall\",\n    actors: \"Johnny Depp, Penélope Cruz, Geoffrey Rush, Ian McShane\",\n    plot: \"Jack Sparrow and Barbossa embark on a quest to find the elusive fountain of youth, only to discover that Blackbeard and his daughter are after it too.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMjE5MjkwODI3Nl5BMl5BanBnXkFtZTcwNjcwMDk4NA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 75,\n    title: \"Crash\",\n    year: \"2004\",\n    runtime: \"112\",\n    genres: [\"Crime\", \"Drama\", \"Thriller\"],\n    director: \"Paul Haggis\",\n    actors: \"Karina Arroyave, Dato Bakhtadze, Sandra Bullock, Don Cheadle\",\n    plot: \"Los Angeles citizens with vastly separate lives collide in interweaving stories of race, loss and redemption.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BOTk1OTA1MjIyNV5BMl5BanBnXkFtZTcwODQxMTkyMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 76,\n    title: \"Pirates of the Caribbean: The Curse of the Black Pearl\",\n    year: \"2003\",\n    runtime: \"143\",\n    genres: [\"Action\", \"Adventure\", \"Fantasy\"],\n    director: \"Gore Verbinski\",\n    actors: \"Johnny Depp, Geoffrey Rush, Orlando Bloom, Keira Knightley\",\n    plot: \"Blacksmith Will Turner teams up with eccentric pirate \\\"Captain\\\" Jack Sparrow to save his love, the governor's daughter, from Jack's former pirate allies, who are now undead.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjAyNDM4MTc2N15BMl5BanBnXkFtZTYwNDk0Mjc3._V1_SX300.jpg\",\n  },\n  {\n    id: 77,\n    title: \"The Lord of the Rings: The Return of the King\",\n    year: \"2003\",\n    runtime: \"201\",\n    genres: [\"Action\", \"Adventure\", \"Drama\"],\n    director: \"Peter Jackson\",\n    actors: \"Noel Appleby, Ali Astin, Sean Astin, David Aston\",\n    plot: \"Gandalf and Aragorn lead the World of Men against Sauron's army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjE4MjA1NTAyMV5BMl5BanBnXkFtZTcwNzM1NDQyMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 78,\n    title: \"Oldboy\",\n    year: \"2003\",\n    runtime: \"120\",\n    genres: [\"Drama\", \"Mystery\", \"Thriller\"],\n    director: \"Chan-wook Park\",\n    actors: \"Min-sik Choi, Ji-tae Yu, Hye-jeong Kang, Dae-han Ji\",\n    plot: \"After being kidnapped and imprisoned for 15 years, Oh Dae-Su is released, only to find that he must find his captor in 5 days.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTI3NTQyMzU5M15BMl5BanBnXkFtZTcwMTM2MjgyMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 79,\n    title: \"Chocolat\",\n    year: \"2000\",\n    runtime: \"121\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Lasse Hallström\",\n    actors:\n      \"Alfred Molina, Carrie-Anne Moss, Aurelien Parent Koenig, Antonio Gil\",\n    plot: \"A woman and her daughter open a chocolate shop in a small French village that shakes up the rigid morality of the community.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjA4MDI3NTQwMV5BMl5BanBnXkFtZTcwNjIzNDcyMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 80,\n    title: \"Casino Royale\",\n    year: \"2006\",\n    runtime: \"144\",\n    genres: [\"Action\", \"Adventure\", \"Thriller\"],\n    director: \"Martin Campbell\",\n    actors: \"Daniel Craig, Eva Green, Mads Mikkelsen, Judi Dench\",\n    plot: \"Armed with a licence to kill, Secret Agent James Bond sets out on his first mission as 007 and must defeat a weapons dealer in a high stakes game of poker at Casino Royale, but things are not what they seem.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTM5MjI4NDExNF5BMl5BanBnXkFtZTcwMDM1MjMzMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 81,\n    title: \"WALL·E\",\n    year: \"2008\",\n    runtime: \"98\",\n    genres: [\"Animation\", \"Adventure\", \"Family\"],\n    director: \"Andrew Stanton\",\n    actors: \"Ben Burtt, Elissa Knight, Jeff Garlin, Fred Willard\",\n    plot: \"In the distant future, a small waste-collecting robot inadvertently embarks on a space journey that will ultimately decide the fate of mankind.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTczOTA3MzY2N15BMl5BanBnXkFtZTcwOTYwNjE2MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 82,\n    title: \"The Wolf of Wall Street\",\n    year: \"2013\",\n    runtime: \"180\",\n    genres: [\"Biography\", \"Comedy\", \"Crime\"],\n    director: \"Martin Scorsese\",\n    actors: \"Leonardo DiCaprio, Jonah Hill, Margot Robbie, Matthew McConaughey\",\n    plot: \"Based on the true story of Jordan Belfort, from his rise to a wealthy stock-broker living the high life to his fall involving crime, corruption and the federal government.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxMjgxNTk0MF5BMl5BanBnXkFtZTgwNjIyOTg2MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 83,\n    title: \"Hellboy II: The Golden Army\",\n    year: \"2008\",\n    runtime: \"120\",\n    genres: [\"Action\", \"Adventure\", \"Fantasy\"],\n    director: \"Guillermo del Toro\",\n    actors: \"Ron Perlman, Selma Blair, Doug Jones, John Alexander\",\n    plot: \"The mythical world starts a rebellion against humanity in order to rule the Earth, so Hellboy and his team must save the world from the rebellious creatures.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjA5NzgyMjc2Nl5BMl5BanBnXkFtZTcwOTU3MDI3MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 84,\n    title: \"Sunset Boulevard\",\n    year: \"1950\",\n    runtime: \"110\",\n    genres: [\"Drama\", \"Film-Noir\", \"Romance\"],\n    director: \"Billy Wilder\",\n    actors: \"William Holden, Gloria Swanson, Erich von Stroheim, Nancy Olson\",\n    plot: \"A hack screenwriter writes a screenplay for a former silent-film star who has faded into Hollywood obscurity.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTc3NDYzODAwNV5BMl5BanBnXkFtZTgwODg1MTczMTE@._V1_SX300.jpg\",\n  },\n  {\n    id: 85,\n    title: \"I-See-You.Com\",\n    year: \"2006\",\n    runtime: \"92\",\n    genres: [\"Comedy\"],\n    director: \"Eric Steven Stahl\",\n    actors: \"Beau Bridges, Rosanna Arquette, Mathew Botuchis, Shiri Appleby\",\n    plot: \"A 17-year-old boy buys mini-cameras and displays the footage online at I-see-you.com. The cash rolls in as the site becomes a major hit. Everyone seems to have fun until it all comes crashing down....\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYwMDUzNzA5Nl5BMl5BanBnXkFtZTcwMjQ2Njk3MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 86,\n    title: \"The Grand Budapest Hotel\",\n    year: \"2014\",\n    runtime: \"99\",\n    genres: [\"Adventure\", \"Comedy\", \"Crime\"],\n    director: \"Wes Anderson\",\n    actors: \"Ralph Fiennes, F. Murray Abraham, Mathieu Amalric, Adrien Brody\",\n    plot: \"The adventures of Gustave H, a legendary concierge at a famous hotel from the fictional Republic of Zubrowka between the first and second World Wars, and Zero Moustafa, the lobby boy who becomes his most trusted friend.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMzM5NjUxOTEyMl5BMl5BanBnXkFtZTgwNjEyMDM0MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 87,\n    title: \"The Hitchhiker's Guide to the Galaxy\",\n    year: \"2005\",\n    runtime: \"109\",\n    genres: [\"Adventure\", \"Comedy\", \"Sci-Fi\"],\n    director: \"Garth Jennings\",\n    actors: \"Bill Bailey, Anna Chancellor, Warwick Davis, Yasiin Bey\",\n    plot: 'Mere seconds before the Earth is to be demolished by an alien construction crew, journeyman Arthur Dent is swept off the planet by his friend Ford Prefect, a researcher penning a new edition of \"The Hitchhiker\\'s Guide to the Galaxy.\"',\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMjEwOTk4NjU2MF5BMl5BanBnXkFtZTYwMDA3NzI3._V1_SX300.jpg\",\n  },\n  {\n    id: 88,\n    title: \"Once Upon a Time in America\",\n    year: \"1984\",\n    runtime: \"229\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Sergio Leone\",\n    actors: \"Robert De Niro, James Woods, Elizabeth McGovern, Joe Pesci\",\n    plot: \"A former Prohibition-era Jewish gangster returns to the Lower East Side of Manhattan over thirty years later, where he once again must confront the ghosts and regrets of his old life.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMGFkNWI4MTMtNGQ0OC00MWVmLTk3MTktOGYxN2Y2YWVkZWE2XkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg\",\n  },\n  {\n    id: 89,\n    title: \"Oblivion\",\n    year: \"2013\",\n    runtime: \"124\",\n    genres: [\"Action\", \"Adventure\", \"Mystery\"],\n    director: \"Joseph Kosinski\",\n    actors: \"Tom Cruise, Morgan Freeman, Olga Kurylenko, Andrea Riseborough\",\n    plot: \"A veteran assigned to extract Earth's remaining resources begins to question what he knows about his mission and himself.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQwMDY0MTA4MF5BMl5BanBnXkFtZTcwNzI3MDgxOQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 90,\n    title: \"V for Vendetta\",\n    year: \"2005\",\n    runtime: \"132\",\n    genres: [\"Action\", \"Drama\", \"Thriller\"],\n    director: \"James McTeigue\",\n    actors: \"Natalie Portman, Hugo Weaving, Stephen Rea, Stephen Fry\",\n    plot: 'In a future British tyranny, a shadowy freedom fighter, known only by the alias of \"V\", plots to overthrow it with the help of a young woman.',\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BOTI5ODc3NzExNV5BMl5BanBnXkFtZTcwNzYxNzQzMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 91,\n    title: \"Gattaca\",\n    year: \"1997\",\n    runtime: \"106\",\n    genres: [\"Drama\", \"Sci-Fi\", \"Thriller\"],\n    director: \"Andrew Niccol\",\n    actors: \"Ethan Hawke, Uma Thurman, Gore Vidal, Xander Berkeley\",\n    plot: \"A genetically inferior man assumes the identity of a superior one in order to pursue his lifelong dream of space travel.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNDQxOTc0MzMtZmRlOS00OWQ5LWI2ZDctOTAwNmMwOTYxYzlhXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg\",\n  },\n  {\n    id: 92,\n    title: \"Silver Linings Playbook\",\n    year: \"2012\",\n    runtime: \"122\",\n    genres: [\"Comedy\", \"Drama\", \"Romance\"],\n    director: \"David O. Russell\",\n    actors: \"Bradley Cooper, Jennifer Lawrence, Robert De Niro, Jacki Weaver\",\n    plot: \"After a stint in a mental institution, former teacher Pat Solitano moves back in with his parents and tries to reconcile with his ex-wife. Things get more challenging when Pat meets Tiffany, a mysterious girl with problems of her own.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTM2MTI5NzA3MF5BMl5BanBnXkFtZTcwODExNTc0OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 93,\n    title: \"Alice in Wonderland\",\n    year: \"2010\",\n    runtime: \"108\",\n    genres: [\"Adventure\", \"Family\", \"Fantasy\"],\n    director: \"Tim Burton\",\n    actors: \"Johnny Depp, Mia Wasikowska, Helena Bonham Carter, Anne Hathaway\",\n    plot: \"Nineteen-year-old Alice returns to the magical world from her childhood adventure, where she reunites with her old friends and learns of her true destiny: to end the Red Queen's reign of terror.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTMwNjAxMTc0Nl5BMl5BanBnXkFtZTcwODc3ODk5Mg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 94,\n    title: \"Gandhi\",\n    year: \"1982\",\n    runtime: \"191\",\n    genres: [\"Biography\", \"Drama\"],\n    director: \"Richard Attenborough\",\n    actors: \"Ben Kingsley, Candice Bergen, Edward Fox, John Gielgud\",\n    plot: \"Gandhi's character is fully explained as a man of nonviolence. Through his patience, he is able to drive the British out of the subcontinent. And the stubborn nature of Jinnah and his commitment towards Pakistan is portrayed.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMzJiZDRmOWUtYjE2MS00Mjc1LTg1ZDYtNTQxYWJkZTg1OTM4XkEyXkFqcGdeQXVyNjUwNzk3NDc@._V1_SX300.jpg\",\n  },\n  {\n    id: 95,\n    title: \"Pacific Rim\",\n    year: \"2013\",\n    runtime: \"131\",\n    genres: [\"Action\", \"Adventure\", \"Sci-Fi\"],\n    director: \"Guillermo del Toro\",\n    actors: \"Charlie Hunnam, Diego Klattenhoff, Idris Elba, Rinko Kikuchi\",\n    plot: \"As a war between humankind and monstrous sea creatures wages on, a former pilot and a trainee are paired up to drive a seemingly obsolete special weapon in a desperate effort to save the world from the apocalypse.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY3MTI5NjQ4Nl5BMl5BanBnXkFtZTcwOTU1OTU0OQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 96,\n    title: \"Kiss Kiss Bang Bang\",\n    year: \"2005\",\n    runtime: \"103\",\n    genres: [\"Comedy\", \"Crime\", \"Mystery\"],\n    director: \"Shane Black\",\n    actors: \"Robert Downey Jr., Val Kilmer, Michelle Monaghan, Corbin Bernsen\",\n    plot: \"A murder mystery brings together a private eye, a struggling actress, and a thief masquerading as an actor.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTY5NDExMDA3M15BMl5BanBnXkFtZTYwNTc2MzA3._V1_SX300.jpg\",\n  },\n  {\n    id: 97,\n    title: \"The Quiet American\",\n    year: \"2002\",\n    runtime: \"101\",\n    genres: [\"Drama\", \"Mystery\", \"Romance\"],\n    director: \"Phillip Noyce\",\n    actors: \"Michael Caine, Brendan Fraser, Do Thi Hai Yen, Rade Serbedzija\",\n    plot: \"An older British reporter vies with a young U.S. doctor for the affections of a beautiful Vietnamese woman.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMjE2NTUxNTE3Nl5BMl5BanBnXkFtZTYwNTczMTg5._V1_SX300.jpg\",\n  },\n  {\n    id: 98,\n    title: \"Cloud Atlas\",\n    year: \"2012\",\n    runtime: \"172\",\n    genres: [\"Drama\", \"Sci-Fi\"],\n    director: \"Tom Tykwer, Lana Wachowski, Lilly Wachowski\",\n    actors: \"Tom Hanks, Halle Berry, Jim Broadbent, Hugo Weaving\",\n    plot: \"An exploration of how the actions of individual lives impact one another in the past, present and future, as one soul is shaped from a killer into a hero, and an act of kindness ripples across centuries to inspire a revolution.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTczMTgxMjc4NF5BMl5BanBnXkFtZTcwNjM5MTA2OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 99,\n    title: \"The Impossible\",\n    year: \"2012\",\n    runtime: \"114\",\n    genres: [\"Drama\", \"Thriller\"],\n    director: \"J.A. Bayona\",\n    actors: \"Naomi Watts, Ewan McGregor, Tom Holland, Samuel Joslin\",\n    plot: \"The story of a tourist family in Thailand caught in the destruction and chaotic aftermath of the 2004 Indian Ocean tsunami.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjA5NTA3NzQ5Nl5BMl5BanBnXkFtZTcwOTYxNjY0OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 100,\n    title: \"All Quiet on the Western Front\",\n    year: \"1930\",\n    runtime: \"136\",\n    genres: [\"Drama\", \"War\"],\n    director: \"Lewis Milestone\",\n    actors: \"Louis Wolheim, Lew Ayres, John Wray, Arnold Lucy\",\n    plot: \"A young soldier faces profound disillusionment in the soul-destroying horror of World War I.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNTM5OTg2NDY1NF5BMl5BanBnXkFtZTcwNTQ4MTMwNw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 101,\n    title: \"The English Patient\",\n    year: \"1996\",\n    runtime: \"162\",\n    genres: [\"Drama\", \"Romance\", \"War\"],\n    director: \"Anthony Minghella\",\n    actors:\n      \"Ralph Fiennes, Juliette Binoche, Willem Dafoe, Kristin Scott Thomas\",\n    plot: \"At the close of WWII, a young nurse tends to a badly-burned plane crash victim. His past is shown in flashbacks, revealing an involvement in a fateful love affair.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNDg2OTcxNDE0OF5BMl5BanBnXkFtZTgwOTg2MDM0MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 102,\n    title: \"Dallas Buyers Club\",\n    year: \"2013\",\n    runtime: \"117\",\n    genres: [\"Biography\", \"Drama\"],\n    director: \"Jean-Marc Vallée\",\n    actors: \"Matthew McConaughey, Jennifer Garner, Jared Leto, Denis O'Hare\",\n    plot: \"In 1985 Dallas, electrician and hustler Ron Woodroof works around the system to help AIDS patients get the medication they need after he is diagnosed with the disease.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYwMTA4MzgyNF5BMl5BanBnXkFtZTgwMjEyMjE0MDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 103,\n    title: \"Frida\",\n    year: \"2002\",\n    runtime: \"123\",\n    genres: [\"Biography\", \"Drama\", \"Romance\"],\n    director: \"Julie Taymor\",\n    actors: \"Salma Hayek, Mía Maestro, Alfred Molina, Antonio Banderas\",\n    plot: \"A biography of artist Frida Kahlo, who channeled the pain of a crippling injury and her tempestuous marriage into her work.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTMyODUyMDY1OV5BMl5BanBnXkFtZTYwMDA2OTU3._V1_SX300.jpg\",\n  },\n  {\n    id: 104,\n    title: \"Before Sunrise\",\n    year: \"1995\",\n    runtime: \"105\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Richard Linklater\",\n    actors: \"Ethan Hawke, Julie Delpy, Andrea Eckert, Hanno Pöschl\",\n    plot: \"A young man and woman meet on a train in Europe, and wind up spending one evening together in Vienna. Unfortunately, both know that this will probably be their only night together.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQyMTM3MTQxMl5BMl5BanBnXkFtZTcwMDAzNjQ4Mg@@._V1_SX300.jpg\",\n  },\n  {\n    id: 105,\n    title: \"The Rum Diary\",\n    year: \"2011\",\n    runtime: \"120\",\n    genres: [\"Comedy\", \"Drama\"],\n    director: \"Bruce Robinson\",\n    actors: \"Johnny Depp, Aaron Eckhart, Michael Rispoli, Amber Heard\",\n    plot: \"American journalist Paul Kemp takes on a freelance job in Puerto Rico for a local newspaper during the 1960s and struggles to find a balance between island culture and the expatriates who live there.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTM5ODA4MjYxM15BMl5BanBnXkFtZTcwMTM3NTE5Ng@@._V1_SX300.jpg\",\n  },\n  {\n    id: 106,\n    title: \"The Last Samurai\",\n    year: \"2003\",\n    runtime: \"154\",\n    genres: [\"Action\", \"Drama\", \"History\"],\n    director: \"Edward Zwick\",\n    actors: \"Ken Watanabe, Tom Cruise, William Atherton, Chad Lindberg\",\n    plot: \"An American military advisor embraces the Samurai culture he was hired to destroy after he is captured in battle.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMzkyNzQ1Mzc0NV5BMl5BanBnXkFtZTcwODg3MzUzMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 107,\n    title: \"Chinatown\",\n    year: \"1974\",\n    runtime: \"130\",\n    genres: [\"Drama\", \"Mystery\", \"Thriller\"],\n    director: \"Roman Polanski\",\n    actors: \"Jack Nicholson, Faye Dunaway, John Huston, Perry Lopez\",\n    plot: \"A private detective hired to expose an adulterer finds himself caught up in a web of deceit, corruption and murder.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BN2YyNDE5NzItMjAwNC00MGQxLTllNjktZGIzMWFkZjA3OWQ0XkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_SX300.jpg\",\n  },\n  {\n    id: 108,\n    title: \"Calvary\",\n    year: \"2014\",\n    runtime: \"102\",\n    genres: [\"Comedy\", \"Drama\"],\n    director: \"John Michael McDonagh\",\n    actors: \"Brendan Gleeson, Chris O'Dowd, Kelly Reilly, Aidan Gillen\",\n    plot: \"After he is threatened during a confession, a good-natured priest must battle the dark forces closing in around him.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTc3MjQ1MjE2M15BMl5BanBnXkFtZTgwNTMzNjE4MTE@._V1_SX300.jpg\",\n  },\n  {\n    id: 109,\n    title: \"Before Sunset\",\n    year: \"2004\",\n    runtime: \"80\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Richard Linklater\",\n    actors: \"Ethan Hawke, Julie Delpy, Vernon Dobtcheff, Louise Lemoine Torrès\",\n    plot: \"Nine years after Jesse and Celine first met, they encounter each other again on the French leg of Jesse's book tour.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTQ1MjAwNTM5Ml5BMl5BanBnXkFtZTYwNDM0MTc3._V1_SX300.jpg\",\n  },\n  {\n    id: 110,\n    title: \"Spirited Away\",\n    year: \"2001\",\n    runtime: \"125\",\n    genres: [\"Animation\", \"Adventure\", \"Family\"],\n    director: \"Hayao Miyazaki\",\n    actors: \"Rumi Hiiragi, Miyu Irino, Mari Natsuki, Takashi Naitô\",\n    plot: \"During her family's move to the suburbs, a sullen 10-year-old girl wanders into a world ruled by gods, witches, and spirits, and where humans are changed into beasts.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjYxMDcyMzIzNl5BMl5BanBnXkFtZTYwNDg2MDU3._V1_SX300.jpg\",\n  },\n  {\n    id: 111,\n    title: \"Indochine\",\n    year: \"1992\",\n    runtime: \"159\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Régis Wargnier\",\n    actors: \"Catherine Deneuve, Vincent Perez, Linh Dan Pham, Jean Yanne\",\n    plot: \"This story is set in 1930, at the time when French colonial rule in Indochina is ending. A widowed French woman who works in the rubber fields, raises a Vietnamese princess as if she was ...\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTM1MTkzNzA3NF5BMl5BanBnXkFtZTYwNTI2MzU5._V1_SX300.jpg\",\n  },\n  {\n    id: 112,\n    title: \"Birdman or (The Unexpected Virtue of Ignorance)\",\n    year: \"2014\",\n    runtime: \"119\",\n    genres: [\"Comedy\", \"Drama\", \"Romance\"],\n    director: \"Alejandro G. Iñárritu\",\n    actors: \"Michael Keaton, Emma Stone, Kenny Chin, Jamahl Garrison-Lowe\",\n    plot: \"Illustrated upon the progress of his latest Broadway play, a former popular actor's struggle to cope with his current life as a wasted actor is shown.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BODAzNDMxMzAxOV5BMl5BanBnXkFtZTgwMDMxMjA4MjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 113,\n    title: \"Boyhood\",\n    year: \"2014\",\n    runtime: \"165\",\n    genres: [\"Drama\"],\n    director: \"Richard Linklater\",\n    actors:\n      \"Ellar Coltrane, Patricia Arquette, Elijah Smith, Lorelei Linklater\",\n    plot: \"The life of Mason, from early childhood to his arrival at college.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYzNDc2MDc0N15BMl5BanBnXkFtZTgwOTcwMDQ5MTE@._V1_SX300.jpg\",\n  },\n  {\n    id: 114,\n    title: \"12 Angry Men\",\n    year: \"1957\",\n    runtime: \"96\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Sidney Lumet\",\n    actors: \"Martin Balsam, John Fiedler, Lee J. Cobb, E.G. Marshall\",\n    plot: \"A jury holdout attempts to prevent a miscarriage of justice by forcing his colleagues to reconsider the evidence.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BODQwOTc5MDM2N15BMl5BanBnXkFtZTcwODQxNTEzNA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 115,\n    title: \"The Imitation Game\",\n    year: \"2014\",\n    runtime: \"114\",\n    genres: [\"Biography\", \"Drama\", \"Thriller\"],\n    director: \"Morten Tyldum\",\n    actors:\n      \"Benedict Cumberbatch, Keira Knightley, Matthew Goode, Rory Kinnear\",\n    plot: \"During World War II, mathematician Alan Turing tries to crack the enigma code with help from fellow mathematicians.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNDkwNTEyMzkzNl5BMl5BanBnXkFtZTgwNTAwNzk3MjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 116,\n    title: \"Interstellar\",\n    year: \"2014\",\n    runtime: \"169\",\n    genres: [\"Adventure\", \"Drama\", \"Sci-Fi\"],\n    director: \"Christopher Nolan\",\n    actors: \"Ellen Burstyn, Matthew McConaughey, Mackenzie Foy, John Lithgow\",\n    plot: \"A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 117,\n    title: \"Big Nothing\",\n    year: \"2006\",\n    runtime: \"86\",\n    genres: [\"Comedy\", \"Crime\", \"Thriller\"],\n    director: \"Jean-Baptiste Andrea\",\n    actors: \"David Schwimmer, Simon Pegg, Alice Eve, Natascha McElhone\",\n    plot: \"A frustrated, unemployed teacher joining forces with a scammer and his girlfriend in a blackmailing scheme.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5NTc2NjYwOV5BMl5BanBnXkFtZTcwMzk5OTY0MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 118,\n    title: \"Das Boot\",\n    year: \"1981\",\n    runtime: \"149\",\n    genres: [\"Adventure\", \"Drama\", \"Thriller\"],\n    director: \"Wolfgang Petersen\",\n    actors:\n      \"Jürgen Prochnow, Herbert Grönemeyer, Klaus Wennemann, Hubertus Bengsch\",\n    plot: \"The claustrophobic world of a WWII German U-boat; boredom, filth, and sheer terror.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjE5Mzk5OTQ0Nl5BMl5BanBnXkFtZTYwNzUwMTQ5._V1_SX300.jpg\",\n  },\n  {\n    id: 119,\n    title: \"Shrek 2\",\n    year: \"2004\",\n    runtime: \"93\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Andrew Adamson, Kelly Asbury, Conrad Vernon\",\n    actors: \"Mike Myers, Eddie Murphy, Cameron Diaz, Julie Andrews\",\n    plot: \"Princess Fiona's parents invite her and Shrek to dinner to celebrate her marriage. If only they knew the newlyweds were both ogres.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTk4MTMwNjI4M15BMl5BanBnXkFtZTcwMjExMzUyMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 120,\n    title: \"Sin City\",\n    year: \"2005\",\n    runtime: \"124\",\n    genres: [\"Crime\", \"Thriller\"],\n    director: \"Frank Miller, Robert Rodriguez, Quentin Tarantino\",\n    actors: \"Jessica Alba, Devon Aoki, Alexis Bledel, Powers Boothe\",\n    plot: \"A film that explores the dark and miserable town, Basin City, and tells the story of three different people, all caught up in violent corruption.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BODZmYjMwNzEtNzVhNC00ZTRmLTk2M2UtNzE1MTQ2ZDAxNjc2XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg\",\n  },\n  {\n    id: 121,\n    title: \"Nebraska\",\n    year: \"2013\",\n    runtime: \"115\",\n    genres: [\"Adventure\", \"Comedy\", \"Drama\"],\n    director: \"Alexander Payne\",\n    actors: \"Bruce Dern, Will Forte, June Squibb, Bob Odenkirk\",\n    plot: \"An aging, booze-addled father makes the trip from Montana to Nebraska with his estranged son in order to claim a million-dollar Mega Sweepstakes Marketing prize.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTU2Mjk2NDkyMl5BMl5BanBnXkFtZTgwNTk0NzcyMDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 122,\n    title: \"Shrek\",\n    year: \"2001\",\n    runtime: \"90\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Andrew Adamson, Vicky Jenson\",\n    actors: \"Mike Myers, Eddie Murphy, Cameron Diaz, John Lithgow\",\n    plot: \"After his swamp is filled with magical creatures, an ogre agrees to rescue a princess for a villainous lord in order to get his land back.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTk2NTE1NTE0M15BMl5BanBnXkFtZTgwNjY4NTYxMTE@._V1_SX300.jpg\",\n  },\n  {\n    id: 123,\n    title: \"Mr. & Mrs. Smith\",\n    year: \"2005\",\n    runtime: \"120\",\n    genres: [\"Action\", \"Comedy\", \"Crime\"],\n    director: \"Doug Liman\",\n    actors: \"Brad Pitt, Angelina Jolie, Vince Vaughn, Adam Brody\",\n    plot: \"A bored married couple is surprised to learn that they are both assassins hired by competing agencies to kill each other.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTUxMzcxNzQzOF5BMl5BanBnXkFtZTcwMzQxNjUyMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 124,\n    title: \"Original Sin\",\n    year: \"2001\",\n    runtime: \"116\",\n    genres: [\"Drama\", \"Mystery\", \"Romance\"],\n    director: \"Michael Cristofer\",\n    actors: \"Antonio Banderas, Angelina Jolie, Thomas Jane, Jack Thompson\",\n    plot: \"A woman along with her lover, plan to con a rich man by marrying him and on earning his trust running away with all his money. Everything goes as planned until she actually begins to fall in love with him.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BODg3Mjg0MDY4M15BMl5BanBnXkFtZTcwNjY5MDQ2NA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 125,\n    title: \"Shrek Forever After\",\n    year: \"2010\",\n    runtime: \"93\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Mike Mitchell\",\n    actors: \"Mike Myers, Eddie Murphy, Cameron Diaz, Antonio Banderas\",\n    plot: \"Rumpelstiltskin tricks a mid-life crisis burdened Shrek into allowing himself to be erased from existence and cast in a dark alternate timeline where Rumpel rules supreme.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTY0OTU1NzkxMl5BMl5BanBnXkFtZTcwMzI2NDUzMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 126,\n    title: \"Before Midnight\",\n    year: \"2013\",\n    runtime: \"109\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Richard Linklater\",\n    actors:\n      \"Ethan Hawke, Julie Delpy, Seamus Davey-Fitzpatrick, Jennifer Prior\",\n    plot: \"We meet Jesse and Celine nine years on in Greece. Almost two decades have passed since their first meeting on that train bound for Vienna.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMjA5NzgxODE2NF5BMl5BanBnXkFtZTcwNTI1NTI0OQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 127,\n    title: \"Despicable Me\",\n    year: \"2010\",\n    runtime: \"95\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Pierre Coffin, Chris Renaud\",\n    actors: \"Steve Carell, Jason Segel, Russell Brand, Julie Andrews\",\n    plot: \"When a criminal mastermind uses a trio of orphan girls as pawns for a grand scheme, he finds their love is profoundly changing him for the better.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY3NjY0MTQ0Nl5BMl5BanBnXkFtZTcwMzQ2MTc0Mw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 128,\n    title: \"Troy\",\n    year: \"2004\",\n    runtime: \"163\",\n    genres: [\"Adventure\"],\n    director: \"Wolfgang Petersen\",\n    actors: \"Julian Glover, Brian Cox, Nathan Jones, Adoni Maropis\",\n    plot: \"An adaptation of Homer's great epic, the film follows the assault on Troy by the united Greek forces and chronicles the fates of the men involved.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTk5MzU1MDMwMF5BMl5BanBnXkFtZTcwNjczODMzMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 129,\n    title: \"The Hobbit: An Unexpected Journey\",\n    year: \"2012\",\n    runtime: \"169\",\n    genres: [\"Adventure\", \"Fantasy\"],\n    director: \"Peter Jackson\",\n    actors: \"Ian McKellen, Martin Freeman, Richard Armitage, Ken Stott\",\n    plot: \"A reluctant hobbit, Bilbo Baggins, sets out to the Lonely Mountain with a spirited group of dwarves to reclaim their mountain home - and the gold within it - from the dragon Smaug.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTcwNTE4MTUxMl5BMl5BanBnXkFtZTcwMDIyODM4OA@@._V1_SX300.jpg\",\n  },\n  {\n    id: 130,\n    title: \"The Great Gatsby\",\n    year: \"2013\",\n    runtime: \"143\",\n    genres: [\"Drama\", \"Romance\"],\n    director: \"Baz Luhrmann\",\n    actors: \"Lisa Adam, Frank Aldridge, Amitabh Bachchan, Steve Bisley\",\n    plot: \"A writer and wall street trader, Nick, finds himself drawn to the past and lifestyle of his millionaire neighbor, Jay Gatsby.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTkxNTk1ODcxNl5BMl5BanBnXkFtZTcwMDI1OTMzOQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 131,\n    title: \"Ice Age\",\n    year: \"2002\",\n    runtime: \"81\",\n    genres: [\"Animation\", \"Adventure\", \"Comedy\"],\n    director: \"Chris Wedge, Carlos Saldanha\",\n    actors: \"Ray Romano, John Leguizamo, Denis Leary, Goran Visnjic\",\n    plot: \"Set during the Ice Age, a sabertooth tiger, a sloth, and a wooly mammoth find a lost human infant, and they try to return him to his tribe.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjEyNzI1ODA0MF5BMl5BanBnXkFtZTYwODIxODY3._V1_SX300.jpg\",\n  },\n  {\n    id: 132,\n    title: \"The Lord of the Rings: The Fellowship of the Ring\",\n    year: \"2001\",\n    runtime: \"178\",\n    genres: [\"Action\", \"Adventure\", \"Drama\"],\n    director: \"Peter Jackson\",\n    actors: \"Alan Howard, Noel Appleby, Sean Astin, Sala Baker\",\n    plot: \"A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle Earth from the Dark Lord Sauron.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNTEyMjAwMDU1OV5BMl5BanBnXkFtZTcwNDQyNTkxMw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 133,\n    title: \"The Lord of the Rings: The Two Towers\",\n    year: \"2002\",\n    runtime: \"179\",\n    genres: [\"Action\", \"Adventure\", \"Drama\"],\n    director: \"Peter Jackson\",\n    actors: \"Bruce Allpress, Sean Astin, John Bach, Sala Baker\",\n    plot: \"While Frodo and Sam edge closer to Mordor with the help of the shifty Gollum, the divided fellowship makes a stand against Sauron's new ally, Saruman, and his hordes of Isengard.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTAyNDU0NjY4NTheQTJeQWpwZ15BbWU2MDk4MTY2Nw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 134,\n    title: \"Ex Machina\",\n    year: \"2015\",\n    runtime: \"108\",\n    genres: [\"Drama\", \"Mystery\", \"Sci-Fi\"],\n    director: \"Alex Garland\",\n    actors: \"Domhnall Gleeson, Corey Johnson, Oscar Isaac, Alicia Vikander\",\n    plot: \"A young programmer is selected to participate in a ground-breaking experiment in synthetic intelligence by evaluating the human qualities of a breath-taking humanoid A.I.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTUxNzc0OTIxMV5BMl5BanBnXkFtZTgwNDI3NzU2NDE@._V1_SX300.jpg\",\n  },\n  {\n    id: 135,\n    title: \"The Theory of Everything\",\n    year: \"2014\",\n    runtime: \"123\",\n    genres: [\"Biography\", \"Drama\", \"Romance\"],\n    director: \"James Marsh\",\n    actors: \"Eddie Redmayne, Felicity Jones, Tom Prior, Sophie Perry\",\n    plot: \"A look at the relationship between the famous physicist Stephen Hawking and his wife.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTAwMTU4MDA3NDNeQTJeQWpwZ15BbWU4MDk4NTMxNTIx._V1_SX300.jpg\",\n  },\n  {\n    id: 136,\n    title: \"Shogun\",\n    year: \"1980\",\n    runtime: \"60\",\n    genres: [\"Adventure\", \"Drama\", \"History\"],\n    director: \"N/A\",\n    actors: \"Richard Chamberlain, Toshirô Mifune, Yôko Shimada, Furankî Sakai\",\n    plot: \"A English navigator becomes both a player and pawn in the complex political games in feudal Japan.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY1ODI4NzYxMl5BMl5BanBnXkFtZTcwNDA4MzUxMQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 137,\n    title: \"Spotlight\",\n    year: \"2015\",\n    runtime: \"128\",\n    genres: [\"Biography\", \"Crime\", \"Drama\"],\n    director: \"Tom McCarthy\",\n    actors: \"Mark Ruffalo, Michael Keaton, Rachel McAdams, Liev Schreiber\",\n    plot: \"The true story of how the Boston Globe uncovered the massive scandal of child molestation and cover-up within the local Catholic Archdiocese, shaking the entire Catholic Church to its core.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIyOTM5OTIzNV5BMl5BanBnXkFtZTgwMDkzODE2NjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 138,\n    title: \"Vertigo\",\n    year: \"1958\",\n    runtime: \"128\",\n    genres: [\"Mystery\", \"Romance\", \"Thriller\"],\n    director: \"Alfred Hitchcock\",\n    actors: \"James Stewart, Kim Novak, Barbara Bel Geddes, Tom Helmore\",\n    plot: \"A San Francisco detective suffering from acrophobia investigates the strange activities of an old friend's wife, all the while becoming dangerously obsessed with her.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BNzY0NzQyNzQzOF5BMl5BanBnXkFtZTcwMTgwNTk4OQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 139,\n    title: \"Whiplash\",\n    year: \"2014\",\n    runtime: \"107\",\n    genres: [\"Drama\", \"Music\"],\n    director: \"Damien Chazelle\",\n    actors: \"Miles Teller, J.K. Simmons, Paul Reiser, Melissa Benoist\",\n    plot: \"A promising young drummer enrolls at a cut-throat music conservatory where his dreams of greatness are mentored by an instructor who will stop at nothing to realize a student's potential.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTU4OTQ3MDUyMV5BMl5BanBnXkFtZTgwOTA2MjU0MjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 140,\n    title: \"The Lives of Others\",\n    year: \"2006\",\n    runtime: \"137\",\n    genres: [\"Drama\", \"Thriller\"],\n    director: \"Florian Henckel von Donnersmarck\",\n    actors: \"Martina Gedeck, Ulrich Mühe, Sebastian Koch, Ulrich Tukur\",\n    plot: \"In 1984 East Berlin, an agent of the secret police, conducting surveillance on a writer and his lover, finds himself becoming increasingly absorbed by their lives.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BNDUzNjYwNDYyNl5BMl5BanBnXkFtZTcwNjU3ODQ0MQ@@._V1_SX300.jpg\",\n  },\n  {\n    id: 141,\n    title: \"Hotel Rwanda\",\n    year: \"2004\",\n    runtime: \"121\",\n    genres: [\"Drama\", \"History\", \"War\"],\n    director: \"Terry George\",\n    actors: \"Xolani Mali, Don Cheadle, Desmond Dube, Hakeem Kae-Kazim\",\n    plot: \"Paul Rusesabagina was a hotel manager who housed over a thousand Tutsi refugees during their struggle against the Hutu militia in Rwanda.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTI2MzQyNTc1M15BMl5BanBnXkFtZTYwMjExNjc3._V1_SX300.jpg\",\n  },\n  {\n    id: 142,\n    title: \"The Martian\",\n    year: \"2015\",\n    runtime: \"144\",\n    genres: [\"Adventure\", \"Drama\", \"Sci-Fi\"],\n    director: \"Ridley Scott\",\n    actors: \"Matt Damon, Jessica Chastain, Kristen Wiig, Jeff Daniels\",\n    plot: \"An astronaut becomes stranded on Mars after his team assume him dead, and must rely on his ingenuity to find a way to signal to Earth that he is alive.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMTc2MTQ3MDA1Nl5BMl5BanBnXkFtZTgwODA3OTI4NjE@._V1_SX300.jpg\",\n  },\n  {\n    id: 143,\n    title: \"To Kill a Mockingbird\",\n    year: \"1962\",\n    runtime: \"129\",\n    genres: [\"Crime\", \"Drama\"],\n    director: \"Robert Mulligan\",\n    actors: \"Gregory Peck, John Megna, Frank Overton, Rosemary Murphy\",\n    plot: \"Atticus Finch, a lawyer in the Depression-era South, defends a black man against an undeserved rape charge, and his kids against prejudice.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMjA4MzI1NDY2Nl5BMl5BanBnXkFtZTcwMTcyODc5Mw@@._V1_SX300.jpg\",\n  },\n  {\n    id: 144,\n    title: \"The Hateful Eight\",\n    year: \"2015\",\n    runtime: \"187\",\n    genres: [\"Crime\", \"Drama\", \"Mystery\"],\n    director: \"Quentin Tarantino\",\n    actors:\n      \"Samuel L. Jackson, Kurt Russell, Jennifer Jason Leigh, Walton Goggins\",\n    plot: \"In the dead of a Wyoming winter, a bounty hunter and his prisoner find shelter in a cabin currently inhabited by a collection of nefarious characters.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BMjA1MTc1NTg5NV5BMl5BanBnXkFtZTgwOTM2MDEzNzE@._V1_SX300.jpg\",\n  },\n  {\n    id: 145,\n    title: \"A Separation\",\n    year: \"2011\",\n    runtime: \"123\",\n    genres: [\"Drama\", \"Mystery\"],\n    director: \"Asghar Farhadi\",\n    actors: \"Peyman Moaadi, Leila Hatami, Sareh Bayat, Shahab Hosseini\",\n    plot: \"A married couple are faced with a difficult decision - to improve the life of their child by moving to another country or to stay in Iran and look after a deteriorating parent who has Alzheimer's disease.\",\n    posterUrl:\n      \"http://ia.media-imdb.com/images/M/MV5BMTYzMzU4NDUwOF5BMl5BanBnXkFtZTcwMTM5MjA5Ng@@._V1_SX300.jpg\",\n  },\n  {\n    id: 146,\n    title: \"The Big Short\",\n    year: \"2015\",\n    runtime: \"130\",\n    genres: [\"Biography\", \"Comedy\", \"Drama\"],\n    director: \"Adam McKay\",\n    actors: \"Ryan Gosling, Rudy Eisenzopf, Casey Groves, Charlie Talbert\",\n    plot: \"Four denizens in the world of high-finance predict the credit and housing bubble collapse of the mid-2000s, and decide to take on the big banks for their greed and lack of foresight.\",\n    posterUrl:\n      \"https://images-na.ssl-images-amazon.com/images/M/MV5BNDc4MThhN2EtZjMzNC00ZDJmLThiZTgtNThlY2UxZWMzNjdkXkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_SX300.jpg\",\n  },\n];\n"
  },
  {
    "path": "ui/src/pages/misc/TaskQueue.jsx",
    "content": "import { useRouteMatch } from \"react-router-dom\";\nimport sharedStyles from \"../styles\";\nimport { usePollData, useQueueSizes, useTaskNames } from \"../../data/task\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { Helmet } from \"react-helmet\";\nimport { usePushHistory } from \"../../components/NavLink\";\nimport { formatRelative } from \"date-fns\";\n\nimport {\n  Paper,\n  DataTable,\n  LinearProgress,\n  Heading,\n  Dropdown,\n} from \"../../components\";\nimport _ from \"lodash\";\nimport { timestampRenderer } from \"../../utils/helpers\";\n\nconst useStyles = makeStyles(sharedStyles);\n\nfunction getSizesMap(sizes) {\n  const retval = new Map();\n  for (let row of sizes) {\n    if (row.isSuccess) {\n      retval.set(row.data.domain, row.data.size);\n    }\n  }\n  return retval;\n}\n\nexport default function TaskDefinition() {\n  const taskNames = useTaskNames();\n  const pushHistory = usePushHistory();\n  const classes = useStyles();\n  const match = useRouteMatch();\n  const taskName = match.params.name || \"\";\n\n  const { data: pollData, isFetching } = usePollData(taskName);\n  const domains = pollData ? pollData.map((row) => row.domain) : null;\n  const sizes = useQueueSizes(taskName, domains);\n  const sizesMap = getSizesMap(sizes);\n  const now = new Date();\n\n  function setTaskName(name) {\n    if (name === null) {\n      name = \"\";\n    }\n    pushHistory(`/taskQueue/${name}`);\n  }\n\n  return (\n    <div className={classes.wrapper}>\n      <Helmet>\n        <title>Conductor UI - Task Queue</title>\n      </Helmet>\n      <div className={classes.header} style={{ paddingBottom: 20 }}>\n        <Heading level={3} style={{ marginBottom: 30 }}>\n          Task Queues\n        </Heading>\n        <Dropdown\n          label=\"Select a Task Name\"\n          style={{ width: 500 }}\n          options={taskNames}\n          onChange={(evt, val) => setTaskName(val)}\n          disableClearable\n          getOptionSelected={(option, value) => {\n            // Accept empty string\n            if (value === \"\") return false;\n            return value === option;\n          }}\n          value={taskName}\n        />\n      </div>\n      {isFetching && <LinearProgress />}\n      <div className={classes.tabContent}>\n        {!_.isUndefined(pollData) && (\n          <Paper>\n            <DataTable\n              title=\"Poll Status by Domain\"\n              defaultShowColumns={[\n                \"workerId\",\n                \"domain\",\n                \"lastPollTime\",\n                \"queueSize\",\n              ]}\n              default\n              data={pollData}\n              columns={[\n                {\n                  name: \"domain\",\n                  label: \"Domain\",\n                  renderer: (domain) =>\n                    _.isEmpty(domain) ? \"(Domain not set)\" : domain,\n                },\n                { name: \"workerId\", label: \"Last Polled Worker\" },\n                {\n                  name: \"lastPollTime\",\n                  label: \"Last Poll Time\",\n                  renderer: (time) =>\n                    `${timestampRenderer(time)} (${formatRelative(time, now)})`,\n                },\n                {\n                  name: \"domain\",\n                  id: \"queueSize\",\n                  label: \"Queue Size\",\n                  renderer: (domain) => sizesMap.get(domain),\n                },\n              ]}\n            />\n          </Paper>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/styles.js",
    "content": "import { colors } from \"../theme/variables\";\n\nexport default {\n  wrapper: {\n    overflowY: \"scroll\",\n    overflowX: \"hidden\",\n    height: \"100%\",\n  },\n  padded: {\n    padding: 30,\n  },\n  header: {\n    backgroundColor: colors.gray14,\n    padding: \"20px 30px 0 30px\",\n    zIndex: 1,\n  },\n  paddingBottom: {\n    paddingBottom: 25,\n  },\n  tabContent: {\n    padding: 30,\n  },\n  buttonRow: {\n    marginBottom: 15,\n    display: \"flex\",\n    justifyContent: \"flex-end\",\n  },\n  field: {\n    marginBottom: 15,\n  },\n};\n"
  },
  {
    "path": "ui/src/pages/workbench/ExecutionHistory.jsx",
    "content": "import {\n  List,\n  ListItem,\n  ListItemText,\n  Toolbar,\n  IconButton,\n} from \"@material-ui/core\";\nimport { StatusBadge, Text, NavLink } from \"../../components\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { colors } from \"../../theme/variables\";\nimport _ from \"lodash\";\nimport { useInvalidateWorkflows, useWorkflowsByIds } from \"../../data/workflow\";\nimport { formatRelative } from \"date-fns\";\nimport RefreshIcon from \"@material-ui/icons/Refresh\";\n\nconst useStyles = makeStyles({\n  sidebar: {\n    width: 360,\n    border: \"0px solid rgba(0, 0, 0, 0)\",\n    zIndex: 1,\n    boxShadow: \"0 2px 4px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%)\",\n    background: \"#fff\",\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n  toolbar: {\n    backgroundColor: colors.gray14,\n  },\n  list: {\n    overflowY: \"auto\",\n    flex: 1,\n  },\n});\n\nexport default function ExecutionHistory({ run }) {\n  const classes = useStyles();\n  const workflowRecords = run ? run.workflowRecords : [];\n  const workflowIds = workflowRecords.map((record) => `${record.workflowId}`);\n  const results =\n    useWorkflowsByIds(workflowIds, {\n      staleTime: 60000,\n    }) || [];\n  const resultsMap = new Map(\n    results\n      .filter((r) => r.isSuccess)\n      .map((result) => [result.data.workflowId, result.data])\n  );\n  const invalidateWorkflows = useInvalidateWorkflows();\n\n  function handleRefresh() {\n    invalidateWorkflows(workflowIds);\n  }\n\n  return (\n    <div className={classes.sidebar}>\n      <Toolbar className={classes.toolbar}>\n        <Text level={0} className={classes.title}>\n          Execution History\n        </Text>\n        <IconButton onClick={handleRefresh}>\n          <RefreshIcon />\n        </IconButton>\n      </Toolbar>\n      <List className={classes.list}>\n        {Array.from(resultsMap.values()).map((workflow) => (\n          <ListItem key={workflow.workflowId}>\n            <ListItemText\n              primary={\n                <NavLink path={`/execution/${workflow.workflowId}`} newTab>\n                  {workflow.workflowId}\n                </NavLink>\n              }\n              secondary={\n                <span>\n                  <StatusBadge status={workflow.status} size=\"small\" />{\" \"}\n                  {formatRelative(new Date(workflow.startTime), new Date())}\n                </span>\n              }\n              secondaryTypographyProps={{ component: \"div\" }}\n            />\n          </ListItem>\n        ))}\n        {_.isEmpty(workflowRecords) && (\n          <ListItem>\n            <ListItemText>No execution history.</ListItemText>\n          </ListItem>\n        )}\n      </List>\n    </div>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/workbench/RunHistory.tsx",
    "content": "import { useImperativeHandle, useState, forwardRef } from \"react\";\nimport { useLocalStorage } from \"../../utils/localstorage\";\nimport { Text } from \"../../components\";\nimport {\n  List,\n  ListItem,\n  ListItemText,\n  ListItemSecondaryAction,\n  Toolbar,\n  IconButton,\n} from \"@material-ui/core\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { immutableReplaceAt } from \"../../utils/helpers\";\nimport { formatRelative } from \"date-fns\";\nimport DeleteIcon from \"@material-ui/icons/DeleteForever\";\nimport { colors } from \"../../theme/variables\";\nimport CloseIcon from \"@material-ui/icons/Close\";\nimport _ from \"lodash\";\nimport { useEnv } from \"../../plugins/env\";\n\nconst useStyles = makeStyles({\n  sidebar: {\n    width: 300,\n    border: \"0px solid rgba(0, 0, 0, 0)\",\n    zIndex: 1,\n    boxShadow: \"0 2px 4px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%)\",\n    background: \"#fff\",\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n  toolbar: {\n    backgroundColor: colors.gray14,\n  },\n  title: {\n    fontWeight: \"bold\",\n    flex: 1,\n  },\n  list: {\n    overflowY: \"auto\",\n    cursor: \"pointer\",\n    flex: 1,\n  },\n});\ntype RunPayload = any;\ntype RunEntry = {\n  runPayload: RunPayload;\n  workflowRecords: WorkflowRecord[];\n  createTime: number;\n};\ntype WorkflowRecord = {\n  workflowId: string;\n};\n\ntype RunHistoryProps = {\n  onRunSelected: (run: RunEntry | undefined) => void;\n};\n\nconst RUN_HISTORY_SCHEMA_VER = 1;\n\nconst RunHistory = forwardRef((props: RunHistoryProps, ref) => {\n  const { onRunSelected } = props;\n  const { stack } = useEnv();\n  const classes = useStyles();\n  const [selectedCreateTime, setSelectedCreateTime] = useState<\n    number | undefined\n  >(undefined);\n  const [runHistory, setRunHistory]: readonly [\n    RunEntry[],\n    (v: RunEntry[]) => void\n  ] = useLocalStorage(`runHistory_${stack}_${RUN_HISTORY_SCHEMA_VER}`, []);\n\n  useImperativeHandle(ref, () => ({\n    pushNewRun: (runPayload: RunPayload) => {\n      const createTime = new Date().getTime();\n      const newRun = {\n        runPayload: runPayload,\n        workflowRecords: [],\n        createTime: createTime,\n      };\n      setRunHistory([newRun, ...runHistory]);\n      setSelectedCreateTime(createTime);\n\n      return newRun;\n    },\n    updateRun: (createTime: number, workflowId: string) => {\n      const idx = runHistory.findIndex((v) => v.createTime === createTime);\n      const currRun = runHistory[idx];\n      const oldRecords = currRun.workflowRecords;\n      const updatedRun = {\n        runPayload: currRun.runPayload,\n        workflowRecords: [\n          {\n            workflowId: workflowId,\n          },\n          ...oldRecords,\n        ],\n        createTime: currRun.createTime,\n      };\n\n      setRunHistory(immutableReplaceAt(runHistory, idx, updatedRun));\n      onRunSelected(updatedRun);\n    },\n  }));\n\n  function handleSelectRun(run: RunEntry) {\n    if (onRunSelected) onRunSelected(run);\n    setSelectedCreateTime(run.createTime);\n  }\n\n  function handleDeleteAll() {\n    if (window.confirm(\"Delete all run history in this browser?\")) {\n      setRunHistory([]);\n    }\n  }\n\n  function handleDeleteItem(run: RunEntry) {\n    const newHistory = runHistory.filter(\n      (v) => v.createTime !== run.createTime\n    );\n    if (newHistory.length > 0) {\n      setSelectedCreateTime(newHistory[0].createTime);\n      onRunSelected(newHistory[0]);\n    } else {\n      console.log(\"Empty history\");\n      setSelectedCreateTime(undefined);\n      onRunSelected(undefined);\n    }\n    setRunHistory(newHistory);\n  }\n\n  return (\n    <div className={classes.sidebar}>\n      <Toolbar className={classes.toolbar}>\n        <Text level={0} className={classes.title}>\n          Run History\n        </Text>\n        <IconButton onClick={handleDeleteAll}>\n          <DeleteIcon />\n        </IconButton>\n      </Toolbar>\n      <List className={classes.list}>\n        {runHistory.map((run) => (\n          <ListItem\n            key={run.createTime}\n            selected={selectedCreateTime === run.createTime}\n            onClick={() => handleSelectRun(run)}\n          >\n            <ListItemText\n              primary={run.runPayload.name}\n              secondary={formatRelative(new Date(run.createTime), new Date())}\n            />\n            <ListItemSecondaryAction>\n              <IconButton edge=\"end\" onClick={() => handleDeleteItem(run)}>\n                <CloseIcon />\n              </IconButton>\n            </ListItemSecondaryAction>\n          </ListItem>\n        ))}\n        {_.isEmpty(runHistory) && <ListItem>No saved runs.</ListItem>}\n      </List>\n    </div>\n  );\n});\n\nexport default RunHistory;\n"
  },
  {
    "path": "ui/src/pages/workbench/Workbench.jsx",
    "content": "import { useState, useRef } from \"react\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport { Helmet } from \"react-helmet\";\nimport RunHistory from \"./RunHistory\";\nimport WorkbenchForm from \"./WorkbenchForm\";\nimport { colors } from \"../../theme/variables\";\nimport { useStartWorkflow } from \"../../data/workflow\";\nimport ExecutionHistory from \"./ExecutionHistory\";\n\nconst useStyles = makeStyles({\n  wrapper: {\n    height: \"100%\",\n    overflow: \"hidden\",\n    display: \"flex\",\n    flexDirection: \"row\",\n    position: \"relative\",\n  },\n  name: {\n    width: \"50%\",\n  },\n  submitButton: {\n    float: \"right\",\n  },\n  toolbar: {\n    backgroundColor: colors.gray14,\n  },\n  workflowName: {\n    fontWeight: \"bold\",\n  },\n  main: {\n    flex: 1,\n    display: \"flex\",\n    flexDirection: \"column\",\n  },\n  row: {\n    display: \"flex\",\n    flexDirection: \"row\",\n  },\n  fields: {\n    margin: 30,\n    flex: 1,\n    display: \"flex\",\n    flexDirection: \"column\",\n    gap: 15,\n  },\n  runInfo: {\n    marginLeft: -350,\n  },\n});\n\nexport default function Workbench() {\n  const classes = useStyles();\n\n  const runHistoryRef = useRef();\n  const [run, setRun] = useState(undefined);\n\n  const { mutate: startWorkflow } = useStartWorkflow({\n    onSuccess: (workflowId, variables) => {\n      runHistoryRef.current.updateRun(variables.createTime, workflowId);\n    },\n  });\n\n  const handleRunSelect = (run) => {\n    setRun(run);\n  };\n\n  const handleSaveRun = (runPayload) => {\n    const newRun = runHistoryRef.current.pushNewRun(runPayload);\n    setRun(newRun);\n    return newRun;\n  };\n\n  const handleExecuteRun = (createTime, runPayload) => {\n    startWorkflow({\n      createTime,\n      body: runPayload,\n    });\n  };\n\n  return (\n    <>\n      <Helmet>\n        <title>Conductor UI - Workbench</title>\n      </Helmet>\n\n      <div className={classes.wrapper}>\n        <RunHistory ref={runHistoryRef} onRunSelected={handleRunSelect} />\n\n        <WorkbenchForm\n          selectedRun={run}\n          saveRun={handleSaveRun}\n          executeRun={handleExecuteRun}\n        />\n        <ExecutionHistory run={run} />\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "ui/src/pages/workbench/WorkbenchForm.jsx",
    "content": "import { Text, Pill } from \"../../components\";\nimport { Toolbar, IconButton, Tooltip } from \"@material-ui/core\";\nimport FormikInput from \"../../components/formik/FormikInput\";\nimport FormikJsonInput from \"../../components/formik/FormikJsonInput\";\nimport { makeStyles } from \"@material-ui/styles\";\nimport _ from \"lodash\";\nimport { Form, setNestedObjectValues, withFormik } from \"formik\";\nimport { useWorkflowDef } from \"../../data/workflow\";\nimport FormikVersionDropdown from \"../../components/formik/FormikVersionDropdown\";\nimport PlayArrowIcon from \"@material-ui/icons/PlayArrow\";\nimport PlaylistAddIcon from \"@material-ui/icons/PlaylistAdd\";\nimport SaveIcon from \"@material-ui/icons/Save\";\nimport { colors } from \"../../theme/variables\";\nimport { timestampRenderer } from \"../../utils/helpers\";\nimport * as Yup from \"yup\";\nimport FormikWorkflowNameInput from \"../../components/formik/FormikWorkflowNameInput\";\n\nconst useStyles = makeStyles({\n  name: {\n    width: \"50%\",\n  },\n  submitButton: {\n    float: \"right\",\n  },\n  toolbar: {\n    backgroundColor: colors.gray14,\n  },\n  workflowName: {\n    fontWeight: \"bold\",\n  },\n  main: {\n    flex: 1,\n    display: \"flex\",\n    flexDirection: \"column\",\n    overflow: \"auto\",\n  },\n  fields: {\n    width: \"100%\",\n    padding: 30,\n    flex: 1,\n    display: \"flex\",\n    flexDirection: \"column\",\n    overflowX: \"hidden\",\n    overflowY: \"auto\",\n    gap: 15,\n  },\n});\n\nYup.addMethod(Yup.string, \"isJson\", function () {\n  return this.test(\"is-json\", \"is not valid json\", (value) => {\n    if (_.isEmpty(value)) return true;\n\n    try {\n      JSON.parse(value);\n    } catch (e) {\n      return false;\n    }\n    return true;\n  });\n});\nconst validationSchema = Yup.object({\n  workflowName: Yup.string().required(\"Workflow Name is required\"),\n  workflowInput: Yup.string().isJson(),\n  taskToDomain: Yup.string().isJson(),\n});\n\nexport default withFormik({\n  enableReinitialize: true,\n  mapPropsToValues: ({ selectedRun }) =>\n    runPayloadToFormData(_.get(selectedRun, \"runPayload\")),\n  validationSchema: validationSchema,\n})(WorkbenchForm);\n\nfunction WorkbenchForm(props) {\n  const {\n    values,\n    validateForm,\n    setTouched,\n    setFieldValue,\n    dirty,\n    selectedRun,\n    saveRun,\n    executeRun,\n  } = props;\n  const classes = useStyles();\n  const { workflowName, workflowVersion } = values;\n  const createTime = selectedRun ? selectedRun.createTime : undefined;\n\n  const { refetch } = useWorkflowDef(workflowName, workflowVersion, null, {\n    onSuccess: populateInput,\n    enabled: false,\n  });\n\n  function triggerPopulateInput() {\n    refetch();\n  }\n\n  function populateInput(workflowDef) {\n    let bootstrap = {};\n\n    if (!_.isEmpty(values.workflowInput)) {\n      const existing = JSON.parse(values.workflowInput);\n      bootstrap = _.pickBy(existing, (v) => v !== \"\");\n    }\n\n    if (workflowDef.inputParameters) {\n      for (let param of workflowDef.inputParameters) {\n        if (!_.has(bootstrap, param)) {\n          bootstrap[param] = \"\";\n        }\n      }\n\n      setFieldValue(\"workflowInput\", JSON.stringify(bootstrap, null, 2));\n    }\n  }\n\n  function handleRun() {\n    validateForm().then((errors) => {\n      if (Object.keys(errors).length === 0) {\n        const payload = formDataToRunPayload(values);\n        if (!dirty && createTime) {\n          console.log(\"Executing pre-existing run. Append workflowRecord\");\n          executeRun(createTime, payload);\n        } else {\n          console.log(\"Executing new run. Save first then execute\");\n          const newRun = saveRun(payload);\n          executeRun(newRun.createTime, payload);\n        }\n      } else {\n        // Handle validation error manually (not using handleSubmit)\n        setTouched(setNestedObjectValues(errors, true));\n      }\n    });\n  }\n\n  function handleSave() {\n    validateForm().then((errors) => {\n      if (Object.keys(errors).length === 0) {\n        const payload = formDataToRunPayload(values);\n        saveRun(payload);\n      } else {\n        setTouched(setNestedObjectValues(errors, true));\n      }\n    });\n  }\n\n  return (\n    <Form className={classes.main}>\n      <Toolbar className={classes.toolbar}>\n        <Text className={classes.workflowName}>Workflow Workbench</Text>\n        <Tooltip title=\"Execute Workflow\">\n          <IconButton onClick={handleRun}>\n            <PlayArrowIcon />\n          </IconButton>\n        </Tooltip>\n\n        <Tooltip title=\"Save Workflow Trigger\">\n          <div>\n            <IconButton disabled={!dirty} onClick={handleSave}>\n              <SaveIcon />\n            </IconButton>\n          </div>\n        </Tooltip>\n\n        <Tooltip title=\"Populate Input Parameters\">\n          <div>\n            <IconButton\n              disabled={!values.workflowName}\n              onClick={triggerPopulateInput}\n            >\n              <PlaylistAddIcon />\n            </IconButton>\n          </div>\n        </Tooltip>\n\n        {dirty && <Pill label=\"Modified\" />}\n        {createTime && <Text>Created: {timestampRenderer(createTime)}</Text>}\n      </Toolbar>\n\n      <div className={classes.fields}>\n        <FormikWorkflowNameInput\n          fullWidth\n          label=\"Workflow Name\"\n          name=\"workflowName\"\n        />\n\n        <FormikVersionDropdown\n          fullWidth\n          label=\"Workflow version\"\n          name=\"workflowVersion\"\n        />\n\n        <FormikJsonInput\n          reinitialize\n          height={200}\n          label=\"Input (JSON)\"\n          name=\"workflowInput\"\n        />\n\n        <FormikInput fullWidth label=\"Correlation ID\" name=\"correlationId\" />\n\n        <FormikJsonInput\n          className={classes.field}\n          reinitialize\n          height={200}\n          label=\"Task to Domain (JSON)\"\n          name=\"taskToDomain\"\n        />\n      </div>\n    </Form>\n  );\n}\n\nfunction runPayloadToFormData(runPayload) {\n  return {\n    workflowName: _.get(runPayload, \"name\", \"\"),\n    workflowVersion: _.get(runPayload, \"version\", \"\"),\n    workflowInput: _.has(runPayload, \"input\")\n      ? JSON.stringify(runPayload.input, null, 2)\n      : \"\",\n    correlationId: _.get(runPayload, \"correlationId\", \"\"),\n    taskToDomain: _.has(runPayload, \"taskToDomain\")\n      ? JSON.stringify(runPayload.taskToDomain, null, 2)\n      : \"\",\n  };\n}\n\nfunction formDataToRunPayload(form) {\n  let runPayload = {\n    name: form.workflowName,\n  };\n  if (form.workflowVersion) {\n    runPayload.version = form.workflowVersion;\n  }\n  if (form.workflowInput) {\n    runPayload.input = JSON.parse(form.workflowInput);\n  }\n  if (form.correlationId) {\n    runPayload.correlationId = form.correlationId;\n  }\n  if (form.taskToDomain) {\n    runPayload.taskToDomain = JSON.parse(form.taskToDomain);\n  }\n  return runPayload;\n}\n\n//  runHistoryRef.current.pushRun(runPayload);\n"
  },
  {
    "path": "ui/src/plugins/AppBarModules.jsx",
    "content": "export default function AppBarModules() {\n  return null;\n}\n"
  },
  {
    "path": "ui/src/plugins/AppLogo.jsx",
    "content": "import React from \"react\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { getBasename } from \"../utils/helpers\";\nimport { cleanDuplicateSlash } from \"./fetch\";\n\nconst useStyles = makeStyles((theme) => ({\n  logo: {\n    height: 37,\n    width: 175,\n    marginRight: 30,\n  },\n}));\n\nexport default function AppLogo() {\n  const classes = useStyles();\n  const logoPath = getBasename() + 'logo.svg';\n  return <img src={cleanDuplicateSlash(logoPath)} alt=\"Conductor\" className={classes.logo} />;\n}\n"
  },
  {
    "path": "ui/src/plugins/CustomAppBarButtons.jsx",
    "content": "export default function CustomAppBarButtons() {\n  return <></>;\n}\n"
  },
  {
    "path": "ui/src/plugins/CustomRoutes.jsx",
    "content": "export default function CustomRoutes() {\n  return <></>;\n}\n"
  },
  {
    "path": "ui/src/plugins/constants.js",
    "content": ""
  },
  {
    "path": "ui/src/plugins/customTypeRenderers.jsx",
    "content": "export const customTypeRenderers = {};\n"
  },
  {
    "path": "ui/src/plugins/env.js",
    "content": "export function useEnv() {\n  return {\n    stack: \"default\",\n    defaultStack: \"default\",\n  };\n}\n"
  },
  {
    "path": "ui/src/plugins/fetch.js",
    "content": "import { getBasename } from \"../utils/helpers\";\nimport { useEnv } from \"./env\";\n\nexport function useFetchContext() {\n  const { stack } = useEnv();\n  return {\n    stack,\n    ready: true,\n  };\n}\nexport function fetchWithContext(\n  path,\n  context,\n  fetchParams,\n  isJsonResponse = true\n) {\n  const newParams = { ...fetchParams };\n\n  const basename = getBasename();\n  const newPath = basename + `api/${path}`;\n  const cleanPath = cleanDuplicateSlash(newPath); // Cleanup duplicated slashes\n\n  return fetch(cleanPath, newParams)\n    .then((res) => Promise.all([res, res.text()]))\n    .then(([res, text]) => {\n      if (!res.ok) {\n        // get error message from body or default to response status\n        const error = text || res.status;\n        return Promise.reject(error);\n      } else if (!text || text.length === 0) {\n        return null;\n      } else if (!isJsonResponse) {\n        return text;\n      } else {\n        try {\n          return JSON.parse(text);\n        } catch (e) {\n          return text;\n        }\n      }\n    });\n}\n\nexport function cleanDuplicateSlash(path) {\n  return path.replace(/([^:]\\/)\\/+/g, \"$1\");\n}\n"
  },
  {
    "path": "ui/src/react-app-env.d.ts",
    "content": "/// <reference types=\"react-scripts\" />\n"
  },
  {
    "path": "ui/src/schema/task.js",
    "content": "export const NEW_TASK_TEMPLATE = {\n  name: \"\",\n  description:\n    \"Edit or extend this sample task. Set the task name to get started\",\n  retryCount: 3,\n  timeoutSeconds: 3600,\n  inputKeys: [],\n  outputKeys: [],\n  timeoutPolicy: \"TIME_OUT_WF\",\n  retryLogic: \"FIXED\",\n  retryDelaySeconds: 60,\n  responseTimeoutSeconds: 600,\n  rateLimitPerFrequency: 0,\n  rateLimitFrequencyInSeconds: 1,\n  ownerEmail: \"\",\n};\n\nexport function configureMonaco(monaco) {\n  // No-op\n}\n"
  },
  {
    "path": "ui/src/schema/workflow.js",
    "content": "/* eslint-disable no-template-curly-in-string */\n\nexport const NEW_WORKFLOW_TEMPLATE = {\n  name: \"\",\n  description:\n    \"Edit or extend this sample workflow. Set the workflow name to get started\",\n  version: 1,\n  tasks: [\n    {\n      name: \"get_population_data\",\n      taskReferenceName: \"get_population_data\",\n      inputParameters: {\n        http_request: {\n          uri: \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n          method: \"GET\",\n        },\n      },\n      type: \"HTTP\",\n    },\n  ],\n  inputParameters: [],\n  outputParameters: {\n    data: \"${get_population_data.output.response.body.data}\",\n    source: \"${get_population_data.output.response.body.source}\",\n  },\n  schemaVersion: 2,\n  restartable: true,\n  workflowStatusListenerEnabled: false,\n  ownerEmail: \"example@email.com\",\n  timeoutPolicy: \"ALERT_ONLY\",\n  timeoutSeconds: 0,\n};\n\nconst WORKFLOW_SCHEMA = {\n  $schema: \"http://json-schema.org/draft-07/schema\",\n  $id: \"http://example.com/example.json\",\n  type: \"object\",\n  title: \"The root schema\",\n  description: \"The root schema comprises the entire JSON document.\",\n  default: {},\n  examples: [\n    {\n      name: \"first_sample_workflow\",\n      description: \"First Sample Workflow\",\n      version: 1,\n      tasks: [\n        {\n          name: \"get_population_data\",\n          taskReferenceName: \"get_population_data\",\n          inputParameters: {\n            http_request: {\n              uri: \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n              method: \"GET\",\n            },\n          },\n          type: \"HTTP\",\n        },\n      ],\n      inputParameters: [],\n      outputParameters: {\n        data: \"${get_population_data.output.response.body.data}\",\n        source: \"${get_population_data.output.response.body.source}\",\n      },\n      schemaVersion: 2,\n      restartable: true,\n      workflowStatusListenerEnabled: false,\n      ownerEmail: \"example@email.com\",\n      timeoutPolicy: \"ALERT_ONLY\",\n      timeoutSeconds: 0,\n    },\n  ],\n  required: [\"name\", \"version\", \"tasks\", \"schemaVersion\"],\n  properties: {\n    name: {\n      $id: \"#/properties/name\",\n      default: \"\",\n      description:\n        \"Workflow Name - should be without spaces or special characters. Underscores and periods are allowed.\",\n      examples: [\"first_sample_workflow\"],\n      maxLength: 100,\n      pattern: \"^[\\\\w\\\\.]+$\",\n      title: \"Workflow Name\",\n      type: \"string\",\n    },\n    description: {\n      $id: \"#/properties/description\",\n      type: \"string\",\n      title: \"Workflow Description\",\n      description: \"An brief description of your workflow for reference.\",\n      default: \"\",\n      examples: [\"First Sample Workflow\"],\n    },\n    version: {\n      $id: \"#/properties/version\",\n      default: 0,\n      description: \"An explanation about the purpose of this instance.\",\n      examples: [1],\n      title: \"The version schema\",\n      minimum: 1,\n      type: \"integer\",\n    },\n    tasks: {\n      $id: \"#/properties/tasks\",\n      type: \"array\",\n      title: \"Workflow Tasks\",\n      description: \"This list holds the tasks for your workflow.\",\n      default: [],\n      examples: [\n        [\n          {\n            name: \"get_population_data\",\n            taskReferenceName: \"get_population_data\",\n            inputParameters: {\n              http_request: {\n                uri: \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n                method: \"GET\",\n              },\n            },\n            type: \"HTTP\",\n          },\n        ],\n      ],\n      additionalItems: true,\n      items: {\n        $id: \"#/properties/tasks/items\",\n        anyOf: [\n          {\n            $id: \"#/properties/tasks/items/anyOf/0\",\n            type: \"object\",\n            title: \"The first anyOf schema\",\n            description: \"Workflow task details\",\n            default: {\n              name: \"\",\n              taskReferenceName: \"\",\n              inputParameters: {},\n              type: \"SIMPLE\",\n            },\n            examples: [\n              {\n                name: \"get_population_data\",\n                taskReferenceName: \"get_population_data\",\n                inputParameters: {\n                  http_request: {\n                    uri: \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n                    method: \"GET\",\n                  },\n                },\n                type: \"HTTP\",\n              },\n            ],\n            required: [\"name\", \"taskReferenceName\", \"inputParameters\", \"type\"],\n            properties: {\n              name: {\n                $id: \"#/properties/tasks/items/anyOf/0/properties/name\",\n                type: \"string\",\n                title: \"Task name\",\n                description: \"Task name\",\n                default: \"\",\n                examples: [\"get_population_data\"],\n              },\n              taskReferenceName: {\n                $id: \"#/properties/tasks/items/anyOf/0/properties/taskReferenceName\",\n                type: \"string\",\n                title: \"Task Reference Name\",\n                description:\n                  \"A unique task reference name for this task in the entire workflow\",\n                default: \"\",\n                examples: [\"get_population_data\"],\n              },\n              inputParameters: {\n                $id: \"#/properties/tasks/items/anyOf/0/properties/inputParameters\",\n                type: \"object\",\n                title: \"Input Parameters\",\n                description: \"Task input parameters\",\n                default: {},\n                examples: [\n                  {\n                    http_request: {\n                      uri: \"https://datausa.io/api/data?drilldowns=Nation&measures=Population\",\n                      method: \"GET\",\n                    },\n                  },\n                ],\n                required: [],\n                properties: {},\n                additionalProperties: true,\n              },\n              type: {\n                $id: \"#/properties/tasks/items/anyOf/0/properties/type\",\n                type: \"string\",\n                title: \"Task Type\",\n                description: \"Task type\",\n                default: \"\",\n                examples: [\"HTTP\"],\n              },\n            },\n            additionalProperties: true,\n          },\n        ],\n      },\n    },\n    inputParameters: {\n      $id: \"#/properties/inputParameters\",\n      type: \"array\",\n      title: \"Workflow Input Parameters\",\n      description: \"An explanation about the purpose of this instance.\",\n      default: [],\n      examples: [[]],\n      additionalItems: true,\n      items: {\n        $id: \"#/properties/inputParameters/items\",\n      },\n    },\n    outputParameters: {\n      $id: \"#/properties/outputParameters\",\n      type: \"object\",\n      title: \"The outputParameters schema\",\n      description: \"An explanation about the purpose of this instance.\",\n      default: {},\n      examples: [\n        {\n          data: \"${get_population_data.output.response.body.data}\",\n          source: \"${get_population_data.output.response.body.source}\",\n        },\n      ],\n      required: [],\n      properties: {},\n      additionalProperties: true,\n    },\n    schemaVersion: {\n      $id: \"#/properties/schemaVersion\",\n      type: \"integer\",\n      title: \"Schema Version\",\n      description: \"Fixed schema version\",\n      default: 2,\n      examples: [2],\n    },\n    restartable: {\n      $id: \"#/properties/restartable\",\n      type: \"boolean\",\n      title: \"Workflow restartable\",\n      description: \"Specify if the workflow is restartable.\",\n      default: true,\n      examples: [true, false],\n    },\n    workflowStatusListenerEnabled: {\n      $id: \"#/properties/workflowStatusListenerEnabled\",\n      type: \"boolean\",\n      title: \"The workflowStatusListenerEnabled schema\",\n      description: \"An explanation about the purpose of this instance.\",\n      default: false,\n      examples: [true, false],\n    },\n    ownerEmail: {\n      $id: \"#/properties/ownerEmail\",\n      type: \"string\",\n      title: \"The ownerEmail schema\",\n      description: \"An explanation about the purpose of this instance.\",\n      default: \"\",\n      examples: [\"example@email.com\"],\n    },\n    timeoutPolicy: {\n      $id: \"#/properties/timeoutPolicy\",\n      type: \"string\",\n      title: \"The timeoutPolicy schema\",\n      description: \"An explanation about the purpose of this instance.\",\n      default: \"\",\n      examples: [\"ALERT_ONLY\", \"TIME_OUT_WF\"],\n    },\n    timeoutSeconds: {\n      $id: \"#/properties/timeoutSeconds\",\n      type: \"integer\",\n      title: \"The timeoutSeconds schema\",\n      description: \"An explanation about the purpose of this instance.\",\n      default: 0,\n      examples: [0],\n    },\n  },\n  additionalProperties: true,\n};\n\nexport const JSON_FILE_NAME = \"file:///workflow.json\";\n\nexport function configureMonaco(monaco) {\n  monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);\n  // noinspection JSUnresolvedVariable\n  monaco.languages.typescript.javascriptDefaults.setCompilerOptions({\n    target: monaco.languages.typescript.ScriptTarget.ES6,\n    allowNonTsExtensions: true,\n  });\n  let modelUri = monaco.Uri.parse(JSON_FILE_NAME);\n  monaco.languages.json.jsonDefaults.setDiagnosticsOptions({\n    validate: true,\n    schemas: [\n      {\n        uri: \"http://conductor.tmp/schemas/workflow.json\", // id of the first schema\n        fileMatch: [modelUri.toString()], // associate with our model\n        schema: WORKFLOW_SCHEMA,\n      },\n    ],\n  });\n}\n"
  },
  {
    "path": "ui/src/serviceWorker.js",
    "content": "// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n  window.location.hostname === \"localhost\" ||\n    // [::1] is the IPv6 localhost address.\n    window.location.hostname === \"[::1]\" ||\n    // 127.0.0.0/8 are considered localhost for IPv4.\n    window.location.hostname.match(\n      /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n    )\n);\n\nexport function register(config) {\n  if (process.env.NODE_ENV === \"production\" && \"serviceWorker\" in navigator) {\n    // The URL constructor is available in all browsers that support SW.\n    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n    if (publicUrl.origin !== window.location.origin) {\n      // Our service worker won't work if PUBLIC_URL is on a different origin\n      // from what our page is served on. This might happen if a CDN is used to\n      // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n      return;\n    }\n\n    window.addEventListener(\"load\", () => {\n      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n      if (isLocalhost) {\n        // This is running on localhost. Let's check if a service worker still exists or not.\n        checkValidServiceWorker(swUrl, config);\n\n        // Add some additional logging to localhost, pointing developers to the\n        // service worker/PWA documentation.\n        navigator.serviceWorker.ready.then(() => {\n          console.log(\n            \"This web app is being served cache-first by a service \" +\n              \"worker. To learn more, visit https://bit.ly/CRA-PWA\"\n          );\n        });\n      } else {\n        // Is not localhost. Just register service worker\n        registerValidSW(swUrl, config);\n      }\n    });\n  }\n}\n\nfunction registerValidSW(swUrl, config) {\n  navigator.serviceWorker\n    .register(swUrl)\n    .then((registration) => {\n      registration.onupdatefound = () => {\n        const installingWorker = registration.installing;\n        if (installingWorker == null) {\n          return;\n        }\n        installingWorker.onstatechange = () => {\n          if (installingWorker.state === \"installed\") {\n            if (navigator.serviceWorker.controller) {\n              // At this point, the updated precached content has been fetched,\n              // but the previous service worker will still serve the older\n              // content until all client tabs are closed.\n              console.log(\n                \"New content is available and will be used when all \" +\n                  \"tabs for this page are closed. See https://bit.ly/CRA-PWA.\"\n              );\n\n              // Execute callback\n              if (config && config.onUpdate) {\n                config.onUpdate(registration);\n              }\n            } else {\n              // At this point, everything has been precached.\n              // It's the perfect time to display a\n              // \"Content is cached for offline use.\" message.\n              console.log(\"Content is cached for offline use.\");\n\n              // Execute callback\n              if (config && config.onSuccess) {\n                config.onSuccess(registration);\n              }\n            }\n          }\n        };\n      };\n    })\n    .catch((error) => {\n      console.error(\"Error during service worker registration:\", error);\n    });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n  // Check if the service worker can be found. If it can't reload the page.\n  fetch(swUrl, {\n    headers: { \"Service-Worker\": \"script\" },\n  })\n    .then((response) => {\n      // Ensure service worker exists, and that we really are getting a JS file.\n      const contentType = response.headers.get(\"content-type\");\n      if (\n        response.status === 404 ||\n        (contentType != null && contentType.indexOf(\"javascript\") === -1)\n      ) {\n        // No service worker found. Probably a different app. Reload the page.\n        navigator.serviceWorker.ready.then((registration) => {\n          registration.unregister().then(() => {\n            window.location.reload();\n          });\n        });\n      } else {\n        // Service worker found. Proceed as normal.\n        registerValidSW(swUrl, config);\n      }\n    })\n    .catch(() => {\n      console.log(\n        \"No internet connection found. App is running in offline mode.\"\n      );\n    });\n}\n\nexport function unregister() {\n  if (\"serviceWorker\" in navigator) {\n    navigator.serviceWorker.ready\n      .then((registration) => {\n        registration.unregister();\n      })\n      .catch((error) => {\n        console.error(error.message);\n      });\n  }\n}\n"
  },
  {
    "path": "ui/src/setupProxy.js",
    "content": "const { createProxyMiddleware } = require(\"http-proxy-middleware\");\nconst target = process.env.WF_SERVER || \"http://localhost:8080\";\n\nmodule.exports = function (app) {\n  app.use(\n    \"/api\",\n    createProxyMiddleware({\n      target: target,\n      //pathRewrite: { \"^/api/\": \"/\" },\n      changeOrigin: true,\n    })\n  );\n};\n"
  },
  {
    "path": "ui/src/setupTests.js",
    "content": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport \"@testing-library/jest-dom/extend-expect\";\n"
  },
  {
    "path": "ui/src/theme/colorOverrides.js",
    "content": "import * as colors from \"./colors\";\n\nconst brandAliases = {\n  brand00: colors.indigo00,\n  brand01: colors.indigo01,\n  brand02: colors.indigo02,\n  brand03: colors.indigo03,\n  brand04: colors.indigo04,\n  brand05: colors.indigo05,\n  brand06: colors.indigo06,\n  brand07: colors.indigo07,\n  brand08: colors.indigo08,\n  brand09: colors.indigo09,\n  brand10: colors.indigo10,\n  brand11: colors.indigo11,\n  brand12: colors.indigo12,\n  brand13: colors.indigo13,\n  brand14: colors.indigo14,\n};\n\nconst brandShortcuts = {\n  brand: brandAliases.brand07,\n  bgBrand: brandAliases.brand07,\n  bgBrandLight: brandAliases.brand09,\n  bgBrandDark: brandAliases.brand05,\n  brandXLight: colors.indigoXLight,\n  brandXXLight: colors.indigoXXLight,\n};\n\nconst failureAliases = {\n  failure: colors.red07,\n  failureLight: colors.red09,\n  failureDark: colors.red05,\n};\n\nexport const colorOverrides = {\n  ...colors,\n  ...brandAliases,\n  ...brandShortcuts,\n  ...failureAliases,\n};\n"
  },
  {
    "path": "ui/src/theme/colors.js",
    "content": "// Backgrounds / Black\nexports.black = \"#050505\";\n\n// Transparents / Black / 00-Black-Light (70%)\nexports.blackLight = \"rgba(5,5,5,0.7)\";\n\n// Transparents / Black / 01-Black-Xlight (40%)\nexports.blackXLight = \"rgba(5,5,5,0.4)\";\n\n// Transparents / Black / 02-Black-Xxlight (10%)\nexports.blackXXLight = \"rgba(5,5,5,0.1)\";\n\n// Backgrounds / Blue / Blue-00 (Xxdark)\nexports.blue00 = \"#00101f\";\n\n// Backgrounds / Blue / Blue-01\nexports.blue01 = \"#05192b\";\n\n// Backgrounds / Blue / Blue-02\nexports.blue02 = \"#092743\";\n\n// Backgrounds / Blue / Blue-03 (Xdark)\nexports.blue03 = \"#0d365c\";\n\n// Backgrounds / Blue / Blue-04\nexports.blue04 = \"#12487a\";\n\n// Backgrounds / Blue / Blue-05 (Dark)\nexports.blue05 = \"#165b99\";\n\n// Backgrounds / Blue / Blue-06\nexports.blue06 = \"#1b6fb9\";\n\n// Backgrounds / Blue / -Blue-07 (Base)\nexports.blue07 = \"#1f83db\";\n\n// Backgrounds / Blue / Blue-08\nexports.blue08 = \"#5995e1\";\n\n// Backgrounds / Blue / Blue-09 (Light)\nexports.blue09 = \"#7ea7e7\";\n\n// Backgrounds / Blue / Blue-10\nexports.blue10 = \"#9dbaec\";\n\n// Backgrounds / Blue / Blue-11 (Xlight)\nexports.blue11 = \"#bacdf2\";\n\n// Backgrounds / Blue / Blue-12\nexports.blue12 = \"#d2def6\";\n\n// Backgrounds / Blue / Blue-13\nexports.blue13 = \"#eaf0fb\";\n\n// Backgrounds / Blue / Blue-14 (Xxlight)\nexports.blue14 = \"#f7fafd\";\n\n// Transparents / Blue / 00-Blue-Light (70%)\nexports.blueLight = \"rgba(31,131,219,0.7)\";\n\n// Transparents / Blue / 01-Blue-Xlight (40%)\nexports.blueXLight = \"rgba(31,131,219,0.4)\";\n\n// Transparents / Blue / 02-Blue-Xxlight (10%)\nexports.blueXXLight = \"rgba(31,131,219,0.1)\";\n\n// Backgrounds / Cyan / Cyan-00 (Xxdark)\nexports.cyan00 = \"#001b1e\";\n\n// Backgrounds / Cyan / Cyan-01\nexports.cyan01 = \"#042529\";\n\n// Backgrounds / Cyan / Cyan-02\nexports.cyan02 = \"#08373d\";\n\n// Backgrounds / Cyan / Cyan-03 (Xdark)\nexports.cyan03 = \"#0f4a52\";\n\n// Backgrounds / Cyan / Cyan-04\nexports.cyan04 = \"#17616c\";\n\n// Backgrounds / Cyan / Cyan-05 (Dark)\nexports.cyan05 = \"#207986\";\n\n// Backgrounds / Cyan / Cyan-06\nexports.cyan06 = \"#2991a2\";\n\n// Backgrounds / Cyan / -Cyan-07 (Base)\nexports.cyan07 = \"#32abbe\";\n\n// Backgrounds / Cyan / Cyan-08\nexports.cyan08 = \"#5fb8c8\";\n\n// Backgrounds / Cyan / Cyan-09 (Light)\nexports.cyan09 = \"#80c5d2\";\n\n// Backgrounds / Cyan / Cyan-10\nexports.cyan10 = \"#9ed2dc\";\n\n// Backgrounds / Cyan / Cyan-11 (Xlight)\nexports.cyan11 = \"#badfe6\";\n\n// Backgrounds / Cyan / Cyan-12\nexports.cyan12 = \"#d2eaef\";\n\n// Backgrounds / Cyan / Cyan-13\nexports.cyan13 = \"#eaf5f8\";\n\n// Backgrounds / Cyan / Cyan-14 (Xxlight)\nexports.cyan14 = \"#f7fcfd\";\n\n// Transparents / Cyan / 00-Cyan-Light (70%)\nexports.cyanLight = \"rgba(50,171,190,0.7)\";\n\n// Transparents / Cyan / 01-Cyan-Xlight (40%)\nexports.cyanXLight = \"rgba(50,171,190,0.4)\";\n\n// Transparents / Cyan / 02-Cyan-Xxlight (10%)\nexports.cyanXXLight = \"rgba(50,171,190,0.1)\";\n\n// Backgrounds / Grape / Grape-00 (Xxdark)\nexports.grape00 = \"#18001f\";\n\n// Backgrounds / Grape / Grape-01\nexports.grape01 = \"#200b2a\";\n\n// Backgrounds / Grape / Grape-02\nexports.grape02 = \"#33143f\";\n\n// Backgrounds / Grape / Grape-03 (Xdark)\nexports.grape03 = \"#481d56\";\n\n// Backgrounds / Grape / Grape-04\nexports.grape04 = \"#602871\";\n\n// Backgrounds / Grape / Grape-05 (Dark)\nexports.grape05 = \"#7a338d\";\n\n// Backgrounds / Grape / Grape-06\nexports.grape06 = \"#943eab\";\n\n// Backgrounds / Grape / -Grape-07 (Base)\nexports.grape07 = \"#b04ac9\";\n\n// Backgrounds / Grape / Grape-08\nexports.grape08 = \"#be68d2\";\n\n// Backgrounds / Grape / Grape-09 (Light)\nexports.grape09 = \"#cb84da\";\n\n// Backgrounds / Grape / Grape-10\nexports.grape10 = \"#d89fe3\";\n\n// Backgrounds / Grape / Grape-11 (Xlight)\nexports.grape11 = \"#e4baeb\";\n\n// Backgrounds / Grape / Grape-12\nexports.grape12 = \"#edd2f2\";\n\n// Backgrounds / Grape / Grape-13\nexports.grape13 = \"#f7e9f9\";\n\n// Backgrounds / Grape / Grape-14 (Xxlight)\nexports.grape14 = \"#fcf7fd\";\n\n// Transparents / Grape / 00-Grape-Light (70%)\nexports.grapeLight = \"rgba(176,74,201,0.7)\";\n\n// Transparents / Grape / 01-Grape-Xlight (40%)\nexports.grapeXLight = \"rgba(176,74,201,0.4)\";\n\n// Transparents / Grape / 02-Grape-Xxlight (10%)\nexports.grapeXXLight = \"rgba(176,74,201,0.1)\";\n\n// Backgrounds / Gray / Gray-00 (Xxdark)\nexports.gray00 = \"#0f0f0f\";\n\n// Backgrounds / Gray / Gray-01\nexports.gray01 = \"#181818\";\n\n// Backgrounds / Gray / Gray-02\nexports.gray02 = \"#242424\";\n\n// Backgrounds / Gray / Gray-03 (Xdark)\nexports.gray03 = \"#323232\";\n\n// Backgrounds / Gray / Gray-04\nexports.gray04 = \"#424242\";\n\n// Backgrounds / Gray / Gray-05 (Dark)\nexports.gray05 = \"#535353\";\n\n// Backgrounds / Gray / Gray-06\nexports.gray06 = \"#646464\";\n\n// Backgrounds / Gray / -Gray-07 (Base)\nexports.gray07 = \"#767676\";\n\n// Backgrounds / Gray / Gray-08\nexports.gray08 = \"#8a8a8a\";\n\n// Backgrounds / Gray / Gray-09 (Light)\nexports.gray09 = \"#9e9e9e\";\n\n// Backgrounds / Gray / Gray-10\nexports.gray10 = \"#b3b3b3\";\n\n// Backgrounds / Gray / Gray-11 (Xlight)\nexports.gray11 = \"#c8c8c8\";\n\n// Backgrounds / Gray / Gray-12\nexports.gray12 = \"#dbdbdb\";\n\n// Backgrounds / Gray / Gray-13\nexports.gray13 = \"#efefef\";\n\n// Backgrounds / Gray / Gray-14 (Xxlight)\nexports.gray14 = \"#fafafa\";\n\n// Transparents / Gray / 00-Gray-Light (70%)\nexports.grayLight = \"rgba(118,118,118,0.7)\";\n\n// Transparents / Gray / 01-Gray-Xlight (40%)\nexports.grayXLight = \"rgba(118,118,118,0.4)\";\n\n// Transparents / Gray / 02-Gray-Xxlight (10%)\nexports.grayXXLight = \"rgba(118,118,118,0.1)\";\n\n// Backgrounds / Green / Green-00 (Xxdark)\nexports.green00 = \"#121e00\";\n\n// Backgrounds / Green / Green-01\nexports.green01 = \"#192a07\";\n\n// Backgrounds / Green / Green-02\nexports.green02 = \"#28400f\";\n\n// Backgrounds / Green / Green-03 (Xdark)\nexports.green03 = \"#385714\";\n\n// Backgrounds / Green / Green-04\nexports.green04 = \"#4c731a\";\n\n// Backgrounds / Green / Green-05 (Dark)\nexports.green05 = \"#61911f\";\n\n// Backgrounds / Green / Green-06\nexports.green06 = \"#76af25\";\n\n// Backgrounds / Green / -Green-07 (Base)\nexports.green07 = \"#8ccf2a\";\n\n// Backgrounds / Green / Green-08\nexports.green08 = \"#a1d753\";\n\n// Backgrounds / Green / Green-09 (Light)\nexports.green09 = \"#b4de74\";\n\n// Backgrounds / Green / Green-10\nexports.green10 = \"#c6e593\";\n\n// Backgrounds / Green / Green-11 (Xlight)\nexports.green11 = \"#d7edb2\";\n\n// Backgrounds / Green / Green-12\nexports.green12 = \"#e5f3cd\";\n\n// Backgrounds / Green / Green-13\nexports.green13 = \"#f3f9e8\";\n\n// Backgrounds / Green / Green-14 (Xxlight)\nexports.green14 = \"#fbfdf7\";\n\n// Transparents / Green / 00-Green-Light (70%)\nexports.greenLight = \"rgba(140,207,42,0.7)\";\n\n// Transparents / Green / 01-Green-Xlight (40%)\nexports.greenXLight = \"rgba(140,207,42,0.4)\";\n\n// Transparents / Green / 02-Green-Xxlight (10%)\nexports.greenXXLight = \"rgba(140,207,42,0.1)\";\n\n// Backgrounds / Indigo / Indigo-00 (Xxdark)\nexports.indigo00 = \"#00071f\";\n\n// Backgrounds / Indigo / Indigo-01\nexports.indigo01 = \"#07122c\";\n\n// Backgrounds / Indigo / Indigo-02\nexports.indigo02 = \"#0f1e44\";\n\n// Backgrounds / Indigo / Indigo-03 (Xdark)\nexports.indigo03 = \"#192b5e\";\n\n// Backgrounds / Indigo / Indigo-04\nexports.indigo04 = \"#24397e\";\n\n// Backgrounds / Indigo / Indigo-05 (Dark)\nexports.indigo05 = \"#30499f\";\n\n// Backgrounds / Indigo / Indigo-06\nexports.indigo06 = \"#3c59c1\";\n\n// Backgrounds / Indigo / -Indigo-07 (Base)\nexports.indigo07 = \"#4969e4\";\n\n// Backgrounds / Indigo / Indigo-08\nexports.indigo08 = \"#6f7ee9\";\n\n// Backgrounds / Indigo / Indigo-09 (Light)\nexports.indigo09 = \"#8e94ed\";\n\n// Backgrounds / Indigo / Indigo-10\nexports.indigo10 = \"#a9abf1\";\n\n// Backgrounds / Indigo / Indigo-11 (Xlight)\nexports.indigo11 = \"#c2c2f5\";\n\n// Backgrounds / Indigo / Indigo-12\nexports.indigo12 = \"#d7d7f8\";\n\n// Backgrounds / Indigo / Indigo-13\nexports.indigo13 = \"#ebedfb\";\n\n// Backgrounds / Indigo / Indigo-14 (Xxlight)\nexports.indigo14 = \"#f7f9fd\";\n\n// Transparents / Indigo / 00-Indigo-Light (70%)\nexports.indigoLight = \"rgba(73,105,228,0.7)\";\n\n// Transparents / Indigo / 01-Indigo-Xlight (40%)\nexports.indigoXLight = \"rgba(73,105,228,0.4)\";\n\n// Transparents / Indigo / 02-Indigo-Xxlight (10%)\nexports.indigoXXLight = \"rgba(73,105,228,0.1)\";\n\n// Backgrounds / Lime / Lime-00 (Xxdark)\nexports.lime00 = \"#001f06\";\n\n// Backgrounds / Lime / Lime-01\nexports.lime01 = \"#05290f\";\n\n// Backgrounds / Lime / Lime-02\nexports.lime02 = \"#0c3c19\";\n\n// Backgrounds / Lime / Lime-03 (Xdark)\nexports.lime03 = \"#145124\";\n\n// Backgrounds / Lime / Lime-04\nexports.lime04 = \"#1f6930\";\n\n// Backgrounds / Lime / Lime-05 (Dark)\nexports.lime05 = \"#2a833c\";\n\n// Backgrounds / Lime / Lime-06\nexports.lime06 = \"#359e4a\";\n\n// Backgrounds / Lime / -Lime-07 (Base)\nexports.lime07 = \"#41b957\";\n\n// Backgrounds / Lime / Lime-08\nexports.lime08 = \"#65c470\";\n\n// Backgrounds / Lime / Lime-09 (Light)\nexports.lime09 = \"#84d08a\";\n\n// Backgrounds / Lime / Lime-10\nexports.lime10 = \"#a0dba3\";\n\n// Backgrounds / Lime / Lime-11 (Xlight)\nexports.lime11 = \"#bbe5bd\";\n\n// Backgrounds / Lime / Lime-12\nexports.lime12 = \"#d2efd4\";\n\n// Backgrounds / Lime / Lime-13\nexports.lime13 = \"#e9f8eb\";\n\n// Backgrounds / Lime / Lime-14 (Xxlight)\nexports.lime14 = \"#f6fdf8\";\n\n// Transparents / Lime / 00-Lime-Light (70%)\nexports.limeLight = \"rgba(65,185,87,0.7)\";\n\n// Transparents / Lime / 01-Lime-Xlight (40%)\nexports.limeXLight = \"rgba(65,185,87,0.4)\";\n\n// Transparents / Lime / 02-Lime-Xxlight (10%)\nexports.limeXXLight = \"rgba(65,185,87,0.1)\";\n\n// Backgrounds / Orange / Orange-00 (Xxdark)\nexports.orange00 = \"#1e0c00\";\n\n// Backgrounds / Orange / Orange-01\nexports.orange01 = \"#2b1505\";\n\n// Backgrounds / Orange / Orange-02\nexports.orange02 = \"#46210d\";\n\n// Backgrounds / Orange / Orange-03 (Xdark)\nexports.orange03 = \"#622e10\";\n\n// Backgrounds / Orange / Orange-04\nexports.orange04 = \"#853d12\";\n\n// Backgrounds / Orange / Orange-05 (Dark)\nexports.orange05 = \"#a94d14\";\n\n// Backgrounds / Orange / Orange-06\nexports.orange06 = \"#cf5d14\";\n\n// Backgrounds / Orange / -Orange-07 (Base)\nexports.orange07 = \"#f66e13\";\n\n// Backgrounds / Orange / Orange-08\nexports.orange08 = \"#fd853f\";\n\n// Backgrounds / Orange / Orange-09 (Light)\nexports.orange09 = \"#ff9c62\";\n\n// Backgrounds / Orange / Orange-10\nexports.orange10 = \"#ffb284\";\n\n// Backgrounds / Orange / Orange-11 (Xlight)\nexports.orange11 = \"#ffc8a7\";\n\n// Backgrounds / Orange / Orange-12\nexports.orange12 = \"#ffdbc5\";\n\n// Backgrounds / Orange / Orange-13\nexports.orange13 = \"#ffeee5\";\n\n// Backgrounds / Orange / Orange-14 (Xxlight)\nexports.orange14 = \"#fdf9f7\";\n\n// Transparents / Orange / 00-Orange-Light (70%)\nexports.orangeLight = \"rgba(246,110,19,0.7)\";\n\n// Transparents / Orange / 01-Orange-Xlight (40%)\nexports.orangeXLight = \"rgba(246,110,19,0.4)\";\n\n// Transparents / Orange / 02-Orange-Xxlight (10%)\nexports.orangeXXLight = \"rgba(246,110,19,0.1)\";\n\n// Backgrounds / Pear / Pear-00 (Xxdark)\nexports.pear00 = \"#1e1d00\";\n\n// Backgrounds / Pear / Pear-01\nexports.pear01 = \"#2a2a07\";\n\n// Backgrounds / Pear / Pear-02\nexports.pear02 = \"#42410e\";\n\n// Backgrounds / Pear / Pear-03 (Xdark)\nexports.pear03 = \"#5d5a12\";\n\n// Backgrounds / Pear / Pear-04\nexports.pear04 = \"#7c7815\";\n\n// Backgrounds / Pear / Pear-05 (Dark)\nexports.pear05 = \"#9d9718\";\n\n// Backgrounds / Pear / Pear-06\nexports.pear06 = \"#bfb71b\";\n\n// Backgrounds / Pear / -Pear-07 (Base)\nexports.pear07 = \"#e3d91c\";\n\n// Backgrounds / Pear / Pear-08\nexports.pear08 = \"#eade4f\";\n\n// Backgrounds / Pear / Pear-09 (Light)\nexports.pear09 = \"#f0e472\";\n\n// Backgrounds / Pear / Pear-10\nexports.pear10 = \"#f6e993\";\n\n// Backgrounds / Pear / Pear-11 (Xlight)\nexports.pear11 = \"#f9efb2\";\n\n// Backgrounds / Pear / Pear-12\nexports.pear12 = \"#fcf4cd\";\n\n// Backgrounds / Pear / Pear-13\nexports.pear13 = \"#fdf9e8\";\n\n// Backgrounds / Pear / Pear-14 (Xxlight)\nexports.pear14 = \"#fdfcf7\";\n\n// Transparents / Pear / 00-Pear-Light (70%)\nexports.pearLight = \"rgba(227,217,28,0.7)\";\n\n// Transparents / Pear / 01-Pear-Xlight (40%)\nexports.pearXLight = \"rgba(227,217,28,0.4)\";\n\n// Transparents / Pear / 02-Pear-Xxlight (10%)\nexports.pearXXLight = \"rgba(227,217,28,0.1)\";\n\n// Backgrounds / Pink / Pink-00 (Xxdark)\nexports.pink00 = \"#1e000a\";\n\n// Backgrounds / Pink / Pink-01\nexports.pink01 = \"#280a14\";\n\n// Backgrounds / Pink / Pink-02\nexports.pink02 = \"#3f1221\";\n\n// Backgrounds / Pink / Pink-03 (Xdark)\nexports.pink03 = \"#58192f\";\n\n// Backgrounds / Pink / Pink-04\nexports.pink04 = \"#75223f\";\n\n// Backgrounds / Pink / Pink-05 (Dark)\nexports.pink05 = \"#942b50\";\n\n// Backgrounds / Pink / Pink-06\nexports.pink06 = \"#b53461\";\n\n// Backgrounds / Pink / -Pink-07 (Base)\nexports.pink07 = \"#d63d73\";\n\n// Backgrounds / Pink / Pink-08\nexports.pink08 = \"#e06187\";\n\n// Backgrounds / Pink / Pink-09 (Light)\nexports.pink09 = \"#e87f9c\";\n\n// Backgrounds / Pink / Pink-10\nexports.pink10 = \"#f09cb1\";\n\n// Backgrounds / Pink / Pink-11 (Xlight)\nexports.pink11 = \"#f5b8c6\";\n\n// Backgrounds / Pink / Pink-12\nexports.pink12 = \"#f9d1da\";\n\n// Backgrounds / Pink / Pink-13\nexports.pink13 = \"#fce9ee\";\n\n// Backgrounds / Pink / Pink-14 (Xxlight)\nexports.pink14 = \"#fdf7f9\";\n\n// Transparents / Pink / 00-Pink-Light (70%)\nexports.pinkLight = \"rgba(214,61,115,0.7)\";\n\n// Transparents / Pink / 01-Pink-Xlight (40%)\nexports.pinkXLight = \"rgba(214,61,115,0.4)\";\n\n// Transparents / Pink / 02-Pink-Xxlight (10%)\nexports.pinkXXLight = \"rgba(214,61,115,0.1)\";\n\n// Backgrounds / Red / Red-00 (Xxdark)\nexports.red00 = \"#1e0002\";\n\n// Backgrounds / Red / Red-01\nexports.red01 = \"#2a0805\";\n\n// Backgrounds / Red / Red-02\nexports.red02 = \"#420e0b\";\n\n// Backgrounds / Red / Red-03 (Xdark)\nexports.red03 = \"#5d110f\";\n\n// Backgrounds / Red / Red-04\nexports.red04 = \"#7d1311\";\n\n// Backgrounds / Red / Red-05 (Dark)\nexports.red05 = \"#9e1313\";\n\n// Backgrounds / Red / Red-06\nexports.red06 = \"#c11014\";\n\n// Backgrounds / Red / -Red-07 (Base)\nexports.red07 = \"#e50914\";\n\n// Backgrounds / Red / Red-08\nexports.red08 = \"#f04c38\";\n\n// Backgrounds / Red / Red-09 (Light)\nexports.red09 = \"#f9715a\";\n\n// Backgrounds / Red / Red-10\nexports.red10 = \"#ff927d\";\n\n// Backgrounds / Red / Red-11 (Xlight)\nexports.red11 = \"#ffb2a2\";\n\n// Backgrounds / Red / Red-12\nexports.red12 = \"#ffcdc3\";\n\n// Backgrounds / Red / Red-13\nexports.red13 = \"#ffe8e4\";\n\n// Backgrounds / Red / Red-14 (Xxlight)\nexports.red14 = \"#fdf7f8\";\n\n// Transparents / Red / 00-Red-Light (70%)\nexports.redLight = \"rgba(229,9,20,0.7)\";\n\n// Transparents / Red / 01-Red-Xlight (40%)\nexports.redXLight = \"rgba(229,9,20,0.4)\";\n\n// Transparents / Red / 02-Red-Xxlight (10%)\nexports.redXXLight = \"rgba(229,9,20,0.1)\";\n\n// Backgrounds / Violet / Violet-00 (Xxdark)\nexports.violet00 = \"#08001e\";\n\n// Backgrounds / Violet / Violet-01\nexports.violet01 = \"#110b2b\";\n\n// Backgrounds / Violet / Violet-02\nexports.violet02 = \"#1d1643\";\n\n// Backgrounds / Violet / Violet-03 (Xdark)\nexports.violet03 = \"#2a1f5d\";\n\n// Backgrounds / Violet / Violet-04\nexports.violet04 = \"#3b297c\";\n\n// Backgrounds / Violet / Violet-05 (Dark)\nexports.violet05 = \"#4c349d\";\n\n// Backgrounds / Violet / Violet-06\nexports.violet06 = \"#5e3fbf\";\n\n// Backgrounds / Violet / -Violet-07 (Base)\nexports.violet07 = \"#714be2\";\n\n// Backgrounds / Violet / Violet-08\nexports.violet08 = \"#8c66e7\";\n\n// Backgrounds / Violet / Violet-09 (Light)\nexports.violet09 = \"#a481ec\";\n\n// Backgrounds / Violet / Violet-10\nexports.violet10 = \"#ba9cf1\";\n\n// Backgrounds / Violet / Violet-11 (Xlight)\nexports.violet11 = \"#ceb8f5\";\n\n// Backgrounds / Violet / Violet-12\nexports.violet12 = \"#dfd0f8\";\n\n// Backgrounds / Violet / Violet-13\nexports.violet13 = \"#f0e9fb\";\n\n// Backgrounds / Violet / Violet-14 (Xxlight)\nexports.violet14 = \"#f9f7fd\";\n\n// Transparents / Violet / 00-Violet-Light (70%)\nexports.violetLight = \"rgba(113,75,226,0.7)\";\n\n// Transparents / Violet / 01-Violet-Xlight (40%)\nexports.violetXLight = \"rgba(113,75,226,0.4)\";\n\n// Transparents / Violet / 02-Violet-Xxlight (10%)\nexports.violetXXLight = \"rgba(113,75,226,0.1)\";\n\n// Backgrounds / White\nexports.white = \"#FFFFFF\";\n\n// Transparents / White / 00-White-Light (70%)\nexports.whiteLight = \"rgba(255,255,255,0.7)\";\n\n// Transparents / White / 01-White-Xlight (40%)\nexports.whiteXLight = \"rgba(255,255,255,0.4)\";\n\n// Transparents / White / 02-White-Xxlight (10%)\nexports.whiteXXLight = \"rgba(255,255,255,0.1)\";\n\n// Backgrounds / Yellow / Yellow-00 (Xxdark)\nexports.yellow00 = \"#1e1400\";\n\n// Backgrounds / Yellow / Yellow-01\nexports.yellow01 = \"#2c1e06\";\n\n// Backgrounds / Yellow / Yellow-02\nexports.yellow02 = \"#47300d\";\n\n// Backgrounds / Yellow / Yellow-03 (Xdark)\nexports.yellow03 = \"#64430f\";\n\n// Backgrounds / Yellow / Yellow-04\nexports.yellow04 = \"#875a11\";\n\n// Backgrounds / Yellow / Yellow-05 (Dark)\nexports.yellow05 = \"#ac7210\";\n\n// Backgrounds / Yellow / Yellow-06\nexports.yellow06 = \"#d38a0c\";\n\n// Backgrounds / Yellow / -Yellow-07 (Base)\nexports.yellow07 = \"#fba404\";\n\n// Backgrounds / Yellow / Yellow-08\nexports.yellow08 = \"#ffb141\";\n\n// Backgrounds / Yellow / Yellow-09 (Light)\nexports.yellow09 = \"#ffbf66\";\n\n// Backgrounds / Yellow / Yellow-10\nexports.yellow10 = \"#ffcd89\";\n\n// Backgrounds / Yellow / Yellow-11 (Xlight)\nexports.yellow11 = \"#ffdbaa\";\n\n// Backgrounds / Yellow / Yellow-12\nexports.yellow12 = \"#ffe7c8\";\n\n// Backgrounds / Yellow / Yellow-13\nexports.yellow13 = \"#fff4e6\";\n\n// Backgrounds / Yellow / Yellow-14 (Xxlight)\nexports.yellow14 = \"#fdfbf7\";\n\n// Transparents / Yellow / 00-Yellow-Light (70%)\nexports.yellowLight = \"rgba(251,164,4,0.7)\";\n\n// Transparents / Yellow / 01-Yellow-Xlight (40%)\nexports.yellowXLight = \"rgba(251,164,4,0.4)\";\n\n// Transparents / Yellow / 02-Yellow-Xxlight (10%)\nexports.yellowXXLight = \"rgba(251,164,4,0.1)\";\n"
  },
  {
    "path": "ui/src/theme/index.js",
    "content": "export { Provider as ThemeProvider } from \"./provider\";\nexport { default as theme } from \"./theme\";\n"
  },
  {
    "path": "ui/src/theme/provider.jsx",
    "content": "import React from \"react\";\nimport { MuiThemeProvider } from \"@material-ui/core/styles\";\n\nimport { theme } from \"./\";\n\nexport const Provider = ({ children, ...rest }) => {\n  return (\n    <MuiThemeProvider theme={theme} {...rest}>\n      {children}\n    </MuiThemeProvider>\n  );\n};\n"
  },
  {
    "path": "ui/src/theme/theme.js",
    "content": "import { unstable_createMuiStrictModeTheme as createMuiTheme } from \"@material-ui/core/styles\";\nimport {\n  borders,\n  colors,\n  spacings,\n  breakpoints,\n  fontSizes,\n  lineHeights,\n  fontWeights,\n  fontFamily,\n} from \"./variables\";\n\nfunction toNumber(v) {\n  return parseFloat(v);\n}\n\nconst spacingFn = (factor) => {\n  const unit = toNumber(spacings.space0);\n\n  // Support theme.spacing('space3')\n  if (typeof factor === \"string\") {\n    return toNumber(spacings[factor]);\n  }\n\n  if (typeof factor === \"number\") {\n    // Support theme.spacing(2)\n    return unit * factor;\n  }\n\n  return unit;\n};\n\nconst colorFn = (color) => colors[color];\n\nconst baseThemeOptions = {\n  palette: {\n    type: \"light\",\n    primary: {\n      main: colors.brand,\n      light: colors.bgBrandLight,\n      dark: colors.bgBrandDark,\n      contrastText: colors.white,\n    },\n    secondary: {\n      main: colors.white,\n      light: colors.bgBrandLight,\n      dark: colors.bgBrandDark,\n      contrastText: colors.black,\n    },\n    text: {\n      primary: colors.black,\n      secondary: colors.blackXLight,\n      disabled: colors.blackXXLight,\n      hint: colors.blackXXLight,\n    },\n    grey: {\n      50: colors.gray14,\n      100: colors.gray13,\n      200: colors.gray12,\n      300: colors.gray11,\n      400: colors.gray10,\n      500: colors.gray09,\n      600: colors.gray07,\n      700: colors.gray06,\n      800: colors.gray04,\n      900: colors.gray02,\n      A100: colors.gray12,\n      A200: colors.gray08,\n      A400: colors.gray03,\n      A700: colors.gray06,\n    },\n    error: {\n      main: colors.failure,\n      light: colors.failureLight,\n      dark: colors.failureDark,\n      contrastText: colors.white,\n    },\n    background: {\n      paper: colors.white,\n      default: colors.gray14,\n    },\n    divider: colors.blackXXLight,\n  },\n  typography: {\n    fontFamily: fontFamily.fontFamilySans,\n    fontSize: toNumber(fontSizes.fontSize2),\n    htmlFontSize: toNumber(fontSizes.fontSize2),\n    fontWeightLight: fontWeights.fontWeight0,\n    fontWeightRegular: fontWeights.fontWeight0,\n    fontWeightMedium: fontWeights.fontWeight1,\n    fontWeightBold: fontWeights.fontWeight2,\n    h1: {\n      fontSize: fontSizes.fontSize10,\n      lineHeight: lineHeights.lineHeight0,\n      fontWeight: fontWeights.fontWeight2,\n    },\n    h2: {\n      fontSize: fontSizes.fontSize9,\n      lineHeight: lineHeights.lineHeight0,\n      fontWeight: fontWeights.fontWeight2,\n    },\n    h3: {\n      fontSize: fontSizes.fontSize8,\n      lineHeight: lineHeights.lineHeight0,\n      fontWeight: fontWeights.fontWeight2,\n    },\n    h4: {\n      fontSize: fontSizes.fontSize7,\n      lineHeight: lineHeights.lineHeight0,\n      fontWeight: fontWeights.fontWeight2,\n    },\n    h5: {\n      fontSize: fontSizes.fontSize6,\n      lineHeight: lineHeights.lineHeight0,\n      fontWeight: fontWeights.fontWeight2,\n    },\n    h6: {\n      fontSize: fontSizes.fontSize5,\n      lineHeight: lineHeights.lineHeight0,\n      fontWeight: fontWeights.fontWeight2,\n    },\n    body1: {\n      fontSize: fontSizes.fontSize4,\n      lineHeight: lineHeights.lineHeight1,\n    },\n    body2: {\n      fontSize: fontSizes.fontSize3,\n      lineHeight: lineHeights.lineHeight1,\n    },\n    caption: {\n      fontSize: fontSizes.fontSize2,\n      lineHeight: lineHeights.lineHeight1,\n      fontWeight: fontWeights.fontWeight1,\n    },\n    button: {\n      fontSize: fontSizes.fontSize2,\n      fontWeight: fontWeights.fontWeight1,\n    },\n  },\n  breakpoints: {\n    // this looks wrong, but it's not\n    // material's breakpoints are a range, so the below basically says\n    // xs is from 0 to breakpoints.large\n    values: {\n      xs: 0,\n      sm: toNumber(breakpoints.xsmall),\n      md: toNumber(breakpoints.small),\n      lg: toNumber(breakpoints.medium),\n      xl: toNumber(breakpoints.large),\n    },\n  },\n  shape: {\n    borderRadius: toNumber(borders.radiusSmall),\n  },\n  color: colorFn,\n  spacing: spacingFn,\n  props: {\n    MuiButtonBase: {\n      disableRipple: true,\n    },\n    MuiFormControl: {\n      variant: \"outlined\",\n    },\n    MuiMenu: {\n      transitionDuration: 0,\n      elevation: 3,\n    },\n    MuiTextField: {\n      variant: \"outlined\",\n      InputProps: {\n        labelWidth: 0,\n      },\n    },\n    MuiInputLabel: {\n      shrink: true,\n      disableAnimation: true,\n    },\n    MuiOutlinedInput: {\n      notched: false,\n    },\n    MuiPaper: {\n      elevation: 3,\n    },\n    MuiPopover: {\n      elevation: 3,\n    },\n  },\n};\n\nconst baseTheme = createMuiTheme(baseThemeOptions);\n\n// Keep overrides in separate object so we can reference attributes of baseTheme.\nconst overrides = {\n  overrides: {\n    MuiSvgIcon: {\n      root: {\n        fontSize: fontSizes.fontSize6,\n      },\n      fontSizeSmall: {\n        fontSize: fontSizes.fontSize1,\n      },\n    },\n    MuiAvatar: {\n      root: {\n        fontSize: \"2.4rem\",\n      },\n    },\n    MuiButton: {\n      root: {\n        textDecoration: \"none !important\",\n        textTransform: \"none\",\n        paddingTop: baseTheme.spacing(\"space1\"),\n        paddingBottom: baseTheme.spacing(\"space1\"),\n        paddingLeft: baseTheme.spacing(\"space2\"),\n        paddingRight: baseTheme.spacing(\"space2\"),\n        border: \"1px solid transparent\",\n        transition: \"none\",\n        \"&$focusVisible\": {\n          boxShadow: \"none\",\n          position: \"relative\",\n          \"&:after\": {\n            content: '\"\"',\n            display: \"block\",\n            position: \"absolute\",\n            width: \"calc(100% + 6px)\",\n            height: \"calc(100% + 6px)\",\n            borderRadius: borders.radiusSmall,\n            border: borders.blueRegular2px,\n            top: -5,\n            left: -5,\n          },\n        },\n      },\n      text: {\n        paddingTop: baseTheme.spacing(\"space1\"),\n        paddingBottom: baseTheme.spacing(\"space1\"),\n        paddingLeft: baseTheme.spacing(\"space2\"),\n        paddingRight: baseTheme.spacing(\"space2\"),\n        \"&:hover\": {\n          backgroundColor: baseTheme.palette.grey.A100,\n        },\n      },\n      textSizeSmall: {\n        fontSize: \"0.8125rem\",\n      },\n      outlined: {\n        paddingTop: baseTheme.spacing(\"space1\"),\n        paddingBottom: baseTheme.spacing(\"space1\"),\n        paddingLeft: baseTheme.spacing(\"space2\"),\n        paddingRight: baseTheme.spacing(\"space2\"),\n      },\n      outlinedPrimary: {\n        border: borders.blackRegular1px,\n      },\n      outlinedSecondary: {\n        border: borders.blackLight1px,\n        color: baseTheme.palette.secondary.contrastText,\n        \"&:hover\": {\n          border: borders.blackLight1px + \" !important\",\n          backgroundColor: baseTheme.palette.grey.A100,\n        },\n      },\n      contained: {\n        \"&:disabled\": {\n          backgroundColor: colors.bgBrandLight,\n          color: baseTheme.palette.common.white,\n        },\n        boxShadow: \"none !important\",\n        \"&:active\": {\n          boxShadow: \"none !important\",\n        },\n      },\n      containedPrimary: {\n        color: `${colors.white} !important`,\n      },\n    },\n    MuiCheckbox: {\n      root: {\n        fontSize: fontSizes.fontSize4,\n        padding: baseTheme.spacing(\"space1\"),\n      },\n      colorSecondary: {\n        color: colors.blackLight,\n        \"&$checked\": {\n          color: baseTheme.palette.primary.main,\n        },\n        \"&$disabled\": {\n          color: colors.blackXLight,\n        },\n      },\n    },\n    MuiChip: {\n      root: {\n        borderRadius: borders.radiusSmall,\n        height: 24,\n        fontSize: fontSizes.fontSize2,\n        fontWeight: fontWeights.fontWeight1,\n      },\n      label: {\n        paddingLeft: baseTheme.spacing(\"space1\"),\n        paddingRight: baseTheme.spacing(\"space1\"),\n      },\n      sizeSmall: {\n        fontSize: fontSizes.fontSize0,\n        height: 20,\n      },\n      deleteIcon: {\n        height: \"100%\",\n        padding: 3,\n        margin: 0,\n        backgroundColor: \"rgba(5, 5, 5, 0.1)\",\n        borderRadius: `0 ${borders.radiusSmall} ${borders.radiusSmall} 0`,\n        width: 24,\n        boxSizing: \"border-box\",\n        textAlign: \"center\",\n        fill: baseTheme.palette.common.white,\n        borderLeftWidth: 1,\n        borderLeftStyle: \"solid\",\n        borderLeftColor: \"rgba(5, 5, 5, 0.1)\",\n      },\n      deleteIconColorPrimary: {\n        color: colors.white,\n      },\n      colorSecondary: {\n        color: colors.white,\n        backgroundColor: colors.lime07,\n      },\n    },\n    MuiRadio: {\n      root: {\n        padding: baseTheme.spacing(\"space1\"),\n      },\n    },\n    MuiInputBase: {\n      root: {\n        fontSize: fontSizes.fontSize2,\n      },\n      input: {\n        \"&[type=number]::-webkit-inner-spin-button \": {\n          appearance: \"none\",\n          margin: 0,\n        },\n      },\n    },\n\n    MuiOutlinedInput: {\n      notchedOutline: {\n        borderColor: colors.blackXXLight,\n        top: 0,\n        \"& legend\": {\n          // force-disable notched legends\n          display: \"none\",\n        },\n      },\n      root: {\n        \"&:hover $notchedOutline\": {\n          borderColor: colors.blackXXLight,\n        },\n        \"&.$Mui-disabled\": {\n          backgroundColor: colors.grayXXLight,\n          borderColor: colors.blackXXLight,\n          color: colors.blackLight,\n        },\n        \"&.$Mui-disabled .MuiOutlinedInput-notchedOutline\": {\n          borderColor: \"inherit\",\n        },\n        backgroundColor: baseTheme.palette.background.paper,\n      },\n      input: {\n        padding: `${baseTheme.spacing(\"space2\")}px ${baseTheme.spacing(\n          \"space2\"\n        )}px`,\n      },\n    },\n    MuiFormControl: {\n      root: {\n        display: \"block\",\n      },\n    },\n    MuiFormControlLabel: {\n      label: {\n        fontSize: fontSizes.fontSize3,\n        lineHeight: lineHeights.lineHeight1,\n      },\n    },\n    MuiInputLabel: {\n      root: {\n        display: \"none\",\n        pointerEvents: \"none\",\n        color: baseTheme.palette.text.primary,\n        \"&.$Mui-disabled\": {\n          color: colors.blackXLight,\n        },\n      },\n      outlined: {\n        \"&$shrink\": {\n          display: \"block\",\n          transform: \"none\",\n          position: \"relative\",\n          fontWeight: fontWeights.fontWeight1,\n          fontSize: fontSizes.fontSize2,\n          paddingLeft: 0,\n          paddingBottom: 8,\n        },\n        \"&$focused\": {\n          // focused attr under MuiInputLabel does not work\n          color: baseTheme.palette.text.primary,\n        },\n      },\n    },\n    MuiFormHelperText: {\n      contained: {\n        margin: 0,\n        marginTop: baseTheme.spacing(\"space1\"),\n      },\n    },\n    MuiSelect: {\n      icon: {\n        fontSize: fontSizes.fontSize5,\n        marginTop: 3,\n        color: baseTheme.palette.text.primary,\n      },\n      selectMenu: {},\n    },\n    MuiPickersClockNumber: {\n      clockNumber: {\n        top: 6,\n      },\n    },\n    MuiMenuItem: {\n      root: {\n        color: baseTheme.palette.text.primary,\n        fontSize: fontSizes.fontSize1,\n        \"&:hover\": {\n          backgroundColor: baseTheme.palette.grey[100],\n        },\n        \"&:focus\": {\n          backgroundColor: baseTheme.palette.grey[100],\n        },\n        \"&$selected\": {\n          backgroundColor: baseTheme.palette.grey[200],\n          \"&:hover\": {\n            backgroundColor: baseTheme.palette.grey[200],\n          },\n          \"&:focus\": {\n            backgroundColor: baseTheme.palette.grey[200],\n          },\n        },\n      },\n      dense: {\n        paddingTop: 0,\n        paddingBottom: 0,\n      },\n    },\n    MuiSnackbarContent: {\n      root: {\n        backgroundColor: baseTheme.palette.primary.main,\n        paddingTop: 0,\n        paddingBottom: 0,\n        marginRight: baseTheme.spacing(\"space3\"),\n        marginLeft: baseTheme.spacing(\"space3\"),\n        borderRadius: baseTheme.shape.borderRadius,\n        boxShadow: \"none\",\n      },\n      action: {\n        \"& button\": {\n          color: baseTheme.palette.common.white,\n        },\n      },\n    },\n    MuiSwitch: {\n      root: {\n        padding: 0,\n        height: 20,\n        width: 40,\n        \"&:hover\": {\n          \"& > $track\": {\n            backgroundColor: colors.gray05,\n          },\n          \"& > $checked + $track\": {\n            backgroundColor: colors.brand05,\n          },\n        },\n      },\n      thumb: {\n        borderRadius: 8,\n        width: 16,\n        height: 16,\n        boxShadow:\n          \"0px 1px 2px 0px rgba(0, 0, 0, 0.4), 0px 0px 1px 0px rgba(0, 0, 0, 0.4)\",\n      },\n      track: {\n        backgroundColor: colors.gray07,\n        borderRadius: 10,\n        opacity: 1,\n      },\n      switchBase: {\n        padding: 2,\n        \"&$checked\": {\n          transform: \"translateX(100%)\",\n          \"& + $track\": {\n            opacity: 1,\n          },\n        },\n      },\n      colorPrimary: {\n        \"&$checked\": {\n          color: baseTheme.palette.common.white,\n        },\n        \"&$checked + $track\": {\n          backgroundColor: baseTheme.palette.primary.main,\n        },\n      },\n    },\n    MuiTab: {\n      root: {\n        textTransform: \"none\",\n        \"&$selected\": {\n          color: \"black\",\n        },\n      },\n    },\n    MuiTabs: {\n      indicator: {\n        height: 4,\n      },\n      root: {\n        minHeight: 0,\n      },\n    },\n    MuiListItemText: {\n      secondary: {\n        fontSize: fontSizes.fontSize2,\n      },\n      primary: {\n        fontSize: fontSizes.fontSize2,\n      },\n    },\n    MuiListSubheader: {\n      root: {\n        fontSize: fontSizes.fontSize2,\n        lineHeight: lineHeights.lineHeight1,\n        paddingTop: baseTheme.spacing(\"space0\"),\n        paddingBottom: baseTheme.spacing(\"space0\"),\n      },\n    },\n    MuiTableCell: {\n      root: {\n        fontSize: fontSizes.fontSize2,\n      },\n      head: {\n        //border: 'none',\n        fontWeight: fontWeights.fontWeight1,\n        color: colors.gray05,\n      },\n    },\n    MuiTableRow: {\n      root: {\n        \"&.Mui-selected:hover\": {\n          backgroundColor: colors.gray12,\n        },\n        \"&.Mui-selected\": {\n          backgroundColor: `${colors.gray12} !important`,\n        },\n      },\n    },\n    MuiDialogTitle: {\n      root: {\n        backgroundColor: baseTheme.palette.grey[50],\n        padding: `${baseTheme.spacing(\"space5\")}px ${baseTheme.spacing(\n          \"space4\"\n        )}px`,\n        borderBottom: `1px solid ${colors.blackXXLight}`,\n      },\n    },\n    MuiDialogContent: {\n      root: {\n        padding: baseTheme.spacing(\"space5\"),\n      },\n    },\n    MuiDialogActions: {\n      root: {\n        backgroundColor: baseTheme.palette.grey[50],\n        padding: `${baseTheme.spacing(\"space3\")}px ${baseTheme.spacing(\n          \"space5\"\n        )}px`,\n        borderTop: `1px solid ${colors.blackXXLight}`,\n        margin: 0,\n\n        \"button + button\": {\n          marginLeft: baseTheme.spacing(\"space1\"),\n        },\n      },\n    },\n    MuiToolbar: {\n      root: {\n        gap: 8,\n      },\n    },\n    MuiAppBar: {\n      colorPrimary: {\n        backgroundColor: colors.white,\n        color: colors.gray00,\n      },\n      root: {\n        zIndex: 999,\n        paddingLeft: 20,\n        paddingRight: 20,\n        boxShadow: \"0 4px 8px 0 rgb(0 0 0 / 10%), 0 0 2px 0 rgb(0 0 0 / 10%)\",\n        height: 80,\n        \"& .MuiButton-label\": {\n          color: colors.black,\n        },\n        \"& .MuiLink-underlineHover:hover\": {\n          textDecoration: \"none !important\",\n        },\n      },\n    },\n    MuiAutocomplete: {\n      input: {\n        padding: \"12px 16px !important\",\n      },\n      paper: {\n        fontSize: fontSizes.fontSize2,\n      },\n      popupIndicator: {\n        fontSize: fontSizes.fontSize5,\n        color: baseTheme.palette.text.primary,\n      },\n      clearIndicator: {\n        fontSize: fontSizes.fontSize5,\n      },\n      inputRoot: {\n        padding: \"0px !important\",\n      },\n      listbox: {\n        backgroundColor: baseTheme.palette.common.white,\n      },\n      tag: {\n        \"&:first-child\": {\n          marginLeft: 8,\n        },\n      },\n    },\n    MuiTablePagination: {\n      select: {\n        paddingRight: \"32px !important\",\n      },\n      selectRoot: {\n        top: 1,\n      },\n    },\n  },\n};\n\nconst finalTheme = createMuiTheme({\n  ...baseTheme,\n  ...overrides,\n});\n\nexport default finalTheme;\n"
  },
  {
    "path": "ui/src/theme/variables.js",
    "content": "export { colorOverrides as colors } from \"./colorOverrides\";\n\nexport const fontSizes = {\n  fontSize0: \"10px\",\n  fontSize1: \"12px\",\n  fontSize2: \"13px\",\n  fontSize3: \"14px\",\n  fontSize4: \"16px\",\n  fontSize5: \"18px\",\n  fontSize6: \"20px\",\n  fontSize7: \"24px\",\n  fontSize8: \"28px\",\n  fontSize9: \"32px\",\n  fontSize10: \"40px\",\n  fontSize11: \"52px\",\n  fontSize12: \"68px\",\n  fontSize13: \"88px\",\n};\nexport const lineHeights = {\n  lineHeight0: 1.25,\n  lineHeight1: 1.5,\n};\n\nexport const fontWeights = {\n  fontWeight0: 400,\n  fontWeight1: 600,\n  fontWeight2: 700,\n  fontWeight3: 800,\n};\n\nexport const fontFamily = {\n  fontFamilySans:\n    '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\"',\n  fontFamilyMono: \"monospace\",\n};\n\nexport const spacings = {\n  space0: \"4px\",\n  space1: \"8px\",\n  space2: \"12px\",\n  space3: \"16px\",\n  space4: \"20px\",\n  space5: \"24px\",\n  space6: \"32px\",\n  space7: \"48px\",\n  space8: \"80px\",\n  space9: \"144px\",\n};\n\nexport const breakpoints = {\n  xsmall: \"599px\",\n  small: \"1023px\",\n  medium: \"1439px\",\n  large: \"1919px\",\n  xlarge: \"3840px\",\n};\n\nexport const borders = {\n  radiusSmall: \"4px\",\n  blueRegular2px: \"2px solid rgba(31,131,219,1)\",\n  blackRegular1px: \"1px solid rgba(5,5,5,1)\",\n  blackLight1px: \"1px solid rgba(5,5,5,0.7)\",\n};\n"
  },
  {
    "path": "ui/src/utils/constants.js",
    "content": "export const workflowStatuses = [\n  \"RUNNING\",\n  \"COMPLETED\",\n  \"FAILED\",\n  \"TIMED_OUT\",\n  \"TERMINATED\",\n  \"PAUSED\",\n];\n\nexport const TASK_STATUSES = [\n  \"IN_PROGRESS\",\n  \"CANCELED\",\n  \"FAILED\",\n  \"FAILED_WITH_TERMINAL_ERROR\",\n  \"COMPLETED\",\n  \"COMPLETED_WITH_ERRORS\",\n  \"SCHEDULED\",\n  \"TIMED_OUT\",\n  \"SKIPPED\",\n];\n\nexport const TASK_TYPES = [\n  \"ARCHER\",\n  \"DECISION\",\n  \"DO_WHILE\",\n  \"DYNAMIC\",\n  \"DYNIMO\",\n  \"EAAS\",\n  \"EVENT\",\n  \"EXCLUSIVE_JOIN\",\n  \"FORK_JOIN\",\n  \"FORK_JOIN_DYNAMIC\",\n  \"HTTP\",\n  \"INLINE\",\n  \"JOIN\",\n  \"JSON_JQ_TRANSFORM\",\n  \"LAMBDA\",\n  \"SIMPLE\",\n  \"SUB_WORKFLOW\",\n  \"SWITCH\",\n  \"TERMINATE\",\n  \"TITUS\",\n  \"TITUS_TASK\",\n  \"WAIT\",\n];\n\nexport const SEARCH_TASK_TYPES_SET = modifyTaskTypes(TASK_TYPES);\n\nfunction modifyTaskTypes(taskTypes) {\n  const newTaskTypes = taskTypes.filter(\n    (ele) => ele !== \"FORK_JOIN_DYNAMIC\" && ele !== \"SIMPLE\"\n  );\n  const fjIdx = newTaskTypes.findIndex((ele) => ele === \"FORK_JOIN\");\n  newTaskTypes[fjIdx] = \"FORK\";\n\n  return new Set(newTaskTypes);\n}\n"
  },
  {
    "path": "ui/src/utils/helpers.js",
    "content": "import { format, formatDuration, intervalToDuration } from \"date-fns\";\nimport _ from \"lodash\";\nimport packageJson from '../../package.json';\n\nexport function timestampRenderer(date) {\n  if (_.isNil(date)) return null;\n\n  const parsed = new Date(date);\n  if (parsed.getTime() === 0) return null; // 0 epoch (UTC 1970-1-1)\n\n  return format(parsed, \"yyyy-MM-dd HH:mm:ss\");\n}\nexport function timestampMsRenderer(date) {\n  if (_.isNil(date)) return null;\n\n  const parsed = new Date(date);\n  if (parsed.getTime() === 0) return null; // 0 epoch (UTC 1970-1-1)\n\n  return format(parsed, \"yyyy-MM-dd HH:mm:ss.SSS\");\n}\n\nexport function durationRenderer(durationMs) {\n  const duration = intervalToDuration({ start: 0, end: durationMs });\n  if (durationMs > 5000) {\n    return formatDuration(duration);\n  } else {\n    return `${durationMs}ms`;\n  }\n}\n\nexport function taskHasResult(task) {\n  const keys = Object.keys(task);\n  return !(keys.length === 1 && keys[0] === \"workflowTask\");\n}\n\nexport function astToQuery(node) {\n  // leaf node\n  if (node.operator !== undefined) {\n    return node.field + node.operator + node.value;\n  } else if (node.combinator !== undefined) {\n    const clauses = node.rules\n      .filter((rule) => !(rule.rules && rule.rules.length === 0)) // Ignore empty groups\n      .map((rule) => astToQuery(rule));\n    const wrapper = clauses.length > 1;\n\n    let combinator = node.combinator.toUpperCase();\n\n    return `${wrapper ? \"(\" : \"\"}${clauses.join(` ${combinator} `)}${\n      wrapper ? \")\" : \"\"\n    }`;\n  } else {\n    return \"\";\n  }\n}\n\nexport function isFailedTask(status) {\n  return (\n    status === \"FAILED\" ||\n    status === \"FAILED_WITH_TERMINAL_ERROR\" ||\n    status === \"TIMED_OUT\" ||\n    status === \"CANCELED\"\n  );\n}\n\nexport function defaultCompare(x, y) {\n  if (x === undefined && y === undefined) return 0;\n\n  if (x === undefined) return 1;\n\n  if (y === undefined) return -1;\n\n  if (x < y) return -1;\n\n  if (x > y) return 1;\n\n  return 0;\n}\n\nexport function immutableReplaceAt(array, index, value) {\n  const ret = array.slice(0);\n  ret[index] = value;\n  return ret;\n}\n\nexport function isEmptyIterable(iterable) {\n  // eslint-disable-next-line no-unused-vars, no-unreachable-loop\n  for (const _ of iterable) {\n    return false;\n  }\n  return true;\n}\n\nexport function getBasename() {\n  let basename = '/';\n  try{\n    basename = new URL(packageJson.homepage).pathname;\n  } catch(e) {}\n  return _.isEmpty(basename) ? '/' : basename;\n}\n"
  },
  {
    "path": "ui/src/utils/localstorage.ts",
    "content": "import { useState } from \"react\";\n\n// If key is null/undefined, hook behaves exactly like useState\nexport const useLocalStorage = (key: string, initialValue: any) => {\n  const initialString = JSON.stringify(initialValue);\n\n  const [storedValue, setStoredValue] = useState(() => {\n    if (key) {\n      const item = window.localStorage.getItem(key);\n      return item ? JSON.parse(item) : initialValue;\n    } else {\n      return initialValue;\n    }\n  });\n\n  const setValue = (value: any) => {\n    // Allow value to be a function so we have same API as useState\n    const valueToStore = value instanceof Function ? value(storedValue) : value;\n\n    // Save state\n    setStoredValue(valueToStore);\n\n    if (key) {\n      const stringToStore = JSON.stringify(valueToStore);\n      if (stringToStore === initialString) {\n        window.localStorage.removeItem(key);\n      } else {\n        window.localStorage.setItem(key, stringToStore);\n      }\n    }\n  };\n\n  return [storedValue, setValue] as const;\n};\n"
  },
  {
    "path": "ui/src/utils/path.js",
    "content": "import { isEmptyIterable } from \"./helpers\";\n\nclass Path {\n  constructor(pathname) {\n    this.search = new URLSearchParams();\n    this.pathname = pathname;\n  }\n\n  toString() {\n    return (\n      this.pathname +\n      (isEmptyIterable(this.search) ? \"\" : `?${this.search.toString()}`)\n    );\n  }\n}\n\nexport default Path;\n"
  },
  {
    "path": "ui/test-karbon.sh",
    "content": ""
  },
  {
    "path": "ui/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\"src\"]\n}\n"
  }
]