diff --git a/pom.xml b/pom.xml index b1ca3f2..6086b25 100644 --- a/pom.xml +++ b/pom.xml @@ -79,10 +79,10 @@ org.springframework.boot spring-boot-starter-webflux - + org.springdoc springdoc-openapi-starter-webmvc-ui - 2.0.2 + 2.8.2 org.projectlombok diff --git a/src/main/java/it/cnr/isti/workflow/manager/configurations/OpenApiConfig.java b/src/main/java/it/cnr/isti/workflow/manager/configurations/OpenApiConfig.java new file mode 100644 index 0000000..8f46877 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/configurations/OpenApiConfig.java @@ -0,0 +1,30 @@ +package it.cnr.isti.workflow.manager.configurations; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.security.SecurityScheme.Type; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI customOpenAPI() { + final String securitySchemeName = "bearerAuth"; + + return new OpenAPI() + .info(new Info().title("API Docs").version("v1")) + .components(new io.swagger.v3.oas.models.Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } +} + diff --git a/src/main/java/it/cnr/isti/workflow/manager/configurations/SecurityConfig.java b/src/main/java/it/cnr/isti/workflow/manager/configurations/SecurityConfig.java index 4cd6eed..6144282 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/configurations/SecurityConfig.java +++ b/src/main/java/it/cnr/isti/workflow/manager/configurations/SecurityConfig.java @@ -14,7 +14,6 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - @Configuration @EnableWebSecurity @EnableMethodSecurity @@ -26,17 +25,19 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http - .cors(Customizer.withDefaults()) - .csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/auth/**").permitAll() - .requestMatchers(HttpMethod.GET,"/health").permitAll() - .requestMatchers(HttpMethod.GET,"/types/nodes/**").permitAll() - .anyRequest().authenticated() - ) - .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) - .build(); + .cors(Customizer.withDefaults()) + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/auth/**").permitAll() + //.requestMatchers(HttpMethod.GET, "/health").permitAll() + .requestMatchers(HttpMethod.GET, "/types/nodes/**").permitAll() + .requestMatchers("/swagger-ui.html").permitAll() + .requestMatchers("/swagger-ui/**").permitAll() + .requestMatchers("/v3/api-docs/**").permitAll() + .anyRequest().authenticated()) + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .build(); } @Bean diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionController.java index e8780ba..6b5597b 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionController.java +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionController.java @@ -8,14 +8,17 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.server.WebServerException; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import it.cnr.isti.workflow.manager.exceptions.WebException; import it.cnr.isti.workflow.manager.executors.ExecutionObject; import it.cnr.isti.workflow.manager.model.ExecutionContext; import it.cnr.isti.workflow.manager.model.flows.Flow; @@ -28,6 +31,44 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +/** + * The ExecutionController class provides RESTful endpoints for managing + * execution objects and their associated contexts and inputs. It allows + * clients to create, retrieve, update, and delete execution objects, as + * well as prepare inputs for executions. + * + *

Endpoints include: + *

+ * + *

Exception handling is provided for cases where a requested flow or + * execution is not found, returning appropriate HTTP status codes. + * + *

Security is enforced using the "bearerAuth" security requirement. + * + *

Dependencies: + *

+ * + *

Logging is performed using SLF4J to provide insights into the operations + * performed by the controller. + * + *

Annotations: + *

+ */ +@SecurityRequirement(name = "bearerAuth") @RestController @RequestMapping("/executions") public class ExecutionController { @@ -40,6 +81,16 @@ public class ExecutionController { @Autowired ExecutionService executionService; + + /** + * Creates a new execution object for the specified flow. + * + * @param flowId the ID of the flow for which the execution is to be created + * @return the created {@link ExecutionObject} + * @throws IllegalArgumentException if the flow with the specified ID is not found + * @throws WebServerException if an error occurs while creating the execution + */ + @Operation(summary = "Creates an execution", description = "Creates a new execution object for the specified flow.") @PostMapping() public ExecutionObject create(@RequestBody String flowId) { logger.info("Creating execution for flow {}", flowId); @@ -50,39 +101,104 @@ public class ExecutionController { return eo; } catch (Throwable e) { logger.error("Error creating execution for flow {}", flowId, e); - throw new WebServerException("Error while creating execution", e); + throw new WebException(HttpStatus.INTERNAL_SERVER_ERROR,"Error while creating execution"); } } + /** + * Retrieves a list of all executions. + * + * @return a list of {@link ExecutionObject} representing all executions. + */ + @Operation(summary = "Retrieves executions", description = "Retrieves a list of all executions") @GetMapping() public List getAll() { return executionService.getAllExecutions(); } + + + /** + * Retrieves all execution contexts. + * + * @return a map of execution IDs to their corresponding {@link ExecutionContext} + */ + @Operation(summary = "Retrieves contexts", description = "Retrieves all execution contexts") @GetMapping(path = "contexts") public Map getAllContexts() { return executionService.getAllExecutions().stream().collect(Collectors.toMap(ExecutionObject::getId, ExecutionObject::getContext)); } @GetMapping(path = "{id}") + /** + * Retrieves a specific execution by its ID. + * + * @param id the ID of the execution to retrieve + * @return the {@link ExecutionObject} with the specified ID + * @throws IllegalArgumentException if the execution with the specified ID is not found + */ + @Operation(summary = "Retrieves an execution", description = "Retrieves a specific execution by its ID") public ExecutionObject get(@PathVariable String id) { return executionService.getExecution(id); } + /** + * Deletes an execution with the specified ID. + * + * @param id the unique identifier of the execution to be deleted + * @return a ResponseEntity indicating the result of the operation + */ + @DeleteMapping(path = "{id}") + @Operation(summary = "Deletes an execution", description = "Deletes an execution with the specified ID") + public ResponseEntity delete(@PathVariable String id) { + executionService.removeExecution(id); + return ResponseEntity.ok().build(); + } + + /** + * Starts the execution of a workflow with the given ID. + * + * @param id the unique identifier of the workflow to start + * @return the {@link ExecutionObject} representing the started execution + */ @PutMapping(path = "{id}/start") + @Operation(summary = "Starts an execution", description = "Starts the execution of a workflow with the given ID") public ExecutionObject start(@PathVariable String id) { return executionService.startExecution(id); } + + /** + * Prepares the input for an execution by associating a given input string + * with a specific input name and execution ID. + * + * @param input The input string to be prepared and associated. + * @param inputName The name of the input to be associated with the execution. + * @param id The unique identifier of the execution. + * @return An {@link ExecutionObject} representing the updated execution state + * after preparing the input. + */ @PutMapping(path = "{id}/input/{inputName}") + @Operation(summary = "Prepares string inputs", description = "Prepares the input for an execution by associating a given input string with a specific input name and execution ID.") public ExecutionObject prepareStringInputs(@RequestBody String input, @PathVariable String inputName, @PathVariable String id) { return executionService.prepareInput(id, inputName, input); } + /** + * Prepares file inputs for a specific execution by uploading a file and associating it + * with the given input name and execution ID. + * + * @param file The multipart file to be uploaded and processed. + * @param inputName The name of the input to associate the file with. + * @param id The ID of the execution to which the input belongs. + * @return An {@link ExecutionObject} representing the updated execution state after + * preparing the input. + * @throws WebServerException If an error occurs while creating or transferring the file. + */ @PutMapping(path = "{id}/input/{inputName}", consumes = "multipart/form-data") - @ResponseBody + @Operation(summary = "Prepares file inputs", description = "Prepares file inputs for a specific execution by uploading a file and associating it with the given input name and execution ID.") public ExecutionObject prepareFileInputs(@RequestParam("file") MultipartFile file, @PathVariable String inputName, @PathVariable String id) { try { @@ -95,8 +211,4 @@ public class ExecutionController { } - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity executionNotFound(IllegalArgumentException exc) { - return ResponseEntity.notFound().build(); - } } diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/FlowsController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/FlowsController.java index 2886d98..854d7a4 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/controllers/FlowsController.java +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/FlowsController.java @@ -10,12 +10,12 @@ import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import it.cnr.isti.workflow.manager.exceptions.WebException; import it.cnr.isti.workflow.manager.model.auth.LoginEntity; import it.cnr.isti.workflow.manager.model.flows.Flow; @@ -29,7 +29,32 @@ import org.springframework.web.bind.annotation.GetMapping; - +/** + * Controller for managing flows. + * + * This controller provides endpoints for creating, updating, retrieving, and listing flows. + * It enforces security requirements and ensures that users can only access or modify flows + * they have created or that are marked as public. + * + * Endpoints: + * - POST /flows: Create a new flow. + * - PUT /flows/{id}: Update an existing flow. + * - GET /flows: Retrieve all flows created by the user or public flows. + * - GET /flows/{id}: Retrieve a specific flow by its ID. + * + * Security: + * - Requires bearer authentication. + * - Users can only modify or retrieve flows they created unless the flow is public. + * + * Dependencies: + * - FlowRepository: Repository for accessing and managing flow data. + * - Logger: Used for logging flow creation and updates. + * + * Exceptions: + * - WebException: Thrown for various HTTP error scenarios, such as invalid input, + * unauthorized access, or resource not found. + */ +@SecurityRequirement(name = "bearerAuth") @RestController @RequestMapping("/flows") public class FlowsController { @@ -40,7 +65,20 @@ public class FlowsController { FlowRepository flowRepository; + /** + * Creates a new flow. + * + * This method handles HTTP POST requests with a JSON payload to create a new flow. + * It validates that the flow ID is null (as it must be generated during creation), + * sets the creator's username, and saves the flow to the repository. + * + * @param flow The flow object to be created, provided in the request body. + * @param userDetails The authenticated user's details, used to set the creator of the flow. + * @return The saved flow object with its generated ID and other persisted details. + * @throws WebException If the flow ID is not null, indicating an invalid creation request. + */ @PostMapping(consumes = "application/json") + @Operation(summary = "Create a new flow", description = "Creates a new flow in the system.") public Flow createFlow(@RequestBody Flow flow, @AuthenticationPrincipal LoginEntity userDetails) { //validateFlow(flow); if (flow.getId() != null) @@ -52,7 +90,19 @@ public class FlowsController { return savedFlow; } + /** + * Updates and saves an existing Flow entity. + * + * @param id The ID of the Flow to be updated. Must match the ID of the provided Flow object. + * @param flow The Flow object containing the updated data. + * @param userDetails The authenticated user's details, used to verify ownership of the Flow. + * @return The updated and saved Flow object. + * @throws WebException If the provided Flow ID does not match the path variable ID (BAD_REQUEST), + * if the Flow is not found (NOT_FOUND), + * or if the authenticated user is not the creator of the Flow (FORBIDDEN). + */ @PutMapping(path = "{id}", consumes = "application/json") + @Operation(summary = "Update an existing flow", description = "Updates an existing flow in the system.") public Flow saveFlow(@PathVariable String id, @RequestBody Flow flow, @AuthenticationPrincipal LoginEntity userDetails) { //validateFlow(flow); if (!id.equals(flow.getId())) @@ -62,12 +112,26 @@ public class FlowsController { if (!savedFlow.getCreatedBy().equals(userDetails.getUsername())) throw new WebException(HttpStatus.FORBIDDEN, "Flow not created by user"); - savedFlow = flowRepository.save(savedFlow); + savedFlow = flowRepository.save(flow); logger.info("Flow saved: {} ",savedFlow.getId()); return savedFlow; } + /** + * Retrieves all flows created by the authenticated user or public flows. + * + *

This endpoint is mapped to a GET request and provides a list of flows + * that are either created by the currently authenticated user or are marked + * as public. The method uses the user's authentication details to filter + * the flows accordingly.

+ * + * @param userDetails The authentication principal containing the details of + * the currently authenticated user. + * @return A list of {@link Flow} objects representing the flows accessible + * to the authenticated user. + */ @GetMapping() + @Operation(summary = "Get all flows", description = "Retrieves all flows created by the user or public flows.") public List getAllFlows(@AuthenticationPrincipal LoginEntity userDetails) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); System.out.println("Authentication: " + auth); @@ -77,6 +141,21 @@ public class FlowsController { } @GetMapping("{id}") + @Operation(summary = "Get a flow by ID", description = "Retrieves a specific flow by its ID.") + /** + * Retrieves a specific flow by its ID. + * + *

This endpoint is mapped to a GET request and allows the retrieval of + * a flow based on its unique identifier. The method checks if the flow + * exists and whether the authenticated user has permission to access it.

+ * + * @param id The ID of the flow to be retrieved. + * @param userDetails The authentication principal containing the details of + * the currently authenticated user. + * @return The {@link Flow} object representing the requested flow. + * @throws WebException If the flow is not found (NOT_FOUND) or if the user + * does not have permission to access it (FORBIDDEN). + */ public Flow getFlow(@PathVariable String id, @AuthenticationPrincipal LoginEntity userDetails) { Flow flowEntity = flowRepository.findById(id).orElseThrow(() -> new WebException(HttpStatus.NOT_FOUND,"Flow not found")); if (!flowEntity.getCreatedBy().equals(userDetails.getUsername()) && !flowEntity.isPublic()) diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/NodeDefinitionController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/NodeDefinitionController.java index afb0634..6af69b2 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/controllers/NodeDefinitionController.java +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/NodeDefinitionController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import it.cnr.isti.workflow.manager.exceptions.WebException; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import it.cnr.isti.workflow.manager.executors.Executor; import it.cnr.isti.workflow.manager.model.ExecutorDescriptor; import it.cnr.isti.workflow.manager.model.types.NodeDefinition; @@ -29,46 +29,32 @@ public class NodeDefinitionController { @Autowired private List executors; + @SecurityRequirement(name = "bearerAuth") @GetMapping("/nodes") public List getNodes() { - try { - return (List) repository.findAll(); - } catch (Throwable e) { - logger.error("Error retrieving nodes", e); - throw new RuntimeException("Error retrieving nodes", e); - } + return (List) repository.findAll(); + } + @GetMapping("/nodes/types") public List getStoredNodeTypes() { - try { - return repository.getUsedTypes(); - } catch (Throwable e) { - logger.error("Error retrieving stored types", e); - throw new WebException(HttpStatus.INTERNAL_SERVER_ERROR, "Error retrieving stored types"); - } + return repository.getUsedTypes(); + } @GetMapping("/nodes/categories") public List getStoredNodeCategories() { - try { - return repository.getCategories(); - } catch (Throwable e) { - logger.error("Error retrieving stored categories", e); - throw new WebException(HttpStatus.INTERNAL_SERVER_ERROR, "Error retrieving stored categories"); - } + return repository.getCategories(); } + @SecurityRequirement(name = "bearerAuth") @PostMapping("/nodes") void addNode(@RequestBody NodeDefinition node) { - try { - repository.save(node); - } catch (Exception e) { - logger.error("Error saving node", e); - throw new RuntimeException("Error saving node", e); - } + repository.save(node); } + @SecurityRequirement(name = "bearerAuth") @GetMapping("/executors") public List getAvailableExecutors() { return executors.stream().map(entry -> entry.getDescriptor(null)).toList(); diff --git a/src/main/java/it/cnr/isti/workflow/manager/exceptions/GlobalExceptionHandler.java b/src/main/java/it/cnr/isti/workflow/manager/exceptions/GlobalExceptionHandler.java index ecc4a8c..19d0e1d 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/it/cnr/isti/workflow/manager/exceptions/GlobalExceptionHandler.java @@ -1,16 +1,23 @@ package it.cnr.isti.workflow.manager.exceptions; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; @ControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(WebException.class) - public ResponseEntity handleWebException(WebException ex) { + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleNotFound(ResourceNotFoundException ex, WebRequest request) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception ex) { return ResponseEntity - .status(ex.getStatus()) - .body(ex.getMessage()); + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("An unexpected error occurred: " + ex.getMessage()); } } diff --git a/src/main/java/it/cnr/isti/workflow/manager/exceptions/InvalidStateException.java b/src/main/java/it/cnr/isti/workflow/manager/exceptions/InvalidStateException.java new file mode 100644 index 0000000..ff86064 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/exceptions/InvalidStateException.java @@ -0,0 +1,24 @@ +package it.cnr.isti.workflow.manager.exceptions; + +public class InvalidStateException extends Exception { + + private static final long serialVersionUID = 1L; + + public InvalidStateException(String message) { + super(message); + } + + public InvalidStateException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidStateException(Throwable cause) { + super(cause); + } + + public InvalidStateException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/exceptions/ResourceNotFoundException.java b/src/main/java/it/cnr/isti/workflow/manager/exceptions/ResourceNotFoundException.java new file mode 100644 index 0000000..cedbeec --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/exceptions/ResourceNotFoundException.java @@ -0,0 +1,14 @@ +package it.cnr.isti.workflow.manager.exceptions; + +public class ResourceNotFoundException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ResourceNotFoundException(String message) { + super(message); + } + + public ResourceNotFoundException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/exceptions/WebException.java b/src/main/java/it/cnr/isti/workflow/manager/exceptions/WebException.java index 3670a27..b30f810 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/exceptions/WebException.java +++ b/src/main/java/it/cnr/isti/workflow/manager/exceptions/WebException.java @@ -13,4 +13,4 @@ public class WebException extends RuntimeException { public HttpStatus getStatus() { return status; } -} \ No newline at end of file +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/model/ExecutionContext.java b/src/main/java/it/cnr/isti/workflow/manager/model/ExecutionContext.java index df5ea1d..51dcd48 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/model/ExecutionContext.java +++ b/src/main/java/it/cnr/isti/workflow/manager/model/ExecutionContext.java @@ -14,22 +14,29 @@ import lombok.Setter; public class ExecutionContext { public enum Status { - CREATED(true), - INITIALIZING(true), - READY(true), - RUNNING(false), - SUCCESS(false), - ERROR(false); + CREATED(true, false), + INITIALIZING(true, false), + READY(true, false), + RUNNING(false, false), + SUCCESS(false, true), + ERROR(false, true); private boolean initState; - Status(boolean initState) { + private boolean finalState; + + Status(boolean initState, boolean finalState) { this.initState = initState; + this.finalState = finalState; } public boolean isInitState() { return initState; } + + public boolean isFinaltate() { + return finalState; + } } Map inputs = new HashMap<>(); diff --git a/src/main/java/it/cnr/isti/workflow/manager/services/ExecutionService.java b/src/main/java/it/cnr/isti/workflow/manager/services/ExecutionService.java index f4143d9..1ac3adb 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/services/ExecutionService.java +++ b/src/main/java/it/cnr/isti/workflow/manager/services/ExecutionService.java @@ -6,7 +6,6 @@ import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; - import it.cnr.isti.workflow.manager.executors.ExecutionObject; import it.cnr.isti.workflow.manager.model.ExecutionContext.Status; import it.cnr.isti.workflow.manager.model.flows.Flow; @@ -26,7 +25,10 @@ public class ExecutionService { } public ExecutionObject getExecution(String id) { - return executions.get(id); + ExecutionObject toReturn = executions.get(id); + if (toReturn == null) + throw new IllegalArgumentException("Execution with id " + id + " not found"); + return toReturn; } public List getAllExecutions() { @@ -34,14 +36,16 @@ public class ExecutionService { } public void removeExecution(String id) { + if (!executions.containsKey(id)) + throw new IllegalArgumentException("Execution with id " + id + " not found"); + if (executions.get(id).getContext().getStatus() == Status.RUNNING) + throw new IllegalStateException("Execution with id "+id+" is still running"); executions.remove(id); } public ExecutionObject prepareInput(String id, String key, Object input) { ExecutionObject eo = getExecution(id); - if (eo == null) - throw new IllegalArgumentException("Execution with id " + id + " not found"); - + if (!eo.getContext().getStatus().isInitState()) { throw new IllegalStateException("Execution with id " + id + " is not in initialization status (CURRENT STATUS is " + eo.getContext().getStatus() + ")"); @@ -58,8 +62,7 @@ public class ExecutionService { public ExecutionObject startExecution(String id) { ExecutionObject eo = getExecution(id); - if (eo == null) - throw new IllegalArgumentException("Execution with id " + id + " not found"); + if (eo.getContext().getStatus() != Status.READY) { throw new IllegalStateException("Execution with id " + id + " is not in READY status (CURRENT STATUS is " + eo.getContext().getStatus() + ")");