You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by di...@apache.org on 2026/08/07 03:30:33 UTC
(airavata) branch refactor-single-module updated: Adding service and controller layers for application template and deployment
This is an automated email from the ASF dual-hosted git repository.
DImuthuUpe pushed a commit to branch refactor-single-module
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/refactor-single-module by this push:
new 980aa409d9 Adding service and controller layers for application template and deployment
980aa409d9 is described below
commit 980aa409d96f455f454dc7a4becb905c7de624ec
Author: DImuthuUpe <di...@gmail.com>
AuthorDate: Thu Aug 6 23:30:27 2026 -0400
Adding service and controller layers for application template and deployment
---
.../airavata/api/ApplicationModuleController.java | 57 ----
.../api/ApplicationTemplateController.java | 56 ++++
.../api/SlurmApplicationDeploymentController.java | 62 ++++
.../SlurmApplicationDeploymentRequestDto.java | 218 +++++++++++++
.../SlurmApplicationDeploymentResponseDto.java | 209 +++++++++++++
.../dto/template/ApplicationTemplateInputDto.java | 85 +++++
.../dto/template/ApplicationTemplateOutputDto.java | 60 ++++
.../template/ApplicationTemplateRequestDto.java | 56 ++++
.../template/ApplicationTemplateResponseDto.java | 56 ++++
.../application/mapper/ApplicationMapper.java | 342 ---------------------
.../mapper/ApplicationTemplateMapper.java | 34 ++
.../mapper/SlurmApplicationDeploymentMapper.java | 28 ++
.../model/template/ApplicationTemplateEntity.java | 13 +-
.../ApplicationDeploymentRepository.java | 29 --
.../repository/ApplicationInputRepository.java | 29 --
.../repository/ApplicationInterfaceRepository.java | 29 --
.../repository/ApplicationModuleRepository.java | 27 --
.../repository/ApplicationOutputRepository.java | 27 --
.../repository/ApplicationTemplateRepository.java | 11 +
.../SlurmApplicationDeploymentRepository.java | 15 +
.../service/ApplicationInputService.java | 8 -
.../service/ApplicationInterfaceService.java | 18 --
.../service/ApplicationModuleService.java | 79 -----
.../service/ApplicationTemplateService.java | 117 +++++++
.../service/SlurmApplicationDeploymentService.java | 88 ++++++
25 files changed, 1104 insertions(+), 649 deletions(-)
diff --git a/src/main/java/org/apache/airavata/api/ApplicationModuleController.java b/src/main/java/org/apache/airavata/api/ApplicationModuleController.java
deleted file mode 100644
index 516a8f6cd8..0000000000
--- a/src/main/java/org/apache/airavata/api/ApplicationModuleController.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.apache.airavata.api;
-
-import java.util.List;
-
-import org.apache.airavata.application.service.ApplicationModuleService;
-import org.apache.airavata.models.appcatalog.appdeployment.ApplicationModule;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.bind.annotation.DeleteMapping;
-
-@RestController
-@RequestMapping("/api/v1/appmodule")
-public class ApplicationModuleController {
-
- private final ApplicationModuleService applicationModuleService;
-
- public ApplicationModuleController(ApplicationModuleService applicationModuleService) {
- this.applicationModuleService = applicationModuleService;
- }
-
- @GetMapping("/{moduleId}")
- @ResponseBody
- public ApplicationModule getApplicationModule(@PathVariable String moduleId) throws Exception {
- return applicationModuleService.getApplicationModule(null, moduleId);
- }
-
- @GetMapping("")
- @ResponseBody
- public List<ApplicationModule> getAllApplicationModules() throws Exception {
- return applicationModuleService.getAllApplicationModules();
- }
-
- @PostMapping("")
- @ResponseBody
- public ApplicationModule createApplicationModule(@RequestBody ApplicationModule module) throws Exception {
- return applicationModuleService.createApplicationModule(null, module);
- }
-
- @PostMapping("/{moduleId}")
- @ResponseBody
- public ApplicationModule updateApplicationModule(
- @PathVariable String moduleId, @RequestBody ApplicationModule module) throws Exception {
- return applicationModuleService.updateApplicationModule(null, moduleId, module);
- }
-
- @DeleteMapping("/{moduleId}")
- @ResponseBody
- public void deleteApplicationModule(@PathVariable String moduleId) throws Exception {
- applicationModuleService.deleteApplicationModule(null, moduleId);
- }
-
-}
diff --git a/src/main/java/org/apache/airavata/api/ApplicationTemplateController.java b/src/main/java/org/apache/airavata/api/ApplicationTemplateController.java
new file mode 100644
index 0000000000..8d5d6c3976
--- /dev/null
+++ b/src/main/java/org/apache/airavata/api/ApplicationTemplateController.java
@@ -0,0 +1,56 @@
+package org.apache.airavata.api;
+
+import jakarta.validation.Valid;
+import java.util.List;
+import org.apache.airavata.application.dto.template.ApplicationTemplateRequestDto;
+import org.apache.airavata.application.dto.template.ApplicationTemplateResponseDto;
+import org.apache.airavata.application.service.ApplicationTemplateService;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/v1/application-templates")
+public class ApplicationTemplateController {
+
+ private final ApplicationTemplateService applicationTemplateService;
+
+ public ApplicationTemplateController(ApplicationTemplateService applicationTemplateService) {
+ this.applicationTemplateService = applicationTemplateService;
+ }
+
+ @GetMapping
+ public List<ApplicationTemplateResponseDto> getAllTemplates() {
+ return applicationTemplateService.getAllTemplates();
+ }
+
+ @GetMapping("/{templateId}")
+ public ApplicationTemplateResponseDto getTemplate(@PathVariable String templateId) {
+ return applicationTemplateService.getTemplate(templateId);
+ }
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public ApplicationTemplateResponseDto createTemplate(@Valid @RequestBody ApplicationTemplateRequestDto request) {
+ return applicationTemplateService.createTemplate(request);
+ }
+
+ @PutMapping("/{templateId}")
+ public ApplicationTemplateResponseDto updateTemplate(
+ @PathVariable String templateId, @Valid @RequestBody ApplicationTemplateRequestDto request) {
+ return applicationTemplateService.updateTemplate(templateId, request);
+ }
+
+ @DeleteMapping("/{templateId}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteTemplate(@PathVariable String templateId) {
+ applicationTemplateService.deleteTemplate(templateId);
+ }
+}
diff --git a/src/main/java/org/apache/airavata/api/SlurmApplicationDeploymentController.java b/src/main/java/org/apache/airavata/api/SlurmApplicationDeploymentController.java
new file mode 100644
index 0000000000..a44a1c8c98
--- /dev/null
+++ b/src/main/java/org/apache/airavata/api/SlurmApplicationDeploymentController.java
@@ -0,0 +1,62 @@
+package org.apache.airavata.api;
+
+import jakarta.validation.Valid;
+import java.util.List;
+import org.apache.airavata.application.dto.deployment.SlurmApplicationDeploymentRequestDto;
+import org.apache.airavata.application.dto.deployment.SlurmApplicationDeploymentResponseDto;
+import org.apache.airavata.application.service.SlurmApplicationDeploymentService;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/v1/slurm-deployments")
+public class SlurmApplicationDeploymentController {
+
+ private final SlurmApplicationDeploymentService slurmApplicationDeploymentService;
+
+ public SlurmApplicationDeploymentController(SlurmApplicationDeploymentService slurmApplicationDeploymentService) {
+ this.slurmApplicationDeploymentService = slurmApplicationDeploymentService;
+ }
+
+ /** Lists every deployment, or only those of {@code templateId} when supplied. */
+ @GetMapping
+ public List<SlurmApplicationDeploymentResponseDto> getAllDeployments(
+ @RequestParam(required = false) String templateId) {
+ return templateId == null
+ ? slurmApplicationDeploymentService.getAllDeployments()
+ : slurmApplicationDeploymentService.getDeploymentsByTemplate(templateId);
+ }
+
+ @GetMapping("/{deploymentId}")
+ public SlurmApplicationDeploymentResponseDto getDeployment(@PathVariable String deploymentId) {
+ return slurmApplicationDeploymentService.getDeployment(deploymentId);
+ }
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public SlurmApplicationDeploymentResponseDto createDeployment(
+ @Valid @RequestBody SlurmApplicationDeploymentRequestDto request) {
+ return slurmApplicationDeploymentService.createDeployment(request);
+ }
+
+ @PutMapping("/{deploymentId}")
+ public SlurmApplicationDeploymentResponseDto updateDeployment(
+ @PathVariable String deploymentId, @Valid @RequestBody SlurmApplicationDeploymentRequestDto request) {
+ return slurmApplicationDeploymentService.updateDeployment(deploymentId, request);
+ }
+
+ @DeleteMapping("/{deploymentId}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteDeployment(@PathVariable String deploymentId) {
+ slurmApplicationDeploymentService.deleteDeployment(deploymentId);
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/dto/deployment/SlurmApplicationDeploymentRequestDto.java b/src/main/java/org/apache/airavata/application/dto/deployment/SlurmApplicationDeploymentRequestDto.java
new file mode 100644
index 0000000000..47576f0266
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/dto/deployment/SlurmApplicationDeploymentRequestDto.java
@@ -0,0 +1,218 @@
+package org.apache.airavata.application.dto.deployment;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+
+/**
+ * Create/update payload for a Slurm deployment of an application template.
+ * Required fields mirror the {@code nullable = false} columns on the entity.
+ */
+public class SlurmApplicationDeploymentRequestDto {
+
+ @NotBlank(message = "Template id cannot be blank")
+ private String templateId;
+
+ private String slurmClusterId;
+
+ @NotBlank(message = "Slurm run section cannot be blank")
+ private String slurmRunSection;
+
+ @NotNull(message = "Wall time cannot be null")
+ @Positive(message = "Wall time must be positive")
+ private Long wallTimeMinutes;
+
+ @NotBlank(message = "Allocation cannot be blank")
+ private String allocation;
+
+ @NotBlank(message = "User cannot be blank")
+ private String user;
+
+ // Slurm resource allocation parameters
+ private Integer cpus;
+ private String mem;
+ private String memPerCpu;
+ private Integer ntasksPerNode;
+ private Integer cpusPerTask;
+ private Integer nodes;
+ private Integer ntasks;
+
+ // GPU related fields
+ private String gres;
+ private Integer gpus;
+ private String memPerGpu;
+ private String cpusPerGpu;
+ private Integer gpusPerNode;
+
+ private String constraints;
+ private String workDir;
+ private String partition;
+
+ public String getTemplateId() {
+ return templateId;
+ }
+
+ public void setTemplateId(String templateId) {
+ this.templateId = templateId;
+ }
+
+ public String getSlurmClusterId() {
+ return slurmClusterId;
+ }
+
+ public void setSlurmClusterId(String slurmClusterId) {
+ this.slurmClusterId = slurmClusterId;
+ }
+
+ public String getSlurmRunSection() {
+ return slurmRunSection;
+ }
+
+ public void setSlurmRunSection(String slurmRunSection) {
+ this.slurmRunSection = slurmRunSection;
+ }
+
+ public Long getWallTimeMinutes() {
+ return wallTimeMinutes;
+ }
+
+ public void setWallTimeMinutes(Long wallTimeMinutes) {
+ this.wallTimeMinutes = wallTimeMinutes;
+ }
+
+ public String getAllocation() {
+ return allocation;
+ }
+
+ public void setAllocation(String allocation) {
+ this.allocation = allocation;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public Integer getCpus() {
+ return cpus;
+ }
+
+ public void setCpus(Integer cpus) {
+ this.cpus = cpus;
+ }
+
+ public String getMem() {
+ return mem;
+ }
+
+ public void setMem(String mem) {
+ this.mem = mem;
+ }
+
+ public String getMemPerCpu() {
+ return memPerCpu;
+ }
+
+ public void setMemPerCpu(String memPerCpu) {
+ this.memPerCpu = memPerCpu;
+ }
+
+ public Integer getNtasksPerNode() {
+ return ntasksPerNode;
+ }
+
+ public void setNtasksPerNode(Integer ntasksPerNode) {
+ this.ntasksPerNode = ntasksPerNode;
+ }
+
+ public Integer getCpusPerTask() {
+ return cpusPerTask;
+ }
+
+ public void setCpusPerTask(Integer cpusPerTask) {
+ this.cpusPerTask = cpusPerTask;
+ }
+
+ public Integer getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Integer nodes) {
+ this.nodes = nodes;
+ }
+
+ public Integer getNtasks() {
+ return ntasks;
+ }
+
+ public void setNtasks(Integer ntasks) {
+ this.ntasks = ntasks;
+ }
+
+ public String getGres() {
+ return gres;
+ }
+
+ public void setGres(String gres) {
+ this.gres = gres;
+ }
+
+ public Integer getGpus() {
+ return gpus;
+ }
+
+ public void setGpus(Integer gpus) {
+ this.gpus = gpus;
+ }
+
+ public String getMemPerGpu() {
+ return memPerGpu;
+ }
+
+ public void setMemPerGpu(String memPerGpu) {
+ this.memPerGpu = memPerGpu;
+ }
+
+ public String getCpusPerGpu() {
+ return cpusPerGpu;
+ }
+
+ public void setCpusPerGpu(String cpusPerGpu) {
+ this.cpusPerGpu = cpusPerGpu;
+ }
+
+ public Integer getGpusPerNode() {
+ return gpusPerNode;
+ }
+
+ public void setGpusPerNode(Integer gpusPerNode) {
+ this.gpusPerNode = gpusPerNode;
+ }
+
+ public String getConstraints() {
+ return constraints;
+ }
+
+ public void setConstraints(String constraints) {
+ this.constraints = constraints;
+ }
+
+ public String getWorkDir() {
+ return workDir;
+ }
+
+ public void setWorkDir(String workDir) {
+ this.workDir = workDir;
+ }
+
+ public String getPartition() {
+ return partition;
+ }
+
+ public void setPartition(String partition) {
+ this.partition = partition;
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/dto/deployment/SlurmApplicationDeploymentResponseDto.java b/src/main/java/org/apache/airavata/application/dto/deployment/SlurmApplicationDeploymentResponseDto.java
new file mode 100644
index 0000000000..ce81d4c6d0
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/dto/deployment/SlurmApplicationDeploymentResponseDto.java
@@ -0,0 +1,209 @@
+package org.apache.airavata.application.dto.deployment;
+
+/** Read view of a Slurm deployment; the owning template is referenced by id only. */
+public class SlurmApplicationDeploymentResponseDto {
+
+ private String deploymentId;
+ private String templateId;
+ private String slurmClusterId;
+ private String slurmRunSection;
+ private Long wallTimeMinutes;
+ private String allocation;
+ private String user;
+
+ // Slurm resource allocation parameters
+ private Integer cpus;
+ private String mem;
+ private String memPerCpu;
+ private Integer ntasksPerNode;
+ private Integer cpusPerTask;
+ private Integer nodes;
+ private Integer ntasks;
+
+ // GPU related fields
+ private String gres;
+ private Integer gpus;
+ private String memPerGpu;
+ private String cpusPerGpu;
+ private Integer gpusPerNode;
+
+ private String constraints;
+ private String workDir;
+ private String partition;
+
+ public String getDeploymentId() {
+ return deploymentId;
+ }
+
+ public void setDeploymentId(String deploymentId) {
+ this.deploymentId = deploymentId;
+ }
+
+ public String getTemplateId() {
+ return templateId;
+ }
+
+ public void setTemplateId(String templateId) {
+ this.templateId = templateId;
+ }
+
+ public String getSlurmClusterId() {
+ return slurmClusterId;
+ }
+
+ public void setSlurmClusterId(String slurmClusterId) {
+ this.slurmClusterId = slurmClusterId;
+ }
+
+ public String getSlurmRunSection() {
+ return slurmRunSection;
+ }
+
+ public void setSlurmRunSection(String slurmRunSection) {
+ this.slurmRunSection = slurmRunSection;
+ }
+
+ public Long getWallTimeMinutes() {
+ return wallTimeMinutes;
+ }
+
+ public void setWallTimeMinutes(Long wallTimeMinutes) {
+ this.wallTimeMinutes = wallTimeMinutes;
+ }
+
+ public String getAllocation() {
+ return allocation;
+ }
+
+ public void setAllocation(String allocation) {
+ this.allocation = allocation;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public Integer getCpus() {
+ return cpus;
+ }
+
+ public void setCpus(Integer cpus) {
+ this.cpus = cpus;
+ }
+
+ public String getMem() {
+ return mem;
+ }
+
+ public void setMem(String mem) {
+ this.mem = mem;
+ }
+
+ public String getMemPerCpu() {
+ return memPerCpu;
+ }
+
+ public void setMemPerCpu(String memPerCpu) {
+ this.memPerCpu = memPerCpu;
+ }
+
+ public Integer getNtasksPerNode() {
+ return ntasksPerNode;
+ }
+
+ public void setNtasksPerNode(Integer ntasksPerNode) {
+ this.ntasksPerNode = ntasksPerNode;
+ }
+
+ public Integer getCpusPerTask() {
+ return cpusPerTask;
+ }
+
+ public void setCpusPerTask(Integer cpusPerTask) {
+ this.cpusPerTask = cpusPerTask;
+ }
+
+ public Integer getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Integer nodes) {
+ this.nodes = nodes;
+ }
+
+ public Integer getNtasks() {
+ return ntasks;
+ }
+
+ public void setNtasks(Integer ntasks) {
+ this.ntasks = ntasks;
+ }
+
+ public String getGres() {
+ return gres;
+ }
+
+ public void setGres(String gres) {
+ this.gres = gres;
+ }
+
+ public Integer getGpus() {
+ return gpus;
+ }
+
+ public void setGpus(Integer gpus) {
+ this.gpus = gpus;
+ }
+
+ public String getMemPerGpu() {
+ return memPerGpu;
+ }
+
+ public void setMemPerGpu(String memPerGpu) {
+ this.memPerGpu = memPerGpu;
+ }
+
+ public String getCpusPerGpu() {
+ return cpusPerGpu;
+ }
+
+ public void setCpusPerGpu(String cpusPerGpu) {
+ this.cpusPerGpu = cpusPerGpu;
+ }
+
+ public Integer getGpusPerNode() {
+ return gpusPerNode;
+ }
+
+ public void setGpusPerNode(Integer gpusPerNode) {
+ this.gpusPerNode = gpusPerNode;
+ }
+
+ public String getConstraints() {
+ return constraints;
+ }
+
+ public void setConstraints(String constraints) {
+ this.constraints = constraints;
+ }
+
+ public String getWorkDir() {
+ return workDir;
+ }
+
+ public void setWorkDir(String workDir) {
+ this.workDir = workDir;
+ }
+
+ public String getPartition() {
+ return partition;
+ }
+
+ public void setPartition(String partition) {
+ this.partition = partition;
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateInputDto.java b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateInputDto.java
new file mode 100644
index 0000000000..5a97fe62d9
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateInputDto.java
@@ -0,0 +1,85 @@
+package org.apache.airavata.application.dto.template;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import org.apache.airavata.application.model.template.ApplicationTemplateInputType;
+
+public class ApplicationTemplateInputDto {
+
+ /** Server assigned; ignored on create/update requests. */
+ private String inputId;
+
+ @NotBlank(message = "Input name cannot be blank")
+ private String inputName;
+
+ private String displayName;
+ private String inputDescription;
+
+ @NotNull(message = "Input type cannot be null")
+ private ApplicationTemplateInputType inputType;
+
+ private boolean required;
+
+ /**
+ * Default input as JSON string. For example:
+ * - For a single value: {"value": "defaultValue"}
+ * - For a list of values: {"values": ["defaultValue1", "defaultValue2"]}
+ */
+ private String defaultValue;
+
+ public String getInputId() {
+ return inputId;
+ }
+
+ public void setInputId(String inputId) {
+ this.inputId = inputId;
+ }
+
+ public String getInputName() {
+ return inputName;
+ }
+
+ public void setInputName(String inputName) {
+ this.inputName = inputName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getInputDescription() {
+ return inputDescription;
+ }
+
+ public void setInputDescription(String inputDescription) {
+ this.inputDescription = inputDescription;
+ }
+
+ public ApplicationTemplateInputType getInputType() {
+ return inputType;
+ }
+
+ public void setInputType(ApplicationTemplateInputType inputType) {
+ this.inputType = inputType;
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public void setDefaultValue(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateOutputDto.java b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateOutputDto.java
new file mode 100644
index 0000000000..b1d439b5ec
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateOutputDto.java
@@ -0,0 +1,60 @@
+package org.apache.airavata.application.dto.template;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import org.apache.airavata.application.model.template.ApplicationTemplateOutputType;
+
+public class ApplicationTemplateOutputDto {
+
+ /** Server assigned; ignored on create/update requests. */
+ private String outputId;
+
+ @NotBlank(message = "Output name cannot be blank")
+ private String outputName;
+
+ private String displayName;
+ private String outputDescription;
+
+ @NotNull(message = "Output type cannot be null")
+ private ApplicationTemplateOutputType outputType;
+
+ public String getOutputId() {
+ return outputId;
+ }
+
+ public void setOutputId(String outputId) {
+ this.outputId = outputId;
+ }
+
+ public String getOutputName() {
+ return outputName;
+ }
+
+ public void setOutputName(String outputName) {
+ this.outputName = outputName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getOutputDescription() {
+ return outputDescription;
+ }
+
+ public void setOutputDescription(String outputDescription) {
+ this.outputDescription = outputDescription;
+ }
+
+ public ApplicationTemplateOutputType getOutputType() {
+ return outputType;
+ }
+
+ public void setOutputType(ApplicationTemplateOutputType outputType) {
+ this.outputType = outputType;
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateRequestDto.java b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateRequestDto.java
new file mode 100644
index 0000000000..7e5a1e7220
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateRequestDto.java
@@ -0,0 +1,56 @@
+package org.apache.airavata.application.dto.template;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * Create/update payload for an application template. Inputs and outputs are owned by
+ * the template, so they are declared inline here rather than through separate endpoints;
+ * an update replaces the previously declared sets.
+ */
+public class ApplicationTemplateRequestDto {
+
+ @NotBlank(message = "Template name cannot be blank")
+ private String templateName;
+
+ private String templateDescription;
+
+ @Valid
+ private List<ApplicationTemplateInputDto> inputs;
+
+ @Valid
+ private List<ApplicationTemplateOutputDto> outputs;
+
+ public String getTemplateName() {
+ return templateName;
+ }
+
+ public void setTemplateName(String templateName) {
+ this.templateName = templateName;
+ }
+
+ public String getTemplateDescription() {
+ return templateDescription;
+ }
+
+ public void setTemplateDescription(String templateDescription) {
+ this.templateDescription = templateDescription;
+ }
+
+ public List<ApplicationTemplateInputDto> getInputs() {
+ return inputs;
+ }
+
+ public void setInputs(List<ApplicationTemplateInputDto> inputs) {
+ this.inputs = inputs;
+ }
+
+ public List<ApplicationTemplateOutputDto> getOutputs() {
+ return outputs;
+ }
+
+ public void setOutputs(List<ApplicationTemplateOutputDto> outputs) {
+ this.outputs = outputs;
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateResponseDto.java b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateResponseDto.java
new file mode 100644
index 0000000000..799799976a
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/dto/template/ApplicationTemplateResponseDto.java
@@ -0,0 +1,56 @@
+package org.apache.airavata.application.dto.template;
+
+import java.util.List;
+
+/**
+ * Read view of an application template. Deployments are not inlined — they are a
+ * separate aggregate, reachable via {@code GET /api/v1/slurm-deployments?templateId=...}.
+ */
+public class ApplicationTemplateResponseDto {
+
+ private String templateId;
+ private String templateName;
+ private String templateDescription;
+ private List<ApplicationTemplateInputDto> inputs;
+ private List<ApplicationTemplateOutputDto> outputs;
+
+ public String getTemplateId() {
+ return templateId;
+ }
+
+ public void setTemplateId(String templateId) {
+ this.templateId = templateId;
+ }
+
+ public String getTemplateName() {
+ return templateName;
+ }
+
+ public void setTemplateName(String templateName) {
+ this.templateName = templateName;
+ }
+
+ public String getTemplateDescription() {
+ return templateDescription;
+ }
+
+ public void setTemplateDescription(String templateDescription) {
+ this.templateDescription = templateDescription;
+ }
+
+ public List<ApplicationTemplateInputDto> getInputs() {
+ return inputs;
+ }
+
+ public void setInputs(List<ApplicationTemplateInputDto> inputs) {
+ this.inputs = inputs;
+ }
+
+ public List<ApplicationTemplateOutputDto> getOutputs() {
+ return outputs;
+ }
+
+ public void setOutputs(List<ApplicationTemplateOutputDto> outputs) {
+ this.outputs = outputs;
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/mapper/ApplicationMapper.java b/src/main/java/org/apache/airavata/application/mapper/ApplicationMapper.java
deleted file mode 100644
index 7043ca2de2..0000000000
--- a/src/main/java/org/apache/airavata/application/mapper/ApplicationMapper.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/**
-*
-* 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.
-*/
-package org.apache.airavata.application.mapper;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.airavata.common.CommonMapperConversions;
-import org.apache.airavata.models.appcatalog.appdeployment.ApplicationDeploymentDescription;
-import org.apache.airavata.models.appcatalog.appdeployment.ApplicationModule;
-import org.apache.airavata.models.appcatalog.appdeployment.CommandObject;
-import org.apache.airavata.models.appcatalog.appdeployment.SetEnvPaths;
-import org.apache.airavata.models.appcatalog.appinterface.ApplicationInterfaceDescription;
-import org.apache.airavata.models.application.io.DataType;
-import org.apache.airavata.models.application.io.InputDataObjectType;
-import org.apache.airavata.models.application.io.OutputDataObjectType;
-import org.apache.airavata.application.model.AppIoParamEntity;
-import org.apache.airavata.application.model.ApplicationDeploymentEntity;
-import org.apache.airavata.application.model.ApplicationInterfaceEntity;
-import org.apache.airavata.application.model.ApplicationModuleEntity;
-import org.mapstruct.AfterMapping;
-import org.mapstruct.Mapper;
-import org.mapstruct.MappingTarget;
-import org.mapstruct.ReportingPolicy;
-import org.mapstruct.factory.Mappers;
-
-@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
-public interface ApplicationMapper extends CommonMapperConversions {
-
- ApplicationMapper INSTANCE = Mappers.getMapper(ApplicationMapper.class);
-
- // --- ApplicationInterfaceDescription ---
- ApplicationInterfaceDescription appInterfaceToModel(ApplicationInterfaceEntity entity);
-
- ApplicationInterfaceEntity appInterfaceToEntity(ApplicationInterfaceDescription model);
-
- // MapStruct does not match protobuf's repeated accessors to the entity's list
- // properties,
- // so it silently drops application_modules (plain strings) and the input/output
- // collections.
- @AfterMapping
- default void afterAppInterfaceToModel(
- ApplicationInterfaceEntity entity, @MappingTarget ApplicationInterfaceDescription.Builder builder) {
- if (entity.getApplicationModules() != null) {
- builder.addAllApplicationModules(entity.getApplicationModules());
- }
- if (entity.getApplicationInputs() != null) {
- for (AppIoParamEntity input : entity.getApplicationInputs()) {
- builder.addApplicationInputs(appInputToModel(input));
- }
- }
- if (entity.getApplicationOutputs() != null) {
- for (AppIoParamEntity output : entity.getApplicationOutputs()) {
- builder.addApplicationOutputs(appOutputToModel(output));
- }
- }
- }
-
- @AfterMapping
- default void afterAppInterfaceToEntity(
- ApplicationInterfaceDescription model, @MappingTarget ApplicationInterfaceEntity entity) {
- if (!model.applicationModules().isEmpty()) {
- entity.setApplicationModules(new ArrayList<>(model.applicationModules()));
- }
- if (!model.applicationInputs().isEmpty()) {
- List<AppIoParamEntity> inputs = new ArrayList<>();
- for (InputDataObjectType input : model.applicationInputs()) {
- inputs.add(appInputToEntity(input));
- }
- entity.setApplicationInputs(inputs);
- }
- if (!model.applicationOutputs().isEmpty()) {
- List<AppIoParamEntity> outputs = new ArrayList<>();
- for (OutputDataObjectType output : model.applicationOutputs()) {
- outputs.add(appOutputToEntity(output));
- }
- entity.setApplicationOutputs(outputs);
- }
- }
-
- // --- ApplicationModule ---
- ApplicationModule appModuleToModel(ApplicationModuleEntity entity);
-
- ApplicationModuleEntity appModuleToEntity(ApplicationModule model);
-
- // --- ApplicationDeploymentDescription ---
- default ApplicationDeploymentDescription appDeploymentToModel(ApplicationDeploymentEntity entity) {
- if (entity == null)
- return null;
- ApplicationDeploymentDescription.Builder builder = ApplicationDeploymentDescription.newBuilder();
- if (entity.getAppDeploymentId() != null)
- builder.setAppDeploymentId(entity.getAppDeploymentId());
- if (entity.getAppDeploymentDescription() != null)
- builder.setAppDeploymentDescription(entity.getAppDeploymentDescription());
- if (entity.getExecutablePath() != null)
- builder.setExecutablePath(entity.getExecutablePath());
- if (entity.getComputeHostId() != null)
- builder.setComputeHostId(entity.getComputeHostId());
- if (entity.getAppModuleId() != null)
- builder.setAppModuleId(entity.getAppModuleId());
- if (entity.getParallelism() != null)
- builder.setParallelism(entity.getParallelism());
- if (entity.getDefaultQueueName() != null)
- builder.setDefaultQueueName(entity.getDefaultQueueName());
- builder.setDefaultNodeCount(entity.getDefaultNodeCount());
- builder.setDefaultCpuCount(entity.getDefaultCPUCount());
- builder.setDefaultWalltime(entity.getDefaultWallTime());
- builder.setEditableByUser(entity.getEditableByUser());
- if (entity.getModuleLoadCmds() != null) {
- builder.addAllModuleLoadCmds(entity.getModuleLoadCmds().stream()
- .map(m -> CommandObject.newBuilder()
- .setCommand((String) m.getOrDefault("command", ""))
- .setCommandOrder(((Number) m.getOrDefault("commandOrder", 0)).intValue())
- .build())
- .toList());
- }
- if (entity.getPreJobCommands() != null) {
- builder.addAllPreJobCommands(entity.getPreJobCommands().stream()
- .map(m -> CommandObject.newBuilder()
- .setCommand((String) m.getOrDefault("command", ""))
- .setCommandOrder(((Number) m.getOrDefault("commandOrder", 0)).intValue())
- .build())
- .toList());
- }
- if (entity.getPostJobCommands() != null) {
- builder.addAllPostJobCommands(entity.getPostJobCommands().stream()
- .map(m -> CommandObject.newBuilder()
- .setCommand((String) m.getOrDefault("command", ""))
- .setCommandOrder(((Number) m.getOrDefault("commandOrder", 0)).intValue())
- .build())
- .toList());
- }
- if (entity.getLibPrependPaths() != null) {
- builder.addAllLibPrependPaths(entity.getLibPrependPaths().stream()
- .map(m -> SetEnvPaths.newBuilder()
- .setName((String) m.getOrDefault("name", ""))
- .setValue((String) m.getOrDefault("value", ""))
- .setEnvPathOrder(((Number) m.getOrDefault("envPathOrder", 0)).intValue())
- .build())
- .toList());
- }
- if (entity.getLibAppendPaths() != null) {
- builder.addAllLibAppendPaths(entity.getLibAppendPaths().stream()
- .map(m -> SetEnvPaths.newBuilder()
- .setName((String) m.getOrDefault("name", ""))
- .setValue((String) m.getOrDefault("value", ""))
- .setEnvPathOrder(((Number) m.getOrDefault("envPathOrder", 0)).intValue())
- .build())
- .toList());
- }
- if (entity.getSetEnvironment() != null) {
- builder.addAllSetEnvironment(entity.getSetEnvironment().stream()
- .map(m -> SetEnvPaths.newBuilder()
- .setName((String) m.getOrDefault("name", ""))
- .setValue((String) m.getOrDefault("value", ""))
- .setEnvPathOrder(((Number) m.getOrDefault("envPathOrder", 0)).intValue())
- .build())
- .toList());
- }
- return builder.build();
- }
-
- default ApplicationDeploymentEntity appDeploymentToEntity(ApplicationDeploymentDescription model) {
- if (model == null)
- return null;
- ApplicationDeploymentEntity entity = new ApplicationDeploymentEntity();
- entity.setAppDeploymentId(model.appDeploymentId());
- entity.setAppDeploymentDescription(model.appDeploymentDescription());
- entity.setExecutablePath(model.executablePath());
- entity.setComputeHostId(model.computeHostId());
- entity.setAppModuleId(model.appModuleId());
- entity.setParallelism(model.parallelism());
- entity.setDefaultQueueName(model.defaultQueueName());
- entity.setDefaultNodeCount(model.defaultNodeCount());
- entity.setDefaultCPUCount(model.defaultCpuCount());
- entity.setDefaultWallTime(model.defaultWalltime());
- entity.setEditableByUser(model.editableByUser());
- if (!model.moduleLoadCmds().isEmpty()) {
- entity.setModuleLoadCmds(model.moduleLoadCmds().stream()
- .map(cmd -> {
- Map<String, Object> m = new LinkedHashMap<>();
- m.put("command", cmd.command());
- m.put("commandOrder", cmd.commandOrder());
- return m;
- })
- .toList());
- }
- if (!model.preJobCommands().isEmpty()) {
- entity.setPreJobCommands(model.preJobCommands().stream()
- .map(cmd -> {
- Map<String, Object> m = new LinkedHashMap<>();
- m.put("command", cmd.command());
- m.put("commandOrder", cmd.commandOrder());
- return m;
- })
- .toList());
- }
- if (!model.postJobCommands().isEmpty()) {
- entity.setPostJobCommands(model.postJobCommands().stream()
- .map(cmd -> {
- Map<String, Object> m = new LinkedHashMap<>();
- m.put("command", cmd.command());
- m.put("commandOrder", cmd.commandOrder());
- return m;
- })
- .toList());
- }
- if (!model.libPrependPaths().isEmpty()) {
- entity.setLibPrependPaths(model.libPrependPaths().stream()
- .map(p -> {
- Map<String, Object> m = new LinkedHashMap<>();
- m.put("name", p.name());
- m.put("value", p.value());
- m.put("envPathOrder", p.envPathOrder());
- return m;
- })
- .toList());
- }
- if (!model.libAppendPaths().isEmpty()) {
- entity.setLibAppendPaths(model.libAppendPaths().stream()
- .map(p -> {
- Map<String, Object> m = new LinkedHashMap<>();
- m.put("name", p.name());
- m.put("value", p.value());
- m.put("envPathOrder", p.envPathOrder());
- return m;
- })
- .toList());
- }
- if (!model.setEnvironment().isEmpty()) {
- entity.setSetEnvironment(model.setEnvironment().stream()
- .map(p -> {
- Map<String, Object> m = new LinkedHashMap<>();
- m.put("name", p.name());
- m.put("value", p.value());
- m.put("envPathOrder", p.envPathOrder());
- return m;
- })
- .toList());
- }
- return entity;
- }
-
- // --- ApplicationInput (AppIoParamEntity, DIRECTION=INPUT) ---
- default InputDataObjectType appInputToModel(AppIoParamEntity entity) {
- if (entity == null)
- return null;
- return InputDataObjectType.newBuilder()
- .setName(entity.getName() != null ? entity.getName() : "")
- .setValue(entity.getValue() != null ? entity.getValue() : "")
- .setType(entity.getType() != null ? entity.getType() : DataType.DATA_TYPE_UNKNOWN)
- .setApplicationArgument(entity.getApplicationArgument() != null ? entity.getApplicationArgument() : "")
- .setStandardInput(entity.isStandardInput())
- .setUserFriendlyDescription(
- entity.getUserFriendlyDescription() != null ? entity.getUserFriendlyDescription() : "")
- .setMetaData(entity.getMetaData() != null ? entity.getMetaData() : "")
- .setInputOrder(entity.getInputOrder())
- .setIsRequired(entity.isIsRequired())
- .setRequiredToAddedToCommandLine(entity.isRequiredToAddedToCommandLine())
- .setDataStaged(entity.isDataStaged())
- .setIsReadOnly(entity.isReadOnly())
- .setOverrideFilename(entity.getOverrideFilename() != null ? entity.getOverrideFilename() : "")
- .build();
- }
-
- default AppIoParamEntity appInputToEntity(InputDataObjectType model) {
- if (model == null)
- return null;
- AppIoParamEntity entity = new AppIoParamEntity();
- entity.setDirection("INPUT");
- entity.setName(model.name());
- entity.setValue(model.value());
- entity.setType(model.type());
- entity.setApplicationArgument(model.applicationArgument());
- entity.setStandardInput(model.standardInput());
- entity.setUserFriendlyDescription(model.userFriendlyDescription());
- entity.setMetaData(model.metaData());
- entity.setInputOrder(model.inputOrder());
- entity.setIsRequired(model.isRequired());
- entity.setRequiredToAddedToCommandLine(model.requiredToAddedToCommandLine());
- entity.setDataStaged(model.dataStaged());
- entity.setReadOnly(model.isReadOnly());
- entity.setOverrideFilename(model.overrideFilename());
- return entity;
- }
-
- // --- ApplicationOutput (AppIoParamEntity, DIRECTION=OUTPUT) ---
- default OutputDataObjectType appOutputToModel(AppIoParamEntity entity) {
- if (entity == null)
- return null;
- return OutputDataObjectType.newBuilder()
- .setName(entity.getName() != null ? entity.getName() : "")
- .setValue(entity.getValue() != null ? entity.getValue() : "")
- .setType(entity.getType() != null ? entity.getType() : DataType.DATA_TYPE_UNKNOWN)
- .setApplicationArgument(entity.getApplicationArgument() != null ? entity.getApplicationArgument() : "")
- .setIsRequired(entity.isIsRequired())
- .setRequiredToAddedToCommandLine(entity.isRequiredToAddedToCommandLine())
- .setDataMovement(entity.isDataMovement())
- .setLocation(entity.getLocation() != null ? entity.getLocation() : "")
- .setSearchQuery(entity.getSearchQuery() != null ? entity.getSearchQuery() : "")
- .setOutputStreaming(entity.isOutputStreaming())
- .setMetaData(entity.getMetaData() != null ? entity.getMetaData() : "")
- .build();
- }
-
- default AppIoParamEntity appOutputToEntity(OutputDataObjectType model) {
- if (model == null)
- return null;
- AppIoParamEntity entity = new AppIoParamEntity();
- entity.setDirection("OUTPUT");
- entity.setName(model.name());
- entity.setValue(model.value());
- entity.setType(model.type());
- entity.setApplicationArgument(model.applicationArgument());
- entity.setIsRequired(model.isRequired());
- entity.setRequiredToAddedToCommandLine(model.requiredToAddedToCommandLine());
- entity.setDataMovement(model.dataMovement());
- entity.setLocation(model.location());
- entity.setSearchQuery(model.searchQuery());
- entity.setOutputStreaming(model.outputStreaming());
- entity.setMetaData(model.metaData());
- return entity;
- }
-}
diff --git a/src/main/java/org/apache/airavata/application/mapper/ApplicationTemplateMapper.java b/src/main/java/org/apache/airavata/application/mapper/ApplicationTemplateMapper.java
new file mode 100644
index 0000000000..cf4f94532e
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/mapper/ApplicationTemplateMapper.java
@@ -0,0 +1,34 @@
+package org.apache.airavata.application.mapper;
+
+import org.apache.airavata.application.dto.template.ApplicationTemplateInputDto;
+import org.apache.airavata.application.dto.template.ApplicationTemplateOutputDto;
+import org.apache.airavata.application.dto.template.ApplicationTemplateResponseDto;
+import org.apache.airavata.application.model.template.ApplicationTemplateEntity;
+import org.apache.airavata.application.model.template.ApplicationTemplateInputEntity;
+import org.apache.airavata.application.model.template.ApplicationTemplateOutputEntity;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+
+/**
+ * Entity/DTO conversions for the application template aggregate. The back-reference to
+ * the owning template is deliberately unmapped on the child entities — the service wires
+ * it up so both sides of the association stay consistent.
+ */
+@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface ApplicationTemplateMapper {
+
+ ApplicationTemplateResponseDto toResponseDto(ApplicationTemplateEntity entity);
+
+ ApplicationTemplateInputDto toInputDto(ApplicationTemplateInputEntity entity);
+
+ ApplicationTemplateOutputDto toOutputDto(ApplicationTemplateOutputEntity entity);
+
+ @Mapping(target = "inputId", ignore = true)
+ @Mapping(target = "applicationTemplate", ignore = true)
+ ApplicationTemplateInputEntity toInputEntity(ApplicationTemplateInputDto dto);
+
+ @Mapping(target = "outputId", ignore = true)
+ @Mapping(target = "applicationTemplate", ignore = true)
+ ApplicationTemplateOutputEntity toOutputEntity(ApplicationTemplateOutputDto dto);
+}
diff --git a/src/main/java/org/apache/airavata/application/mapper/SlurmApplicationDeploymentMapper.java b/src/main/java/org/apache/airavata/application/mapper/SlurmApplicationDeploymentMapper.java
new file mode 100644
index 0000000000..4f9a9c015d
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/mapper/SlurmApplicationDeploymentMapper.java
@@ -0,0 +1,28 @@
+package org.apache.airavata.application.mapper;
+
+import org.apache.airavata.application.dto.deployment.SlurmApplicationDeploymentRequestDto;
+import org.apache.airavata.application.dto.deployment.SlurmApplicationDeploymentResponseDto;
+import org.apache.airavata.application.model.deployment.SlurmApplicationDeploymentEntity;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.ReportingPolicy;
+
+/**
+ * Entity/DTO conversions for Slurm deployments. The template association is resolved by
+ * the service from {@code templateId}, so it is never mapped straight off the request.
+ */
+@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface SlurmApplicationDeploymentMapper {
+
+ @Mapping(target = "templateId", source = "applicationTemplate.templateId")
+ SlurmApplicationDeploymentResponseDto toResponseDto(SlurmApplicationDeploymentEntity entity);
+
+ @Mapping(target = "deploymentId", ignore = true)
+ @Mapping(target = "applicationTemplate", ignore = true)
+ SlurmApplicationDeploymentEntity toEntity(SlurmApplicationDeploymentRequestDto dto);
+
+ @Mapping(target = "deploymentId", ignore = true)
+ @Mapping(target = "applicationTemplate", ignore = true)
+ void updateEntity(SlurmApplicationDeploymentRequestDto dto, @MappingTarget SlurmApplicationDeploymentEntity entity);
+}
diff --git a/src/main/java/org/apache/airavata/application/model/template/ApplicationTemplateEntity.java b/src/main/java/org/apache/airavata/application/model/template/ApplicationTemplateEntity.java
index b3739d09a6..d068d42ea7 100644
--- a/src/main/java/org/apache/airavata/application/model/template/ApplicationTemplateEntity.java
+++ b/src/main/java/org/apache/airavata/application/model/template/ApplicationTemplateEntity.java
@@ -1,10 +1,12 @@
package org.apache.airavata.application.model.template;
+import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.OneToMany;
+import java.util.ArrayList;
import java.util.List;
import org.apache.airavata.application.model.deployment.SlurmApplicationDeploymentEntity;
@@ -17,11 +19,14 @@ public class ApplicationTemplateEntity {
private String templateName;
private String templateDescription;
- @OneToMany(mappedBy = "applicationTemplate")
- private List<ApplicationTemplateInputEntity> inputs;
+ // Inputs and outputs are owned by the template: they are created, replaced and
+ // deleted with it. Deployments are not — they outlive individual edits and are
+ // managed through their own endpoints.
+ @OneToMany(mappedBy = "applicationTemplate", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List<ApplicationTemplateInputEntity> inputs = new ArrayList<>();
- @OneToMany(mappedBy = "applicationTemplate")
- private List<ApplicationTemplateOutputEntity> outputs;
+ @OneToMany(mappedBy = "applicationTemplate", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List<ApplicationTemplateOutputEntity> outputs = new ArrayList<>();
@OneToMany(mappedBy = "applicationTemplate")
private List<SlurmApplicationDeploymentEntity> deployments;
diff --git a/src/main/java/org/apache/airavata/application/repository/ApplicationDeploymentRepository.java b/src/main/java/org/apache/airavata/application/repository/ApplicationDeploymentRepository.java
deleted file mode 100644
index e9bdef4095..0000000000
--- a/src/main/java/org/apache/airavata/application/repository/ApplicationDeploymentRepository.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
-*
-* 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.
-*/
-package org.apache.airavata.application.repository;
-
-import org.springframework.data.repository.CrudRepository;
-import org.springframework.stereotype.Component;
-import org.apache.airavata.application.model.ApplicationDeploymentEntity;
-
-@Component
-public interface ApplicationDeploymentRepository extends CrudRepository<ApplicationDeploymentEntity, String> {
-
-}
diff --git a/src/main/java/org/apache/airavata/application/repository/ApplicationInputRepository.java b/src/main/java/org/apache/airavata/application/repository/ApplicationInputRepository.java
deleted file mode 100644
index d37b8b34b9..0000000000
--- a/src/main/java/org/apache/airavata/application/repository/ApplicationInputRepository.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
-*
-* 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.
-*/
-package org.apache.airavata.application.repository;
-
-import org.springframework.data.repository.CrudRepository;
-import org.apache.airavata.application.model.AppIoParamEntity;
-import org.springframework.stereotype.Component;
-
-@Component
-public interface ApplicationInputRepository extends CrudRepository<AppIoParamEntity, String> {
-
-}
diff --git a/src/main/java/org/apache/airavata/application/repository/ApplicationInterfaceRepository.java b/src/main/java/org/apache/airavata/application/repository/ApplicationInterfaceRepository.java
deleted file mode 100644
index 2a5787c50a..0000000000
--- a/src/main/java/org/apache/airavata/application/repository/ApplicationInterfaceRepository.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
-*
-* 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.
-*/
-package org.apache.airavata.application.repository;
-
-import org.apache.airavata.application.model.ApplicationInterfaceEntity;
-import org.springframework.data.repository.CrudRepository;
-import org.springframework.stereotype.Component;
-
-@Component
-public interface ApplicationInterfaceRepository
- extends CrudRepository<ApplicationInterfaceEntity, String> {
-}
diff --git a/src/main/java/org/apache/airavata/application/repository/ApplicationModuleRepository.java b/src/main/java/org/apache/airavata/application/repository/ApplicationModuleRepository.java
deleted file mode 100644
index 4096c6ecb8..0000000000
--- a/src/main/java/org/apache/airavata/application/repository/ApplicationModuleRepository.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
-*
-* 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.
-*/
-package org.apache.airavata.application.repository;
-
-import org.apache.airavata.application.model.ApplicationModuleEntity;
-import org.springframework.data.repository.CrudRepository;
-
-public interface ApplicationModuleRepository extends CrudRepository<ApplicationModuleEntity, String> {
-
-}
diff --git a/src/main/java/org/apache/airavata/application/repository/ApplicationOutputRepository.java b/src/main/java/org/apache/airavata/application/repository/ApplicationOutputRepository.java
deleted file mode 100644
index 037e3d4681..0000000000
--- a/src/main/java/org/apache/airavata/application/repository/ApplicationOutputRepository.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
-*
-* 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.
-*/
-package org.apache.airavata.application.repository;
-
-import org.springframework.data.repository.CrudRepository;
-import org.apache.airavata.application.model.AppIoParamEntity;
-
-public interface ApplicationOutputRepository extends CrudRepository<AppIoParamEntity, String> {
-
-}
diff --git a/src/main/java/org/apache/airavata/application/repository/ApplicationTemplateRepository.java b/src/main/java/org/apache/airavata/application/repository/ApplicationTemplateRepository.java
new file mode 100644
index 0000000000..921feb9f3b
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/repository/ApplicationTemplateRepository.java
@@ -0,0 +1,11 @@
+package org.apache.airavata.application.repository;
+
+import org.apache.airavata.application.model.template.ApplicationTemplateEntity;
+import org.springframework.data.repository.ListCrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ApplicationTemplateRepository extends ListCrudRepository<ApplicationTemplateEntity, String> {
+
+ boolean existsByTemplateName(String templateName);
+}
diff --git a/src/main/java/org/apache/airavata/application/repository/SlurmApplicationDeploymentRepository.java b/src/main/java/org/apache/airavata/application/repository/SlurmApplicationDeploymentRepository.java
new file mode 100644
index 0000000000..29e2964cb1
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/repository/SlurmApplicationDeploymentRepository.java
@@ -0,0 +1,15 @@
+package org.apache.airavata.application.repository;
+
+import java.util.List;
+import org.apache.airavata.application.model.deployment.SlurmApplicationDeploymentEntity;
+import org.springframework.data.repository.ListCrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SlurmApplicationDeploymentRepository
+ extends ListCrudRepository<SlurmApplicationDeploymentEntity, String> {
+
+ List<SlurmApplicationDeploymentEntity> findByApplicationTemplate_TemplateId(String templateId);
+
+ boolean existsByApplicationTemplate_TemplateId(String templateId);
+}
diff --git a/src/main/java/org/apache/airavata/application/service/ApplicationInputService.java b/src/main/java/org/apache/airavata/application/service/ApplicationInputService.java
deleted file mode 100644
index fbf90dcfd1..0000000000
--- a/src/main/java/org/apache/airavata/application/service/ApplicationInputService.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.apache.airavata.application.service;
-
-import org.springframework.stereotype.Component;
-
-@Component
-public class ApplicationInputService {
-
-}
diff --git a/src/main/java/org/apache/airavata/application/service/ApplicationInterfaceService.java b/src/main/java/org/apache/airavata/application/service/ApplicationInterfaceService.java
deleted file mode 100644
index 54dc8f7bba..0000000000
--- a/src/main/java/org/apache/airavata/application/service/ApplicationInterfaceService.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.apache.airavata.application.service;
-
-import org.apache.airavata.application.mapper.ApplicationMapper;
-import org.apache.airavata.application.model.ApplicationInterfaceEntity;
-import org.apache.airavata.models.appcatalog.appinterface.ApplicationInterfaceDescription;
-import org.springframework.stereotype.Component;
-
-@Component
-public class ApplicationInterfaceService {
-
- protected ApplicationInterfaceDescription toModel(ApplicationInterfaceEntity entity) {
- return ApplicationMapper.INSTANCE.appInterfaceToModel(entity);
- }
-
- protected ApplicationInterfaceEntity toEntity(ApplicationInterfaceDescription model) {
- return ApplicationMapper.INSTANCE.appInterfaceToEntity(model);
- }
-}
diff --git a/src/main/java/org/apache/airavata/application/service/ApplicationModuleService.java b/src/main/java/org/apache/airavata/application/service/ApplicationModuleService.java
deleted file mode 100644
index 0ecbe8ea37..0000000000
--- a/src/main/java/org/apache/airavata/application/service/ApplicationModuleService.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.apache.airavata.application.service;
-
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.airavata.application.mapper.ApplicationMapper;
-import org.apache.airavata.application.model.ApplicationModuleEntity;
-import org.apache.airavata.application.repository.ApplicationModuleRepository;
-import org.apache.airavata.common.AiravataUtils;
-import org.apache.airavata.common.RequestContext;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.stereotype.Component;
-import org.apache.airavata.models.appcatalog.appdeployment.ApplicationModule;
-
-@Component
-public class ApplicationModuleService {
-
- private final ApplicationModuleRepository applicationModuleRepository;
-
- public ApplicationModuleService(ApplicationModuleRepository applicationModuleRepository) {
- this.applicationModuleRepository = applicationModuleRepository;
- }
-
- public ApplicationModule getApplicationModule(RequestContext requestContext, String moduleId) throws Exception {
- ApplicationModuleEntity entity = applicationModuleRepository.findById(moduleId)
- .orElseThrow(() -> new Exception("Application module not found: " + moduleId));
- return toModel(entity);
- }
-
- public List<ApplicationModule> getAllApplicationModules() throws Exception {
- List<ApplicationModule> modules = new ArrayList<>();
- applicationModuleRepository.findAll().forEach(entity -> modules.add(toModel(entity)));
- return modules;
- }
-
- public ApplicationModule createApplicationModule(RequestContext requestContext, ApplicationModule module)
- throws Exception {
- String moduleId = module.appModuleId() == null || module.appModuleId().isBlank()
- ? AiravataUtils.getId(module.appModuleName())
- : module.appModuleId();
- ApplicationModuleEntity entity = toEntity(module.toBuilder().setAppModuleId(moduleId).build());
- entity.setGatewayId("default"); // TODO: Set with defaiult gatewayId from requestContext when available
- Timestamp now = new Timestamp(System.currentTimeMillis());
- entity.setCreationTime(now);
- entity.setUpdateTime(now);
- ApplicationModuleEntity savedEntity = applicationModuleRepository.save(entity);
- return toModel(savedEntity);
- }
-
- public ApplicationModule updateApplicationModule(RequestContext requestContext, String moduleId,
- ApplicationModule module) throws Exception {
- ApplicationModuleEntity existingEntity = applicationModuleRepository.findById(moduleId)
- .orElseThrow(() -> new Exception("Application module not found: " + moduleId));
- ApplicationModuleEntity updatedEntity = toEntity(module);
- updatedEntity.setAppModuleId(existingEntity.getAppModuleId()); // Preserve the original ID
- ApplicationModuleEntity savedEntity = applicationModuleRepository.save(updatedEntity);
- return toModel(savedEntity);
- }
-
- // Demo of the JWT -> role-lookup -> @PreAuthorize wiring: deleting a module now
- // requires the caller's bearer token to resolve to the ADMIN authority.
- @PreAuthorize("hasAuthority('ADMIN')")
- public void deleteApplicationModule(RequestContext requestContext, String moduleId) throws Exception {
- if (!applicationModuleRepository.existsById(moduleId)) {
- throw new Exception("Application module not found: " + moduleId);
- }
- applicationModuleRepository.deleteById(moduleId);
- }
-
- protected ApplicationModule toModel(ApplicationModuleEntity entity) {
- return ApplicationMapper.INSTANCE.appModuleToModel(entity);
- }
-
- protected ApplicationModuleEntity toEntity(ApplicationModule model) {
- return ApplicationMapper.INSTANCE.appModuleToEntity(model);
- }
-
-}
diff --git a/src/main/java/org/apache/airavata/application/service/ApplicationTemplateService.java b/src/main/java/org/apache/airavata/application/service/ApplicationTemplateService.java
new file mode 100644
index 0000000000..d9d91c9c37
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/service/ApplicationTemplateService.java
@@ -0,0 +1,117 @@
+package org.apache.airavata.application.service;
+
+import java.util.List;
+import org.apache.airavata.application.dto.template.ApplicationTemplateInputDto;
+import org.apache.airavata.application.dto.template.ApplicationTemplateOutputDto;
+import org.apache.airavata.application.dto.template.ApplicationTemplateRequestDto;
+import org.apache.airavata.application.dto.template.ApplicationTemplateResponseDto;
+import org.apache.airavata.application.mapper.ApplicationTemplateMapper;
+import org.apache.airavata.application.model.template.ApplicationTemplateEntity;
+import org.apache.airavata.application.model.template.ApplicationTemplateInputEntity;
+import org.apache.airavata.application.model.template.ApplicationTemplateOutputEntity;
+import org.apache.airavata.application.repository.ApplicationTemplateRepository;
+import org.apache.airavata.application.repository.SlurmApplicationDeploymentRepository;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.server.ResponseStatusException;
+
+/**
+ * CRUD for application templates and their owned input/output declarations.
+ *
+ * <p>Reads run inside a transaction so the lazily loaded input/output collections can be
+ * mapped to DTOs before the session closes — {@code spring.jpa.open-in-view=false} means
+ * they cannot be walked from the serialization layer.
+ */
+@Service
+public class ApplicationTemplateService {
+
+ private final ApplicationTemplateRepository templateRepository;
+ private final SlurmApplicationDeploymentRepository deploymentRepository;
+ private final ApplicationTemplateMapper mapper;
+
+ public ApplicationTemplateService(
+ ApplicationTemplateRepository templateRepository,
+ SlurmApplicationDeploymentRepository deploymentRepository,
+ ApplicationTemplateMapper mapper) {
+ this.templateRepository = templateRepository;
+ this.deploymentRepository = deploymentRepository;
+ this.mapper = mapper;
+ }
+
+ @Transactional(readOnly = true)
+ public List<ApplicationTemplateResponseDto> getAllTemplates() {
+ return templateRepository.findAll().stream()
+ .map(mapper::toResponseDto)
+ .toList();
+ }
+
+ @Transactional(readOnly = true)
+ public ApplicationTemplateResponseDto getTemplate(String templateId) {
+ return mapper.toResponseDto(findOrThrow(templateId));
+ }
+
+ @PreAuthorize("hasAnyAuthority('ADMIN', 'SUPER_ADMIN')")
+ @Transactional
+ public ApplicationTemplateResponseDto createTemplate(ApplicationTemplateRequestDto request) {
+ ApplicationTemplateEntity entity = new ApplicationTemplateEntity();
+ entity.setTemplateName(request.getTemplateName());
+ entity.setTemplateDescription(request.getTemplateDescription());
+ replaceChildren(entity, request);
+ return mapper.toResponseDto(templateRepository.save(entity));
+ }
+
+ @PreAuthorize("hasAnyAuthority('ADMIN', 'SUPER_ADMIN')")
+ @Transactional
+ public ApplicationTemplateResponseDto updateTemplate(String templateId, ApplicationTemplateRequestDto request) {
+ ApplicationTemplateEntity entity = findOrThrow(templateId);
+ entity.setTemplateName(request.getTemplateName());
+ entity.setTemplateDescription(request.getTemplateDescription());
+ replaceChildren(entity, request);
+ return mapper.toResponseDto(templateRepository.save(entity));
+ }
+
+ @PreAuthorize("hasAnyAuthority('ADMIN', 'SUPER_ADMIN')")
+ @Transactional
+ public void deleteTemplate(String templateId) {
+ ApplicationTemplateEntity entity = findOrThrow(templateId);
+ if (deploymentRepository.existsByApplicationTemplate_TemplateId(templateId)) {
+ throw new ResponseStatusException(
+ HttpStatus.CONFLICT, "Template has deployments and cannot be deleted: " + templateId);
+ }
+ templateRepository.delete(entity);
+ }
+
+ private ApplicationTemplateEntity findOrThrow(String templateId) {
+ return templateRepository
+ .findById(templateId)
+ .orElseThrow(() ->
+ new ResponseStatusException(HttpStatus.NOT_FOUND, "Template not found: " + templateId));
+ }
+
+ /**
+ * Rewrites the owned inputs/outputs in place. The existing collections are mutated
+ * rather than reassigned — swapping the list instance out from under an
+ * orphan-removing association makes Hibernate throw.
+ */
+ private void replaceChildren(ApplicationTemplateEntity entity, ApplicationTemplateRequestDto request) {
+ entity.getInputs().clear();
+ if (request.getInputs() != null) {
+ for (ApplicationTemplateInputDto dto : request.getInputs()) {
+ ApplicationTemplateInputEntity input = mapper.toInputEntity(dto);
+ input.setApplicationTemplate(entity);
+ entity.getInputs().add(input);
+ }
+ }
+
+ entity.getOutputs().clear();
+ if (request.getOutputs() != null) {
+ for (ApplicationTemplateOutputDto dto : request.getOutputs()) {
+ ApplicationTemplateOutputEntity output = mapper.toOutputEntity(dto);
+ output.setApplicationTemplate(entity);
+ entity.getOutputs().add(output);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/airavata/application/service/SlurmApplicationDeploymentService.java b/src/main/java/org/apache/airavata/application/service/SlurmApplicationDeploymentService.java
new file mode 100644
index 0000000000..5fb17fb8ad
--- /dev/null
+++ b/src/main/java/org/apache/airavata/application/service/SlurmApplicationDeploymentService.java
@@ -0,0 +1,88 @@
+package org.apache.airavata.application.service;
+
+import java.util.List;
+import org.apache.airavata.application.dto.deployment.SlurmApplicationDeploymentRequestDto;
+import org.apache.airavata.application.dto.deployment.SlurmApplicationDeploymentResponseDto;
+import org.apache.airavata.application.mapper.SlurmApplicationDeploymentMapper;
+import org.apache.airavata.application.model.deployment.SlurmApplicationDeploymentEntity;
+import org.apache.airavata.application.model.template.ApplicationTemplateEntity;
+import org.apache.airavata.application.repository.ApplicationTemplateRepository;
+import org.apache.airavata.application.repository.SlurmApplicationDeploymentRepository;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.server.ResponseStatusException;
+
+/** CRUD for Slurm deployments of an application template. */
+@Service
+public class SlurmApplicationDeploymentService {
+
+ private final SlurmApplicationDeploymentRepository deploymentRepository;
+ private final ApplicationTemplateRepository templateRepository;
+ private final SlurmApplicationDeploymentMapper mapper;
+
+ public SlurmApplicationDeploymentService(
+ SlurmApplicationDeploymentRepository deploymentRepository,
+ ApplicationTemplateRepository templateRepository,
+ SlurmApplicationDeploymentMapper mapper) {
+ this.deploymentRepository = deploymentRepository;
+ this.templateRepository = templateRepository;
+ this.mapper = mapper;
+ }
+
+ @Transactional(readOnly = true)
+ public List<SlurmApplicationDeploymentResponseDto> getAllDeployments() {
+ return deploymentRepository.findAll().stream().map(mapper::toResponseDto).toList();
+ }
+
+ @Transactional(readOnly = true)
+ public List<SlurmApplicationDeploymentResponseDto> getDeploymentsByTemplate(String templateId) {
+ return deploymentRepository.findByApplicationTemplate_TemplateId(templateId).stream()
+ .map(mapper::toResponseDto)
+ .toList();
+ }
+
+ @Transactional(readOnly = true)
+ public SlurmApplicationDeploymentResponseDto getDeployment(String deploymentId) {
+ return mapper.toResponseDto(findOrThrow(deploymentId));
+ }
+
+ @PreAuthorize("hasAnyAuthority('ADMIN', 'SUPER_ADMIN')")
+ @Transactional
+ public SlurmApplicationDeploymentResponseDto createDeployment(SlurmApplicationDeploymentRequestDto request) {
+ SlurmApplicationDeploymentEntity entity = mapper.toEntity(request);
+ entity.setApplicationTemplate(resolveTemplate(request.getTemplateId()));
+ return mapper.toResponseDto(deploymentRepository.save(entity));
+ }
+
+ @PreAuthorize("hasAnyAuthority('ADMIN', 'SUPER_ADMIN')")
+ @Transactional
+ public SlurmApplicationDeploymentResponseDto updateDeployment(
+ String deploymentId, SlurmApplicationDeploymentRequestDto request) {
+ SlurmApplicationDeploymentEntity entity = findOrThrow(deploymentId);
+ mapper.updateEntity(request, entity);
+ entity.setApplicationTemplate(resolveTemplate(request.getTemplateId()));
+ return mapper.toResponseDto(deploymentRepository.save(entity));
+ }
+
+ @PreAuthorize("hasAnyAuthority('ADMIN', 'SUPER_ADMIN')")
+ @Transactional
+ public void deleteDeployment(String deploymentId) {
+ deploymentRepository.delete(findOrThrow(deploymentId));
+ }
+
+ private SlurmApplicationDeploymentEntity findOrThrow(String deploymentId) {
+ return deploymentRepository
+ .findById(deploymentId)
+ .orElseThrow(() ->
+ new ResponseStatusException(HttpStatus.NOT_FOUND, "Deployment not found: " + deploymentId));
+ }
+
+ private ApplicationTemplateEntity resolveTemplate(String templateId) {
+ return templateRepository
+ .findById(templateId)
+ .orElseThrow(() ->
+ new ResponseStatusException(HttpStatus.NOT_FOUND, "Template not found: " + templateId));
+ }
+}