This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git


The following commit(s) were added to refs/heads/master by this push:
     new 99432b15 Initial commit for distributed workload manager
99432b15 is described below

commit 99432b1503304404b5bff18b3ae944f05fd6b4bb
Author: DImuthuUpe <[email protected]>
AuthorDate: Fri Nov 28 09:52:34 2025 -0500

    Initial commit for distributed workload manager
---
 distributed-workqueue/work-client/README.md        |  21 ++
 distributed-workqueue/work-client/go.mod           |   3 +
 distributed-workqueue/work-client/main.go          |  90 +++++++
 distributed-workqueue/work-orchestrator/.gitignore |  33 +++
 distributed-workqueue/work-orchestrator/mvnw       | 295 +++++++++++++++++++++
 distributed-workqueue/work-orchestrator/mvnw.cmd   | 189 +++++++++++++
 .../work/orchestrator/WorkOrchestratorMain.java    |  13 +
 .../controller/WorkloadController.java             |  80 ++++++
 .../work/orchestrator/core/RegistryValidator.java  | 133 ++++++++++
 .../airavata/work/orchestrator/model/WorkItem.java | 108 ++++++++
 .../work/orchestrator/model/WorkItemStatus.java    |   8 +
 .../airavata/work/orchestrator/model/Workload.java |  69 +++++
 .../work/orchestrator/model/WorkloadStatus.java    |   8 +
 .../repository/WorkItemRepository.java             |  11 +
 .../repository/WorkloadRepository.java             |  20 ++
 .../work/orchestrator/service/WorkloadService.java |  93 +++++++
 .../src/main/resources/application.properties      |   1 +
 .../orchestrator/WorkOrchestratorMainTests.java    |  13 +
 18 files changed, 1188 insertions(+)

diff --git a/distributed-workqueue/work-client/README.md 
b/distributed-workqueue/work-client/README.md
new file mode 100644
index 00000000..6a7ecd78
--- /dev/null
+++ b/distributed-workqueue/work-client/README.md
@@ -0,0 +1,21 @@
+# Development build
+go run main.go
+
+# Build for your current platform
+go build -o work-client
+
+# Build for specific platforms by setting GOOS and GOARCH
+# Linux (64-bit)
+GOOS=linux GOARCH=amd64 go build -o work-client-linux-amd64
+
+# Windows (64-bit)
+GOOS=windows GOARCH=amd64 go build -o work-client-windows-amd64.exe
+
+# macOS (Intel)
+GOOS=darwin GOARCH=amd64 go build -o work-client-darwin-amd64
+
+# macOS (Apple Silicon)
+GOOS=darwin GOARCH=arm64 go build -o work-client-darwin-arm64
+
+# Linux (ARM64)
+GOOS=linux GOARCH=arm64 go build -o work-client-linux-arm64
\ No newline at end of file
diff --git a/distributed-workqueue/work-client/go.mod 
b/distributed-workqueue/work-client/go.mod
new file mode 100644
index 00000000..02ab05df
--- /dev/null
+++ b/distributed-workqueue/work-client/go.mod
@@ -0,0 +1,3 @@
+module work-client
+
+go 1.24.2
diff --git a/distributed-workqueue/work-client/main.go 
b/distributed-workqueue/work-client/main.go
new file mode 100644
index 00000000..c5fd4e59
--- /dev/null
+++ b/distributed-workqueue/work-client/main.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+       "encoding/json"
+       "fmt"
+       "io"
+       "log"
+       "net/http"
+       "os"
+)
+
+// Response represents the workload lease API response
+type Response struct {
+       AiravataExperimentID     string  `json:"airavataExperimentId"`
+       AiravataExperimentStatus *string `json:"airavataExperimentStatus"`
+       ID                       string  `json:"id"`
+       LastUpdated              int64   `json:"lastUpdated"`
+       Status                   string  `json:"status"`
+       WorkConfig               string  `json:"workConfig"`
+       WorkloadID               string  `json:"workloadId"`
+}
+
+func main() {
+       // Read experiment ID from environment variable or use default
+       experimentId := os.Getenv("EXPERIMENT_ID")
+       if experimentId == "" {
+               experimentId = "exp001"
+       }
+
+       // Read workload ID from environment variable or use default
+       workloadId := os.Getenv("WORKLOAD_ID")
+       if workloadId == "" {
+               workloadId = "ff8c561f-aa49-4920-b324-c30fe0610610"
+       }
+
+       // Read base URL from environment variable or use default
+       baseURL := os.Getenv("BASE_URL")
+       if baseURL == "" {
+               baseURL = "http://localhost:8080";
+       }
+
+       apiURL := baseURL + "/api/workload/lease/" + workloadId + "/" + 
experimentId
+
+       // Make the HTTP GET request
+       resp, err := http.Get(apiURL)
+       if err != nil {
+               log.Fatalf("Error making request: %v", err)
+       }
+       defer resp.Body.Close()
+
+       // Check if the request was successful
+       if resp.StatusCode == http.StatusNoContent {
+               log.Fatalf("Error: Resource not found (204 No Content)")
+       }
+
+       if resp.StatusCode != http.StatusOK {
+               log.Fatalf("Request failed with status: %s", resp.Status)
+       }
+
+       // Read the response body
+       body, err := io.ReadAll(resp.Body)
+       if err != nil {
+               log.Fatalf("Error reading response: %v", err)
+       }
+
+       // Print raw JSON response
+       fmt.Println("Raw JSON Response:")
+       fmt.Println(string(body))
+       fmt.Println()
+
+       // Parse JSON into struct
+       var data Response
+       if err := json.Unmarshal(body, &data); err != nil {
+               log.Fatalf("Error parsing JSON: %v", err)
+       }
+
+       // Print parsed data
+       fmt.Println("Parsed Data:")
+       fmt.Printf("ID: %s\n", data.ID)
+       fmt.Printf("Workload ID: %s\n", data.WorkloadID)
+       fmt.Printf("Airavata Experiment ID: %s\n", data.AiravataExperimentID)
+       fmt.Printf("Status: %s\n", data.Status)
+       fmt.Printf("Work Config: %s\n", data.WorkConfig)
+       fmt.Printf("Last Updated: %d\n", data.LastUpdated)
+       if data.AiravataExperimentStatus != nil {
+               fmt.Printf("Airavata Experiment Status: %s\n", 
*data.AiravataExperimentStatus)
+       } else {
+               fmt.Println("Airavata Experiment Status: null")
+       }
+}
diff --git a/distributed-workqueue/work-orchestrator/.gitignore 
b/distributed-workqueue/work-orchestrator/.gitignore
new file mode 100644
index 00000000..667aaef0
--- /dev/null
+++ b/distributed-workqueue/work-orchestrator/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/distributed-workqueue/work-orchestrator/mvnw 
b/distributed-workqueue/work-orchestrator/mvnw
new file mode 100755
index 00000000..bd8896bf
--- /dev/null
+++ b/distributed-workqueue/work-orchestrator/mvnw
@@ -0,0 +1,295 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.4
+#
+# Optional ENV vars
+# -----------------
+#   JAVA_HOME - location of a JDK home dir, required when download maven via 
java source
+#   MVNW_REPOURL - repo url base for downloading maven distribution
+#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; 
others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+  [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+  native_path() { cygpath --path --windows "$1"; }
+  ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+  # For Cygwin and MinGW, ensure paths are in Unix format before anything is 
touched
+  if [ -n "${JAVA_HOME-}" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+      JAVACCMD="$JAVA_HOME/jre/sh/javac"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+      JAVACCMD="$JAVA_HOME/bin/javac"
+
+      if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+        echo "The JAVA_HOME environment variable is not defined correctly, so 
mvnw cannot run." >&2
+        echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" 
or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+        return 1
+      fi
+    fi
+  else
+    JAVACMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v java
+    )" || :
+    JAVACCMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v javac
+    )" || :
+
+    if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+      echo "The java/javac command does not exist in PATH nor is JAVA_HOME 
set, so mvnw cannot run." >&2
+      return 1
+    fi
+  fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+  str="${1:-}" h=0
+  while [ -n "$str" ]; do
+    char="${str%"${str#?}"}"
+    h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+    str="${str#?}"
+  done
+  printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+  printf %s\\n "$1" >&2
+  exit 1
+}
+
+trim() {
+  # MWRAPPER-139:
+  #   Trims trailing and leading whitespace, carriage returns, tabs, and 
linefeeds.
+  #   Needed for removing poorly interpreted newline sequences when running in 
more
+  #   exotic environments such as mingw bash on Windows.
+  printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# parse distributionUrl and optional distributionSha256Sum, requires 
.mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+  case "${key-}" in
+  distributionUrl) distributionUrl=$(trim "${value-}") ;;
+  distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+  esac
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in 
$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+  case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+  *)
+    echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use 
pure java version" >&2
+    distributionPlatform=linux-amd64
+    ;;
+  esac
+  distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+  ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: 
~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] || 
distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string
 "$distributionUrl")"
+
+exec_maven() {
+  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+  exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec 
$MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+  verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or 
maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+  clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+  trap clean HUP INT TERM EXIT
+else
+  die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+  distributionUrl="${distributionUrl%.zip}.tar.gz"
+  distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q 
__MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' 
__MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' 
;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+  verbose "Found wget ... using wget"
+  wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O 
"$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch 
$distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+  verbose "Found curl ... using curl"
+  curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o 
"$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: 
Failed to fetch $distributionUrl"
+elif set_java_home; then
+  verbose "Falling back to use Java to download"
+  javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+  targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+  cat >"$javaSource" <<-END
+       public class Downloader extends java.net.Authenticator
+       {
+         protected java.net.PasswordAuthentication getPasswordAuthentication()
+         {
+           return new java.net.PasswordAuthentication( System.getenv( 
"MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+         }
+         public static void main( String[] args ) throws Exception
+         {
+           setDefault( new Downloader() );
+           java.nio.file.Files.copy( java.net.URI.create( args[0] 
).toURL().openStream(), java.nio.file.Paths.get( args[1] 
).toAbsolutePath().normalize() );
+         }
+       }
+       END
+  # For Cygwin/MinGW, switch paths to Windows format before running javac and 
java
+  verbose " - Compiling Downloader.java ..."
+  "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed 
to compile Downloader.java"
+  verbose " - Running Downloader.java ..."
+  "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" 
Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+  distributionSha256Result=false
+  if [ "$MVN_CMD" = mvnd.sh ]; then
+    echo "Checksum validation is not supported for maven-mvnd." >&2
+    echo "Please disable validation by removing 'distributionSha256Sum' from 
your maven-wrapper.properties." >&2
+    exit 1
+  elif command -v sha256sum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | 
sha256sum -c - >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  elif command -v shasum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | 
shasum -a 256 -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 
'shasum' are available." >&2
+    echo "Please install either command, or disable validation by removing 
'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  fi
+  if [ $distributionSha256Result = false ]; then
+    echo "Error: Failed to validate Maven distribution SHA-256, your Maven 
distribution might be compromised." >&2
+    echo "If you updated your Maven version, you need to update the specified 
distributionSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+  unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} 
"$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed 
to unzip"
+else
+  tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} 
"$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed 
to untar"
+fi
+
+# Find the actual extracted directory name (handles snapshots where filename 
!= directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+  if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+    actualDistributionDir="$distributionUrlNameMain"
+  fi
+fi
+
+# If not found, search for any directory with the Maven executable (for 
snapshots)
+if [ -z "$actualDistributionDir" ]; then
+  # enable globbing to iterate over items
+  set +f
+  for dir in "$TMP_DOWNLOAD_DIR"/*; do
+    if [ -d "$dir" ]; then
+      if [ -f "$dir/bin/$MVN_CMD" ]; then
+        actualDistributionDir="$(basename "$dir")"
+        break
+      fi
+    fi
+  done
+  set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+  verbose "Contents of $TMP_DOWNLOAD_DIR:"
+  verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+  die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" 
>"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d 
"$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/distributed-workqueue/work-orchestrator/mvnw.cmd 
b/distributed-workqueue/work-orchestrator/mvnw.cmd
new file mode 100644
index 00000000..92450f93
--- /dev/null
+++ b/distributed-workqueue/work-orchestrator/mvnw.cmd
@@ -0,0 +1,189 @@
+<# : batch portion
+@REM 
----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM 
----------------------------------------------------------------------------
+
+@REM 
----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
+@REM
+@REM Optional ENV vars
+@REM   MVNW_REPOURL - repo url base for downloading maven distribution
+@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM 
----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& 
{$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock 
([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+  IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE 
(echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+  $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw 
"$scriptDir/.mvn/wrapper/maven-wrapper.properties" | 
ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+  Write-Error "cannot read distributionUrl property in 
$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+  "maven-mvnd-*" {
+    $USE_MVND = $true
+    $distributionUrl = $distributionUrl -replace 
'-bin\.[^.]*$',"-windows-amd64.zip"
+    $MVN_CMD = "mvnd.cmd"
+    break
+  }
+  default {
+    $USE_MVND = $false
+    $MVN_CMD = $script -replace '^mvnw','mvn'
+    break
+  }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: 
~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+  $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else 
{ "/maven/mvnd/" }
+  $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl 
-replace "^.*$MVNW_REPO_PATTERN",'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' 
-replace '-bin$',''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+  $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+    New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+  $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+  $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = 
([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl)
 | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+  Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+  exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq 
$distributionUrlNameMain)) {
+  Write-Error "distributionUrl is not valid, must end with *-bin.zip, but 
found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path 
"$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+  if ($TMP_DOWNLOAD_DIR.Exists) {
+    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+    catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+  }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+  $webclient.Credentials = New-Object 
System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, 
"$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw 
"$scriptDir/.mvn/wrapper/maven-wrapper.properties" | 
ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+  if ($USE_MVND) {
+    Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease 
disable validation by removing 'distributionSha256Sum' from your 
maven-wrapper.properties."
+  }
+  Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function 
Get-FileHash
+  if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm 
SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+    Write-Error "Error: Failed to validate Maven distribution SHA-256, your 
Maven distribution might be compromised. If you updated your Maven version, you 
need to update the specified distributionSha256Sum property."
+  }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath 
"$TMP_DOWNLOAD_DIR" | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename 
!= directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path 
$expectedMvnPath -PathType Leaf)) {
+  $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for 
snapshots)
+if (!$actualDistributionDir) {
+  Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+    $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+    if (Test-Path -Path $testPath -PathType Leaf) {
+      $actualDistributionDir = $_.Name
+    }
+  }
+}
+
+if (!$actualDistributionDir) {
+  Write-Error "Could not find Maven distribution directory in extracted 
archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: 
$actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName 
$MAVEN_HOME_NAME | Out-Null
+try {
+  Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination 
$MAVEN_HOME_PARENT | Out-Null
+} catch {
+  if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+    Write-Error "fail to move MAVEN_HOME"
+  }
+} finally {
+  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+  catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMain.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMain.java
new file mode 100644
index 00000000..4bc5e831
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMain.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.work.orchestrator;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class WorkOrchestratorMain {
+
+       public static void main(String[] args) {
+               SpringApplication.run(WorkOrchestratorMain.class, args);
+       }
+
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/controller/WorkloadController.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/controller/WorkloadController.java
new file mode 100644
index 00000000..5ed3e2a0
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/controller/WorkloadController.java
@@ -0,0 +1,80 @@
+package org.apache.airavata.work.orchestrator.controller;
+
+import java.util.Optional;
+
+import org.apache.airavata.work.orchestrator.model.WorkItem;
+import org.apache.airavata.work.orchestrator.model.WorkItemStatus;
+import org.apache.airavata.work.orchestrator.model.Workload;
+import org.apache.airavata.work.orchestrator.service.WorkloadService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/workload")
+public class WorkloadController {
+
+    @Autowired
+    private WorkloadService workloadService;
+
+    @PostMapping("/workitem")
+    public ResponseEntity<WorkItem> createWorkItem(@RequestBody WorkItem 
workItem) {
+        WorkItem createdItem = workloadService.createWorkItem(workItem);
+        return new ResponseEntity<>(createdItem, HttpStatus.CREATED);
+    }
+
+    @GetMapping("/workitem/{id}")
+    public ResponseEntity<WorkItem> getWorkItem(@PathVariable String id) {
+        Optional<WorkItem> item = workloadService.getWorkItem(id);
+        return ResponseEntity.ok(item.orElse(null));
+    }
+
+    @GetMapping("/workitem")
+    public ResponseEntity<?> getAllWorkItems() {
+        return ResponseEntity.ok(workloadService.getAllWorkItems());
+    }
+
+    @DeleteMapping("/workitem/{id}")
+    public ResponseEntity<Void> deleteWorkItem(@PathVariable String id) {
+        workloadService.deleteWorkItem(id);
+        return ResponseEntity.noContent().build();
+    }
+
+    @PostMapping
+    public ResponseEntity<Workload> createWorkload(@RequestBody Workload 
workload) {
+        Workload createdWorkload = workloadService.createWorkload(workload);
+        return new ResponseEntity<>(createdWorkload, HttpStatus.CREATED);
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Workload> getWorkload(@PathVariable String id) {
+        Optional<Workload> workload = workloadService.getWorkload(id);
+        return ResponseEntity.ok(workload.orElse(null));
+    }
+
+    @GetMapping
+    public ResponseEntity<Iterable<Workload>> getAllWorkloads() {
+        return ResponseEntity.ok(workloadService.getAllWorkloads());
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteWorkload(@PathVariable String id) {
+        workloadService.deleteWorkload(id);
+        return ResponseEntity.noContent().build();
+    }
+    @GetMapping("/lease/{workloadId}/{airavataExperimentId}")
+    public ResponseEntity<WorkItem> leaseWorkItem(@PathVariable String 
workloadId, @PathVariable String airavataExperimentId) {
+        Optional<WorkItem> leasedItem = 
workloadService.leaseWorkItem(workloadId, airavataExperimentId);
+        return leasedItem.map(item -> ResponseEntity.ok(item))
+                         .orElseGet(() -> 
ResponseEntity.status(HttpStatus.NO_CONTENT).build());
+    }
+
+    @PutMapping("/workitem/{workItemId}/status")
+    public ResponseEntity<WorkItem> updateWorkItemStatus(@PathVariable String 
workItemId, @RequestParam("status") String status) {
+        Optional<WorkItem> updatedItem = 
workloadService.updateWorkItemStatus(workItemId, 
Enum.valueOf(WorkItemStatus.class, status));
+        return updatedItem.map(item -> ResponseEntity.ok(item))
+                          .orElseGet(() -> 
ResponseEntity.status(HttpStatus.NOT_FOUND).build());
+    }
+
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/core/RegistryValidator.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/core/RegistryValidator.java
new file mode 100644
index 00000000..9df0f75a
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/core/RegistryValidator.java
@@ -0,0 +1,133 @@
+package org.apache.airavata.work.orchestrator.core;
+
+import org.apache.airavata.common.exception.AiravataException;
+import org.apache.airavata.common.utils.AiravataUtils;
+import org.apache.airavata.common.utils.ServerSettings;
+import org.apache.airavata.common.utils.ThriftClientPool;
+import org.apache.airavata.messaging.core.*;
+import org.apache.airavata.messaging.core.impl.RabbitMQPublisher;
+import 
org.apache.airavata.model.appcatalog.appinterface.ApplicationInterfaceDescription;
+import 
org.apache.airavata.model.appcatalog.groupresourceprofile.GroupComputeResourcePreference;
+import 
org.apache.airavata.model.appcatalog.groupresourceprofile.GroupResourceProfile;
+import org.apache.airavata.model.experiment.ExperimentCleanupStrategy;
+import org.apache.airavata.model.experiment.ExperimentModel;
+import org.apache.airavata.model.experiment.ExperimentType;
+import org.apache.airavata.model.experiment.UserConfigurationDataModel;
+import org.apache.airavata.model.messaging.event.ExperimentSubmitEvent;
+import org.apache.airavata.model.messaging.event.MessageType;
+import 
org.apache.airavata.model.scheduling.ComputationalResourceSchedulingModel;
+import org.apache.airavata.model.workspace.Gateway;
+import org.apache.airavata.registry.api.RegistryService;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.apache.thrift.TException;
+
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+public class RegistryValidator {
+    private static ThriftClientPool<RegistryService.Client> registryClientPool;
+    private static Publisher experimentPublisher;
+
+    private static <T> GenericObjectPoolConfig<T> 
createGenericObjectPoolConfig() {
+
+        GenericObjectPoolConfig<T> poolConfig = new 
GenericObjectPoolConfig<T>();
+        poolConfig.setMaxTotal(100);
+        poolConfig.setMinIdle(5);
+        poolConfig.setBlockWhenExhausted(true);
+        poolConfig.setTestOnBorrow(true);
+        poolConfig.setTestWhileIdle(true);
+        // must set timeBetweenEvictionRunsMillis since eviction doesn't run 
unless that is positive
+        poolConfig.setTimeBetweenEvictionRunsMillis(5L * 60L * 1000L);
+        poolConfig.setNumTestsPerEvictionRun(10);
+        poolConfig.setMaxWaitMillis(3000);
+        return poolConfig;
+    }
+
+    private static RabbitMQProperties getProperties() {
+        return (new RabbitMQProperties())
+                .setBrokerUrl("amqp://guest:guest@localhost:5672/develop")
+                .setDurable(false)
+                .setPrefetchCount(200)
+                .setAutoRecoveryEnable(true)
+                .setConsumerTag("default")
+                .setExchangeName("experiment_exchange")
+                .setExchangeType(RabbitMQProperties.EXCHANGE_TYPE.TOPIC);
+    }
+
+    public static void main(String args[]) throws TException, 
AiravataException {
+        registryClientPool = new ThriftClientPool<>(
+                tProtocol -> new RegistryService.Client(tProtocol),
+                createGenericObjectPoolConfig(),
+                "localhost",8970);
+
+        RabbitMQProperties rProperties = getProperties();
+        experimentPublisher = new RabbitMQPublisher(rProperties, 
(messageContext) -> rProperties.getExchangeName());
+
+        RegistryService.Client resource = registryClientPool.getResource();
+        List<Gateway> allGateways = resource.getAllGateways();
+        String expName = "VizfoldRepr-" + UUID.randomUUID().toString();
+        ExperimentModel experimentModel = new ExperimentModel();
+
+        experimentModel.setExperimentName(expName);
+        
experimentModel.setProjectId("Default_Project_93d9a30a-4299-44dd-9e48-a7b8961464dc");
+        experimentModel.setUserName("default-admin");
+        experimentModel.setGatewayId("default");
+        
experimentModel.setExecutionId("VizfoldRepresentation_c2c025a7-36f7-4c7d-b7b2-970f02de40f9");
+
+        String groupResourceProfileId = 
"02881ab4-51c6-462d-a4dd-1b3a0d0fa921"; // Validate the access
+
+        ComputationalResourceSchedulingModel 
computationalResourceSchedulingModel =
+                new ComputationalResourceSchedulingModel();
+
+        List<GroupResourceProfile> groupResourceList = 
resource.getGroupResourceList("default", 
Collections.singletonList(groupResourceProfileId));
+        GroupResourceProfile grp = groupResourceList.get(0);
+        Optional<GroupComputeResourcePreference> grpCrp = 
grp.getComputePreferences().stream().filter(pre -> 
pre.getComputeResourceId().equals("NCSADelta_e75b0d04-8b4b-417b-8ab4-da76bbd835f5")).findFirst();
+        GroupComputeResourcePreference groupCompResourcePref = grpCrp.get();
+
+        computationalResourceSchedulingModel.setQueueName("gpuA100x4");
+        computationalResourceSchedulingModel.setNodeCount(1);
+        computationalResourceSchedulingModel.setTotalCPUCount(8);
+        computationalResourceSchedulingModel.setWallTimeLimit(10);
+        computationalResourceSchedulingModel.setTotalPhysicalMemory(0);
+        
computationalResourceSchedulingModel.setResourceHostId(groupCompResourcePref.getComputeResourceId());
+        
computationalResourceSchedulingModel.setOverrideScratchLocation(groupCompResourcePref.getScratchLocation());
+        
computationalResourceSchedulingModel.setOverrideAllocationProjectNumber("bbol-delta-gpu");
+        
computationalResourceSchedulingModel.setOverrideLoginUserName(groupCompResourcePref.getLoginUserName());
+
+        UserConfigurationDataModel userConfigurationDataModel = new 
UserConfigurationDataModel();
+        
userConfigurationDataModel.setComputationalResourceScheduling(computationalResourceSchedulingModel);
+        userConfigurationDataModel.setAiravataAutoSchedule(false);
+        userConfigurationDataModel.setOverrideManualScheduledParams(false);
+        
userConfigurationDataModel.setInputStorageResourceId("default_9c15d8af-3d36-4c3c-a07a-0f3b4bb5b903");
+        
userConfigurationDataModel.setOutputStorageResourceId("vizfold_9c15d8af-3d36-4c3c-a07a-0f3b4bb5b904");
+        String experimentDataDir = "/reprs/" + expName;
+        userConfigurationDataModel.setExperimentDataDir(experimentDataDir);
+        
userConfigurationDataModel.setGroupResourceProfileId(groupCompResourcePref.getGroupResourceProfileId());
+
+        experimentModel.setUserConfigurationData(userConfigurationDataModel);
+
+        String appInterfaceId = 
"VizfoldRepresentation_c2c025a7-36f7-4c7d-b7b2-970f02de40f9";
+
+        ApplicationInterfaceDescription applicationInterface = 
resource.getApplicationInterface("VizfoldRepresentation_c2c025a7-36f7-4c7d-b7b2-970f02de40f9");
+
+        
experimentModel.setExperimentInputs(applicationInterface.getApplicationInputs());
+        
experimentModel.setExperimentOutputs(applicationInterface.getApplicationOutputs());
+
+        experimentModel.setExperimentType(ExperimentType.SINGLE_APPLICATION);
+        
experimentModel.setCleanUpStrategy(ExperimentCleanupStrategy.ONLY_COMPLETED);
+
+        String experimentId = resource.createExperiment("default", 
experimentModel);
+
+
+        ExperimentSubmitEvent event = new ExperimentSubmitEvent(experimentId, 
"default");
+        MessageContext messageContext = new MessageContext(
+                event, MessageType.EXPERIMENT, "LAUNCH.EXP-" + 
UUID.randomUUID().toString(), "default");
+        messageContext.setUpdatedTime(AiravataUtils.getCurrentTimestamp());
+
+        experimentPublisher.publish(messageContext);
+    }
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItem.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItem.java
new file mode 100644
index 00000000..4a191ffc
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItem.java
@@ -0,0 +1,108 @@
+package org.apache.airavata.work.orchestrator.model;
+
+import org.hibernate.annotations.UuidGenerator;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "work_item")
+public class WorkItem {
+
+    @Id
+    @GeneratedValue
+    @UuidGenerator
+    @Column(nullable = false, updatable = false, length = 48)
+    private String id;
+
+    @Column(name = "status")
+    private WorkItemStatus status;
+
+    @Column(name = "last_updated")
+    private long lastUpdated;
+
+    @Column(name = "work_config")
+    private String workConfig;
+
+    @Column(name = "airavata_experiment_id")
+    private String airavataExperimentId;
+
+    @Column(name = "airavata_experiment_status")
+    private String airavataExperimentStatus;
+
+    @JsonIgnore
+    @ManyToOne
+    @JoinColumn(name = "workload_id")
+    private Workload workload;
+
+    @Column(name = "workload_id", insertable = false, updatable = false)
+    private String workloadId;
+
+    public String getId() {
+        return id;
+    }
+
+    public WorkItemStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(WorkItemStatus status) {
+        this.status = status;
+    }
+
+    public long getLastUpdated() {
+        return lastUpdated;
+    }
+
+    public void setLastUpdated(long lastUpdated) {
+        this.lastUpdated = lastUpdated;
+    }
+
+    public String getWorkConfig() {
+        return workConfig;
+    }
+
+    public void setWorkConfig(String workConfig) {
+        this.workConfig = workConfig;
+    }
+
+    public String getAiravataExperimentId() {
+        return airavataExperimentId;
+    }
+
+    public void setAiravataExperimentId(String airavataExperimentId) {
+        this.airavataExperimentId = airavataExperimentId;
+    }
+
+    public String getAiravataExperimentStatus() {
+        return airavataExperimentStatus;
+    }
+
+    public void setAiravataExperimentStatus(String airavataExperimentStatus) {
+        this.airavataExperimentStatus = airavataExperimentStatus;
+    }
+
+    public Workload getWorkload() {
+        return workload;
+    }
+
+    public void setWorkload(Workload workload) {
+        this.workload = workload;
+    }
+
+    public String getWorkloadId() {
+        return workloadId;
+    }
+
+    public void setWorkloadId(String workloadId) {
+        this.workloadId = workloadId;
+    }
+
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItemStatus.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItemStatus.java
new file mode 100644
index 00000000..68b1083a
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItemStatus.java
@@ -0,0 +1,8 @@
+package org.apache.airavata.work.orchestrator.model;
+
+public enum WorkItemStatus {
+    NOT_ASSIGNED,
+    IN_PROGRESS,
+    COMPLETED,
+    FAILED
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/Workload.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/Workload.java
new file mode 100644
index 00000000..b5ce7020
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/Workload.java
@@ -0,0 +1,69 @@
+package org.apache.airavata.work.orchestrator.model;
+
+import java.util.List;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "workload")
+public class Workload {
+    @Id
+    @GeneratedValue
+    @Column(nullable = false, updatable = false, length = 48)
+    private String id;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "status")
+    private WorkloadStatus status;
+
+    @Column(name = "last_updated")
+    private long lastUpdated;
+
+    @OneToMany(mappedBy = "workload", cascade = CascadeType.ALL, fetch = 
FetchType.LAZY)
+    private List<WorkItem> workItems;
+
+    public String getId() {
+        return id;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public List<WorkItem> getWorkItems() {
+        return workItems;
+    }
+
+    public void setWorkItems(List<WorkItem> workItems) {
+        this.workItems = workItems;
+    }
+
+    public WorkloadStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(WorkloadStatus status) {
+        this.status = status;
+    }
+
+    public long getLastUpdated() {
+        return lastUpdated;
+    }
+
+    public void setLastUpdated(long lastUpdated) {
+        this.lastUpdated = lastUpdated;
+    }
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkloadStatus.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkloadStatus.java
new file mode 100644
index 00000000..dd651058
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkloadStatus.java
@@ -0,0 +1,8 @@
+package org.apache.airavata.work.orchestrator.model;
+
+public enum WorkloadStatus {
+    NOT_STARTED,
+    IN_PROGRESS,
+    COMPLETED,
+    FAILED
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkItemRepository.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkItemRepository.java
new file mode 100644
index 00000000..8ee8e625
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkItemRepository.java
@@ -0,0 +1,11 @@
+package org.apache.airavata.work.orchestrator.repository;
+
+import java.util.List;
+
+import org.apache.airavata.work.orchestrator.model.WorkItem;
+import org.apache.airavata.work.orchestrator.model.WorkItemStatus;
+import org.springframework.data.repository.CrudRepository;
+
+public interface WorkItemRepository extends CrudRepository<WorkItem, String> {
+    public List<WorkItem> findByWorkloadIdAndStatus(String workloadId, 
WorkItemStatus status);
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkloadRepository.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkloadRepository.java
new file mode 100644
index 00000000..efb87e2a
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkloadRepository.java
@@ -0,0 +1,20 @@
+package org.apache.airavata.work.orchestrator.repository;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.airavata.work.orchestrator.model.Workload;
+import org.apache.airavata.work.orchestrator.model.WorkloadStatus;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface WorkloadRepository extends CrudRepository<Workload, String> {
+
+    public Optional<Workload> findById(String id);
+
+    public List<Workload> findByStatus(WorkloadStatus status);
+
+    public List<Workload> findAll();
+
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/service/WorkloadService.java
 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/service/WorkloadService.java
new file mode 100644
index 00000000..b81c0066
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/service/WorkloadService.java
@@ -0,0 +1,93 @@
+package org.apache.airavata.work.orchestrator.service;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.airavata.work.orchestrator.model.WorkItem;
+import org.apache.airavata.work.orchestrator.model.WorkItemStatus;
+import org.apache.airavata.work.orchestrator.model.Workload;
+import org.apache.airavata.work.orchestrator.repository.WorkItemRepository;
+import org.apache.airavata.work.orchestrator.repository.WorkloadRepository;
+import org.springframework.stereotype.Service;
+
+@Service
+public class WorkloadService {
+
+    private WorkItemRepository workItemRepository;
+    private WorkloadRepository workloadRepository;
+
+    public WorkloadService(WorkItemRepository workItemRepository, 
WorkloadRepository workloadRepository) {
+        this.workItemRepository = workItemRepository;
+        this.workloadRepository = workloadRepository;
+    }
+
+
+    public Workload createWorkload(Workload workload) {
+        return workloadRepository.save(workload);
+    }
+
+    public Optional<Workload> getWorkload(String id) {
+        return workloadRepository.findById(id);
+    }
+
+    public Iterable<Workload> getAllWorkloads() {
+        return workloadRepository.findAll();
+    }
+
+    public void deleteWorkload(String id) {
+        workloadRepository.deleteById(id);
+    }
+
+    public WorkItem createWorkItem(WorkItem workItem) {
+        // If workloadId is set but workload relationship isn't, fetch and set 
it
+        if (workItem.getWorkloadId() != null && workItem.getWorkload() == 
null) {
+            Optional<Workload> workloadOpt = 
workloadRepository.findById(workItem.getWorkloadId());
+            if (workloadOpt.isPresent()) {
+                workItem.setWorkload(workloadOpt.get());
+            } else {
+                throw new RuntimeException("Workload not found with id: " + 
workItem.getWorkloadId());
+            }
+        }
+        return workItemRepository.save(workItem);
+    }
+
+
+    public Optional<WorkItem> getWorkItem(String id) {
+        return workItemRepository.findById(id);
+    }
+
+    public Iterable<WorkItem> getAllWorkItems() {
+        return workItemRepository.findAll();
+    }
+
+    public void deleteWorkItem(String id) {
+        workItemRepository.deleteById(id);
+    }
+
+    public Optional<WorkItem> leaseWorkItem(String workloadId, String 
airavataExperimentId) {
+
+        synchronized(this) { // Make sure only one work item is leased at a 
time
+            List<WorkItem> workItemOpt = 
workItemRepository.findByWorkloadIdAndStatus(workloadId, 
WorkItemStatus.NOT_ASSIGNED);
+            if (!workItemOpt.isEmpty()) {
+                WorkItem workItem = workItemOpt.get(0);
+                workItem.setStatus(WorkItemStatus.IN_PROGRESS);
+                workItem.setAiravataExperimentId(airavataExperimentId);
+                workItemRepository.save(workItem);
+                return Optional.of(workItem);
+            }
+            return Optional.empty();
+        }
+    }
+
+    public Optional<WorkItem> updateWorkItemStatus(String workItemId, 
WorkItemStatus status) {
+        Optional<WorkItem> workItemOpt = 
workItemRepository.findById(workItemId);
+        if (workItemOpt.isPresent()) {
+            WorkItem workItem = workItemOpt.get();
+            workItem.setStatus(status);
+            workItemRepository.save(workItem);
+            return Optional.of(workItem);
+        }
+        return Optional.empty();
+    }
+
+}
diff --git 
a/distributed-workqueue/work-orchestrator/src/main/resources/application.properties
 
b/distributed-workqueue/work-orchestrator/src/main/resources/application.properties
new file mode 100644
index 00000000..2109a440
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.application.name=demo
diff --git 
a/distributed-workqueue/work-orchestrator/src/test/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMainTests.java
 
b/distributed-workqueue/work-orchestrator/src/test/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMainTests.java
new file mode 100644
index 00000000..46366909
--- /dev/null
+++ 
b/distributed-workqueue/work-orchestrator/src/test/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMainTests.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.work.orchestrator;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class WorkOrchestratorMainTests {
+
+       @Test
+       void contextLoads() {
+       }
+
+}

Reply via email to